/*
 * To change this template, choose Tools | Templates and open the template in the editor.
 */

package com.bekvon.bukkit.residence.protection;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.bukkit.ChatColor;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.plugin.PluginManager;

import com.bekvon.bukkit.residence.event.ResidenceCreationEvent;
import com.bekvon.bukkit.residence.event.ResidenceDeleteEvent;
import com.bekvon.bukkit.residence.event.ResidenceDeleteEvent.DeleteCause;
import com.bekvon.bukkit.residence.permissions.PermissionGroup;

import pw.yumc.Residence.ResidenceMain;
import pw.yumc.Residence.selection.WECUI;
import pw.yumc.Residence.text.help.InformationPager;

import com.bekvon.bukkit.residence.event.ResidenceRenameEvent;

/**
 *
 * @author Administrator
 */
public class ResidenceManager {
    public static final class ChunkRef {

        private final int x;

        private final int z;

        public ChunkRef(final int x, final int z) {
            this.x = x;
            this.z = z;
        }

        public ChunkRef(final Location loc) {
            this.x = getChunkCoord(loc.getBlockX());
            this.z = getChunkCoord(loc.getBlockZ());
        }

        public static int getChunkCoord(final int val) {
            // For more info, see CraftBukkit.CraftWorld.getChunkAt( Location )
            return val >> 4;
        }

        @Override
        public boolean equals(final Object obj) {
            if (this == obj) {
                return true;
            }
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            final ChunkRef other = (ChunkRef) obj;
            return this.x == other.x && this.z == other.z;
        }

        @Override
        public int hashCode() {
            return x ^ z;
        }

        /**
         * Useful for debug
         */
        @Override
        public String toString() {
            final StringBuilder sb = new StringBuilder();
            sb.append("{ x: ").append(x).append(", z: ").append(z).append(" }");
            return sb.toString();
        }
    }

    protected Map<String, Map<ChunkRef, List<String>>> chunkResidences;
    protected ResidenceMain plugin;
    protected PluginManager pm;

    protected Map<String, ClaimedResidence> residences;

    public ResidenceManager(final ResidenceMain plugin) {
        this.plugin = plugin;
        pm = plugin.getServer().getPluginManager();
        residences = new HashMap<String, ClaimedResidence>();
        chunkResidences = new HashMap<String, Map<ChunkRef, List<String>>>();
    }

    @SuppressWarnings("unchecked")
    public static ResidenceManager load(final ResidenceMain plugin, final Map<String, Object> root) throws Exception {
        final ResidenceManager resm = new ResidenceManager(plugin);
        if (root == null) {
            return resm;
        }
        for (final World world : plugin.getServer().getWorlds()) {
            final Map<String, Object> reslist = (Map<String, Object>) root.get(world.getName());
            if (reslist != null) {
                try {
                    resm.chunkResidences.put(world.getName(), loadMap(plugin, reslist, resm));
                } catch (final Exception ex) {
                    plugin.getLogger().warning("载入世界: " + world.getName() + "的领地数据时发生错误!");
                    if (plugin.getConfigManager().stopOnSaveError()) {
                        throw (ex);
                    }
                }
            }
        }
        return resm;
    }

    @SuppressWarnings("unchecked")
    public static Map<ChunkRef, List<String>> loadMap(final ResidenceMain plugin, final Map<String, Object> root, final ResidenceManager resm) throws Exception {
        final Map<ChunkRef, List<String>> retRes = new HashMap<ChunkRef, List<String>>();
        if (root != null) {
            for (final Entry<String, Object> res : root.entrySet()) {
                try {
                    final ClaimedResidence residence = ClaimedResidence.load(plugin, (Map<String, Object>) res.getValue(), null);
                    for (final ChunkRef chunk : getChunks(residence)) {
                        final List<String> ress = new ArrayList<String>();
                        if (retRes.containsKey(chunk)) {
                            ress.addAll(retRes.get(chunk));
                        }
                        ress.add(res.getKey());
                        retRes.put(chunk, ress);
                    }
                    resm.residences.put(res.getKey(), residence);
                } catch (final Exception ex) {
                    plugin.getLogger().warning("无法载入 领地 (" + res.getKey() + ")! 由于:" + ex.getMessage() + " 错误日志:");
                    System.out.print("[Residence] Failed to load residence (" + res.getKey() + ")! Reason:" + ex.getMessage() + " Error Log:");
                    Logger.getLogger(ResidenceManager.class.getName()).log(Level.SEVERE, null, ex);
                    if (plugin.getConfigManager().stopOnSaveError()) {
                        throw (ex);
                    }
                }
            }
        }
        return retRes;
    }

    private static List<ChunkRef> getChunks(final ClaimedResidence res) {
        final List<ChunkRef> chunks = new ArrayList<ChunkRef>();
        for (final CuboidArea area : res.getAreaArray()) {
            chunks.addAll(area.getChunks());
        }
        return chunks;
    }

    public boolean addResidence(final Player player, final String name, final Location loc1, final Location loc2, final boolean resadmin) {
        return this.addResidence(player, player.getName(), name, loc1, loc2, resadmin);
    }

    public boolean addResidence(final Player player, final String owner, String name, final Location loc1, final Location loc2, final boolean resadmin) {
        if (!plugin.validName(name)) {
            if (player != null) {
                player.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("InvalidNameCharacters"));
            }
            return false;
        }
        if (loc1 == null || loc2 == null || !loc1.getWorld().getName().equals(loc2.getWorld().getName())) {
            if (player != null) {
                player.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("SelectPoints"));
            }
            return false;
        }
        final PermissionGroup group = plugin.getPermissionManager().getGroup(owner, loc1.getWorld().getName());
        final boolean createpermission = group.canCreateResidences() || (player == null ? true : player.hasPermission("residence.create"));
        if (!createpermission && !resadmin) {
            if (player != null) {
                player.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("NoPermission"));
            }
            return false;
        }
        if (player != null) {
            if (getOwnedZoneCount(player.getName()) >= group.getMaxZones() && !resadmin) {
                player.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("ResidenceTooMany"));
                return false;
            }
        }
        CuboidArea newArea = new CuboidArea(loc1, loc2);
        final ClaimedResidence newRes = new ClaimedResidence(plugin, owner, loc1.getWorld().getName());
        newRes.getPermissions().applyDefaultFlags();
        newRes.setEnterMessage(group.getDefaultEnterMessage());
        newRes.setLeaveMessage(group.getDefaultLeaveMessage());

        final ResidenceCreationEvent resevent = new ResidenceCreationEvent(player, name, newRes, newArea);
        pm.callEvent(resevent);
        if (resevent.isCancelled()) {
            return false;
        }
        newArea = resevent.getPhysicalArea();
        name = resevent.getResidenceName();
        if (residences.containsKey(name)) {
            if (player != null) {
                player.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("ResidenceAlreadyExists", ChatColor.YELLOW + name + ChatColor.RED));
            }
            return false;
        }
        if (player != null) {
            newRes.addArea(player, newArea, "main", resadmin);
        } else {
            newRes.addArea(newArea, "main");
        }
        if (newRes.getAreaCount() != 0) {
            residences.put(name, newRes);
            calculateChunks(name);
            plugin.getLeaseManager().removeExpireTime(name);
            if (player != null) {
                player.sendMessage(ChatColor.GREEN + plugin.getLanguage().getPhrase("ResidenceCreate", ChatColor.YELLOW + name + ChatColor.GREEN));
            }
            if (plugin.getConfigManager().useLeases()) {
                if (player != null) {
                    plugin.getLeaseManager().setExpireTime(player, name, group.getLeaseGiveTime());
                } else {
                    plugin.getLeaseManager().setExpireTime(name, group.getLeaseGiveTime());
                }
            }
            return true;
        }
        return false;
    }

    public boolean addResidence(final String name, final Location loc1, final Location loc2) {
        return this.addResidence(name, "Server Land", loc1, loc2);
    }

    public boolean addResidence(final String name, final String owner, final Location loc1, final Location loc2) {
        return this.addResidence(null, owner, name, loc1, loc2, true);
    }

    public void calculateChunks(final String name) {
        final ClaimedResidence res = residences.get(name);
        if (res != null) {
            final String world = res.getWorld();
            if (chunkResidences.get(world) == null) {
                chunkResidences.put(world, new HashMap<ChunkRef, List<String>>());
            }
            for (final ChunkRef chunk : getChunks(res)) {
                final List<String> ress = new ArrayList<String>();
                if (chunkResidences.get(world).containsKey(chunk)) {
                    ress.addAll(chunkResidences.get(world).get(chunk));
                }
                ress.add(name);
                chunkResidences.get(world).put(chunk, ress);
            }
        }
    }

    public String checkAreaCollision(final CuboidArea newarea, final ClaimedResidence parentResidence) {
        final Set<Entry<String, ClaimedResidence>> set = residences.entrySet();
        for (final Entry<String, ClaimedResidence> entry : set) {
            final ClaimedResidence check = entry.getValue();
            if (check != parentResidence && check.checkCollision(newarea)) {
                return entry.getKey();
            }
        }
        return null;
    }

    public ClaimedResidence getByLoc(final Location loc) {
        if (loc == null) {
            return null;
        }
        ClaimedResidence res = null;
        boolean found = false;
        final String world = loc.getWorld().getName();
        final ChunkRef chunk = new ChunkRef(loc);
        if (chunkResidences.get(world) != null) {
            if (chunkResidences.get(world).get(chunk) != null) {
                for (final String key : chunkResidences.get(world).get(chunk)) {
                    final ClaimedResidence entry = residences.get(key);
                    if (entry.containsLoc(loc)) {
                        res = entry;
                        found = true;
                        break;
                    }
                }
            }
        }
        if (!found) {
            return null;
        }
        if (res == null) {
            return null;
        }
        final ClaimedResidence subres = res.getSubzoneByLoc(loc);
        if (subres == null) {
            return res;
        }
        return subres;
    }

    public ClaimedResidence getByName(final String name) {
        if (name == null) {
            return null;
        }
        final String[] split = name.split("\\.");
        if (split.length == 1) {
            return residences.get(name);
        }
        ClaimedResidence res = residences.get(split[0]);
        for (int i = 1; i < split.length; i++) {
            if (res != null) {
                res = res.getSubzone(split[i]);
            } else {
                return null;
            }
        }
        return res;
    }

    public String getNameByLoc(final Location loc) {
        if (loc == null) {
            return null;
        }
        ClaimedResidence res = null;
        String name = null;
        boolean found = false;
        final String world = loc.getWorld().getName();
        final ChunkRef chunk = new ChunkRef(loc);
        if (chunkResidences.get(world) != null) {
            if (chunkResidences.get(world).get(chunk) != null) {
                for (final String key : chunkResidences.get(world).get(chunk)) {
                    final ClaimedResidence entry = residences.get(key);
                    if (entry.containsLoc(loc)) {
                        res = entry;
                        found = true;
                        break;
                    }
                }
            }
        }
        if (!found) {
            return null;
        }
        if (res == null) {
            return null;
        }
        name = res.getName();
        if (name == null) {
            return null;
        }
        final String szname = res.getSubzoneNameByLoc(loc);
        if (szname != null) {
            return name + "." + szname;
        }
        return name;
    }

    public String getNameByRes(final ClaimedResidence res) {
        final Set<Entry<String, ClaimedResidence>> set = residences.entrySet();
        for (final Entry<String, ClaimedResidence> check : set) {
            if (check.getValue() == res) {
                return check.getKey();
            }
            final String n = check.getValue().getSubzoneNameByRes(res);
            if (n != null) {
                return check.getKey() + "." + n;
            }
        }
        return null;
    }

    public int getOwnedZoneCount(final String player) {
        final Collection<ClaimedResidence> set = residences.values();
        int count = 0;
        for (final ClaimedResidence res : set) {
            if (res.getPermissions().getOwner().equalsIgnoreCase(player)) {
                count++;
            }
        }
        return count;
    }

    public int getResidenceCount() {
        return residences.size();
    }

    public String[] getResidenceList() {
        return this.getResidenceList(true, true).toArray(new String[0]);
    }

    public ArrayList<String> getResidenceList(final boolean showhidden, final boolean showsubzones) {
        return this.getResidenceList(null, showhidden, showsubzones, false);
    }

    public ArrayList<String> getResidenceList(final String targetplayer, final boolean showhidden, final boolean showsubzones) {
        return this.getResidenceList(targetplayer, showhidden, showsubzones, false);
    }

    public ArrayList<String> getResidenceList(final String targetplayer, final boolean showhidden, final boolean showsubzones, final boolean formattedOutput) {
        final ArrayList<String> list = new ArrayList<String>();
        for (final Entry<String, ClaimedResidence> res : residences.entrySet()) {
            this.getResidenceList(targetplayer, showhidden, showsubzones, "", res.getKey(), res.getValue(), list, formattedOutput);
        }
        return list;
    }

    public void giveResidence(final Player reqPlayer, final String targPlayer, final String residence, final boolean resadmin) {
        final ClaimedResidence res = getByName(residence);
        if (res == null) {
            reqPlayer.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("InvalidResidence"));
            return;
        }
        if (!res.getPermissions().hasResidencePermission(reqPlayer, true) && !resadmin) {
            reqPlayer.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("NoPermission"));
            return;
        }
        final Player giveplayer = plugin.getServer().getPlayer(targPlayer);
        if (giveplayer == null || !giveplayer.isOnline()) {
            reqPlayer.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("NotOnline"));
            return;
        }
        final CuboidArea[] areas = res.getAreaArray();
        final PermissionGroup g = plugin.getPermissionManager().getGroup(giveplayer);
        if (areas.length > g.getMaxPhysicalPerResidence() && !resadmin) {
            reqPlayer.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("ResidenceGiveLimits"));
            return;
        }
        if (getOwnedZoneCount(giveplayer.getName()) >= g.getMaxZones() && !resadmin) {
            reqPlayer.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("ResidenceGiveLimits"));
            return;
        }
        if (!resadmin) {
            for (final CuboidArea area : areas) {
                if (!g.inLimits(area)) {
                    reqPlayer.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("ResidenceGiveLimits"));
                    return;
                }
            }
        }
        res.getPermissions().setOwner(giveplayer.getName(), true);
        // Fix phrases here
        reqPlayer.sendMessage(
                ChatColor.GREEN + plugin.getLanguage().getPhrase("ResidenceGive", ChatColor.YELLOW + residence + ChatColor.GREEN + "." + ChatColor.YELLOW + giveplayer.getName() + ChatColor.GREEN));
        giveplayer.sendMessage(plugin.getLanguage().getPhrase("ResidenceRecieve", ChatColor.GREEN + residence + ChatColor.YELLOW + "." + ChatColor.GREEN + reqPlayer.getName() + ChatColor.YELLOW));
    }

    public void listAllResidences(final Player player, final int page) {
        this.listAllResidences(player, page, false);
    }

    public void listAllResidences(final Player player, final int page, final boolean showhidden) {
        this.listAllResidences(player, page, showhidden, false);
    }

    public void listAllResidences(final Player player, final int page, boolean showhidden, final boolean showsubzones) {
        if (showhidden && !plugin.isResAdminOn(player)) {
            showhidden = false;
        }
        InformationPager.printInfo(plugin, player, plugin.getLanguage().getPhrase("Residences"), this.getResidenceList(null, showhidden, showsubzones, true), page);
    }

    public void listResidences(final Player player) {
        this.listResidences(player, player.getName(), 1);
    }

    public void listResidences(final Player player, final int page) {
        this.listResidences(player, player.getName(), page);
    }

    public void listResidences(final Player player, final int page, final boolean showhidden) {
        this.listResidences(player, player.getName(), page, showhidden);
    }

    public void listResidences(final Player player, final String targetplayer) {
        this.listResidences(player, targetplayer, 1);
    }

    public void listResidences(final Player player, final String targetplayer, final int page) {
        this.listResidences(player, targetplayer, page, false);
    }

    public void listResidences(final Player player, final String targetplayer, final int page, final boolean showhidden) {
        this.listResidences(player, targetplayer, page, showhidden, false);
    }

    public void listResidences(final Player player, final String targetplayer, final int page, boolean showhidden, final boolean showsubzones) {
        if (showhidden && !plugin.isResAdminOn(player) && !player.getName().equals(targetplayer)) {
            showhidden = false;
        }
        InformationPager.printInfo(plugin, player, plugin.getLanguage().getPhrase("Residences") + " - " + targetplayer, this.getResidenceList(targetplayer, showhidden, showsubzones, true), page);
    }

    public void mirrorPerms(final Player reqPlayer, final String targetArea, final String sourceArea, final boolean resadmin) {
        final ClaimedResidence reciever = this.getByName(targetArea);
        final ClaimedResidence source = this.getByName(sourceArea);
        if (source == null || reciever == null) {
            reqPlayer.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("InvalidResidence"));
            return;
        }
        if (!resadmin) {
            if (!reciever.getPermissions().hasResidencePermission(reqPlayer, true) || !source.getPermissions().hasResidencePermission(reqPlayer, true)) {
                reqPlayer.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("NoPermission"));
                return;
            }
        }
        reciever.getPermissions().applyTemplate(reqPlayer, source.getPermissions(), resadmin);
    }

    public void printAreaInfo(final String areaname, final Player player) {
        final ClaimedResidence res = this.getByName(areaname);
        if (res == null) {
            player.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("InvalidResidence"));
            return;
        }
        final ResidencePermissions perms = res.getPermissions();
        if (plugin.getConfigManager().enableEconomy()) {
            player.sendMessage(ChatColor.YELLOW + plugin.getLanguage().getPhrase("Residence") + ":" + ChatColor.DARK_GREEN + " " + areaname + " " + ChatColor.YELLOW + "Bank: " + ChatColor.GOLD
                    + res.getBank().getStoredMoney());
        } else {
            player.sendMessage(ChatColor.YELLOW + plugin.getLanguage().getPhrase("Residence") + ":" + ChatColor.DARK_GREEN + " " + areaname);
        }
        if (plugin.getConfigManager().enabledRentSystem() && plugin.getRentManager().isRented(areaname)) {
            player.sendMessage(ChatColor.YELLOW + plugin.getLanguage().getPhrase("Owner") + ":" + ChatColor.RED + " " + perms.getOwner() + ChatColor.YELLOW + " Rented by: " + ChatColor.RED
                    + plugin.getRentManager().getRentingPlayer(areaname));
        } else {
            player.sendMessage(ChatColor.YELLOW + plugin.getLanguage().getPhrase("Owner") + ":" + ChatColor.RED + " " + perms.getOwner() + ChatColor.YELLOW + " - "
                    + plugin.getLanguage().getPhrase("World") + ": " + ChatColor.RED + perms.getWorld());
        }
        player.sendMessage(ChatColor.YELLOW + plugin.getLanguage().getPhrase("Flags") + ":" + ChatColor.BLUE + " " + perms.listFlags());
        player.sendMessage(ChatColor.YELLOW + plugin.getLanguage().getPhrase("Your.Flags") + ": " + ChatColor.GREEN + perms.listPlayerFlags(player.getName()));
        player.sendMessage(ChatColor.YELLOW + plugin.getLanguage().getPhrase("Group.Flags") + ":" + ChatColor.RED + " " + perms.listGroupFlags());
        player.sendMessage(ChatColor.YELLOW + plugin.getLanguage().getPhrase("Others.Flags") + ":" + ChatColor.RED + " " + perms.listOtherPlayersFlags(player.getName()));
        final String aid = res.getAreaIDbyLoc(player.getLocation());
        if (aid != null) {
            player.sendMessage(ChatColor.YELLOW + plugin.getLanguage().getPhrase("CurrentArea") + ": " + ChatColor.GOLD + aid);
        }
        player.sendMessage(ChatColor.YELLOW + plugin.getLanguage().getPhrase("Total.Size") + ":" + ChatColor.LIGHT_PURPLE + " " + res.getTotalSize());
        if (aid != null) {
            player.sendMessage(ChatColor.YELLOW + plugin.getLanguage().getPhrase("CoordsT") + ": " + ChatColor.LIGHT_PURPLE
                    + plugin.getLanguage().getPhrase("CoordsTop",
                            res.getAreaByLoc(player.getLocation()).getHighLoc().getBlockX() + "." + res.getAreaByLoc(player.getLocation()).getHighLoc().getBlockY() + "."
                                    + res.getAreaByLoc(player.getLocation()).getHighLoc().getBlockZ()));
            player.sendMessage(ChatColor.YELLOW + plugin.getLanguage().getPhrase("CoordsB") + ": " + ChatColor.LIGHT_PURPLE
                    + plugin.getLanguage().getPhrase("CoordsBottom",
                            res.getAreaByLoc(player.getLocation()).getLowLoc().getBlockX() + "." + res.getAreaByLoc(player.getLocation()).getLowLoc().getBlockY() + "."
                                    + res.getAreaByLoc(player.getLocation()).getLowLoc().getBlockZ()));
            if (plugin.isUseWorldEdit()) {
                WECUI.UPDATESELECT(res, player);
            }
        }
        if (plugin.getConfigManager().useLeases() && plugin.getLeaseManager().leaseExpires(areaname)) {
            player.sendMessage(ChatColor.YELLOW + plugin.getLanguage().getPhrase("LeaseExpire") + ":" + ChatColor.GREEN + " " + plugin.getLeaseManager().getExpireTime(areaname));
        }
    }

    public void removeAllByOwner(final Player player, final String owner) {
        this.removeAllByOwner(player, owner, residences);
    }

    public void removeAllByOwner(final String owner) {
        this.removeAllByOwner(null, owner, residences);
    }

    public void removeAllFromWorld(final CommandSender sender, final String world) {
        int count = 0;
        final Iterator<ClaimedResidence> it = residences.values().iterator();
        while (it.hasNext()) {
            final ClaimedResidence next = it.next();
            if (next.getWorld().equals(world)) {
                it.remove();
                count++;
            }
        }
        chunkResidences.remove(world);
        chunkResidences.put(world, new HashMap<ChunkRef, List<String>>());
        if (count == 0) {
            sender.sendMessage(ChatColor.RED + "No residences found in world: " + ChatColor.YELLOW + world);
        } else {
            sender.sendMessage(ChatColor.RED + "Removed " + ChatColor.YELLOW + count + ChatColor.RED + " residences in world: " + ChatColor.YELLOW + world);
        }
    }

    public void removeChunkList(final String name) {
        final ClaimedResidence res = residences.get(name);
        if (res != null) {
            final String world = res.getWorld();
            if (chunkResidences.get(world) != null) {
                for (final ChunkRef chunk : getChunks(res)) {
                    final List<String> ress = new ArrayList<String>();
                    if (chunkResidences.get(world).containsKey(chunk)) {
                        ress.addAll(chunkResidences.get(world).get(chunk));
                    }
                    ress.remove(name);
                    chunkResidences.get(world).put(chunk, ress);
                }
            }
        }
    }

    public void removeResidence(final Player player, final String name, final boolean resadmin) {
        final ClaimedResidence res = this.getByName(name);
        if (res != null) {
            if (player != null && !resadmin) {
                if (!res.getPermissions().hasResidencePermission(player, true) && !resadmin) {
                    player.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("NoPermission"));
                    return;
                }
            }
            final ResidenceDeleteEvent resevent = new ResidenceDeleteEvent(player, res, player == null ? DeleteCause.OTHER : DeleteCause.PLAYER_DELETE);
            pm.callEvent(resevent);
            if (resevent.isCancelled()) {
                return;
            }
            final ClaimedResidence parent = res.getParent();
            if (parent == null) {
                removeChunkList(name);
                residences.remove(name);
                if (player != null) {
                    player.sendMessage(ChatColor.GREEN + plugin.getLanguage().getPhrase("ResidenceRemove", ChatColor.YELLOW + name + ChatColor.GREEN));
                }
            } else {
                final String[] split = name.split("\\.");
                if (player != null) {
                    parent.removeSubzone(player, split[split.length - 1], true);
                } else {
                    parent.removeSubzone(split[split.length - 1]);
                }
            }
            // plugin.getLeaseManager().removeExpireTime(name); - causing
            // concurrent modification exception in lease manager... worked
            // around for now
            plugin.getRentManager().removeRentable(name);

        } else if (player != null) {
            player.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("InvalidResidence"));
        }
    }

    public void removeResidence(final String name) {
        this.removeResidence(null, name, true);
    }

    public boolean renameResidence(final Player player, final String oldName, final String newName, final boolean resadmin) {
        if (!plugin.validName(newName)) {
            player.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("InvalidNameCharacters"));
            return false;
        }
        final ClaimedResidence res = this.getByName(oldName);
        if (res == null) {
            if (player != null) {
                player.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("InvalidResidence"));
            }
            return false;
        }
        if (res.getPermissions().hasResidencePermission(player, true) || resadmin) {
            if (res.getParent() == null) {
                if (residences.containsKey(newName)) {
                    if (player != null) {
                        player.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("ResidenceAlreadyExists", ChatColor.YELLOW + newName + ChatColor.RED));
                    }
                    return false;
                }
                final ResidenceRenameEvent resevent = new ResidenceRenameEvent(res, newName, oldName);
                pm.callEvent(resevent);
                removeChunkList(oldName);
                residences.put(newName, res);
                residences.remove(oldName);
                calculateChunks(newName);
                if (plugin.getConfigManager().useLeases()) {
                    plugin.getLeaseManager().updateLeaseName(oldName, newName);
                }
                if (plugin.getConfigManager().enabledRentSystem()) {
                    plugin.getRentManager().updateRentableName(oldName, newName);
                }
                if (player != null) {
                    player.sendMessage(
                            ChatColor.GREEN + plugin.getLanguage().getPhrase("ResidenceRename", ChatColor.YELLOW + oldName + ChatColor.GREEN + "." + ChatColor.YELLOW + newName + ChatColor.GREEN));
                }
                return true;
            }
            final String[] oldname = oldName.split("\\.");
            final ClaimedResidence parent = res.getParent();
            return parent.renameSubzone(player, oldname[oldname.length - 1], newName, resadmin);
        }
        if (player != null) {
            player.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("NoPermission"));
        }
        return false;
    }

    public boolean renameResidence(final String oldName, final String newName) {
        return this.renameResidence(null, oldName, newName, true);
    }

    public Map<String, Object> save() {
        final Map<String, Object> worldmap = new LinkedHashMap<String, Object>();
        for (final World world : plugin.getServer().getWorlds()) {
            final Map<String, Object> resmap = new LinkedHashMap<String, Object>();
            for (final Entry<String, ClaimedResidence> res : residences.entrySet()) {
                if (res.getValue().getWorld().equals(world.getName())) {
                    try {
                        resmap.put(res.getKey(), res.getValue().save());
                    } catch (final Exception ex) {
                        System.out.println("[Residence] Failed to save residence (" + res.getKey() + ")!");
                        Logger.getLogger(ResidenceManager.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
            }
            worldmap.put(world.getName(), resmap);
        }
        return worldmap;
    }

    private void getResidenceList(final String targetplayer, final boolean showhidden, final boolean showsubzones, final String parentzone, final String resname, final ClaimedResidence res, final ArrayList<String> list, final boolean formattedOutput) {
        final boolean hidden = res.getPermissions().has("hidden", false);
        if ((showhidden) || (!showhidden && !hidden)) {
            if (targetplayer == null || res.getPermissions().getOwner().equalsIgnoreCase(targetplayer)) {
                if (formattedOutput) {
                    list.add(ChatColor.GREEN + parentzone + resname + ChatColor.YELLOW + " - " + plugin.getLanguage().getPhrase("World") + ": " + res.getWorld());
                } else {
                    list.add(parentzone + resname);
                }
            }
            if (showsubzones) {
                for (final Entry<String, ClaimedResidence> sz : res.subzones.entrySet()) {
                    this.getResidenceList(targetplayer, showhidden, showsubzones, parentzone + resname + ".", sz.getKey(), sz.getValue(), list, formattedOutput);
                }
            }
        }
    }

    private void removeAllByOwner(final Player player, final String owner, final Map<String, ClaimedResidence> resholder) {
        final Iterator<ClaimedResidence> it = resholder.values().iterator();
        while (it.hasNext()) {
            final ClaimedResidence res = it.next();
            if (res.getOwner().equalsIgnoreCase(owner)) {
                final ResidenceDeleteEvent resevent = new ResidenceDeleteEvent(player, res, player == null ? DeleteCause.OTHER : DeleteCause.PLAYER_DELETE);
                pm.callEvent(resevent);
                if (resevent.isCancelled()) {
                    return;
                }
                removeChunkList(res.getName());
                it.remove();
            } else {
                this.removeAllByOwner(player, owner, res.subzones);
            }
        }
    }
}
