/*
 * 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.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Player;

import com.bekvon.bukkit.residence.event.ResidenceTPEvent;
import com.bekvon.bukkit.residence.permissions.PermissionGroup;

import cn.citycraft.PluginHelper.utils.CompatibleUtil;
import pw.yumc.Residence.ResidenceMain;
import pw.yumc.Residence.economy.ResidenceBank;
import pw.yumc.Residence.economy.TransactionManager;
import pw.yumc.Residence.itemlist.ResidenceItemList;
import pw.yumc.Residence.itemlist.ItemList.ListType;
import pw.yumc.Residence.text.help.InformationPager;

/**
 *
 * @author Administrator
 *
 */
public class ClaimedResidence {

    protected Map<String, CuboidArea> areas;
    protected ResidenceBank bank;
    protected ResidenceItemList blacklist;
    protected String enterMessage;
    protected ResidenceItemList ignorelist;
    protected String leaveMessage;
    protected ClaimedResidence parent;
    protected ResidencePermissions perms;
    protected ResidenceMain plugin;
    protected Map<String, ClaimedResidence> subzones;
    protected Location tpLoc;

    public ClaimedResidence(final ResidenceMain plugin, final String creationWorld) {
        this(plugin, "Server Land", creationWorld);
    }

    public ClaimedResidence(final ResidenceMain plugin, final String creator, final String creationWorld) {
        this(plugin);
        perms = new ResidencePermissions(plugin, this, creator, creationWorld);
    }

    public ClaimedResidence(final ResidenceMain plugin, final String creator, final String creationWorld, final ClaimedResidence parentResidence) {
        this(plugin, creator, creationWorld);
        parent = parentResidence;
    }

    private ClaimedResidence(final ResidenceMain plugin) {
        this.plugin = plugin;
        subzones = new HashMap<>();
        areas = new HashMap<>();
        bank = new ResidenceBank(plugin, this);
        blacklist = new ResidenceItemList(plugin, this, ListType.BLACKLIST);
        ignorelist = new ResidenceItemList(plugin, this, ListType.IGNORELIST);
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
    public static ClaimedResidence load(final ResidenceMain plugin, final Map root, final ClaimedResidence parent) throws Exception {
        final ClaimedResidence res = new ClaimedResidence(plugin);
        if (root == null) {
            throw new Exception("Null residence!");
        }
        res.enterMessage = (String) root.get("EnterMessage");
        res.leaveMessage = (String) root.get("LeaveMessage");
        if (root.containsKey("StoredMoney")) {
            res.bank.setStoredMoney((Integer) root.get("StoredMoney"));
        }
        if (root.containsKey("BlackList")) {
            res.blacklist = ResidenceItemList.load(plugin, res, (Map<String, Object>) root.get("BlackList"));
        }
        if (root.containsKey("IgnoreList")) {
            res.ignorelist = ResidenceItemList.load(plugin, res, (Map<String, Object>) root.get("IgnoreList"));
        }
        final Map<String, Object> areamap = (Map<String, Object>) root.get("Areas");
        res.perms = ResidencePermissions.load(plugin, res, (Map<String, Object>) root.get("Permissions"));
        final World world = Bukkit.getServer().getWorld(res.perms.getWorld());
        if (world == null) {
            throw new Exception("Cant Find World: " + res.perms.getWorld());
        }
        for (final Entry<String, Object> map : areamap.entrySet()) {
            res.areas.put(map.getKey(), CuboidArea.load((Map<String, Object>) map.getValue(), world));
        }
        final Map<String, Object> subzonemap = (Map<String, Object>) root.get("Subzones");
        for (final Entry<String, Object> map : subzonemap.entrySet()) {
            final ClaimedResidence subres = ClaimedResidence.load(plugin, (Map<String, Object>) map.getValue(), res);
            if (plugin.getConfigManager().flagsInherit()) {
                subres.getPermissions().setParent(res.getPermissions());
            }
            res.subzones.put(map.getKey(), subres);
        }
        res.parent = parent;
        final Map tploc = (Map) root.get("TPLoc");
        if (tploc != null) {
            res.tpLoc = new Location(world, (Integer) tploc.get("X"), (Integer) tploc.get("Y"), (Integer) tploc.get("Z"));
        }
        return res;
    }

    public boolean addArea(final CuboidArea area, final String name) {
        return addArea(null, area, name, true);
    }

    public boolean addArea(final Player player, final CuboidArea area, final String name, final boolean resadmin) {
        if (!plugin.validName(name)) {
            if (player != null) {
                player.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("InvalidNameCharacters"));
            }
            return false;
        }
        if (areas.containsKey(name)) {
            if (player != null) {
                player.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("AreaExists"));
            }
            return false;
        }
        if (!resadmin && plugin.getConfigManager().getEnforceAreaInsideArea() && getParent() == null) {
            boolean inside = false;
            for (final CuboidArea are : areas.values()) {
                if (are.isAreaWithinArea(area)) {
                    inside = true;
                }
            }
            if (!inside) {
                return false;
            }
        }
        if (!area.getWorld().getName().equalsIgnoreCase(perms.getWorld())) {
            if (player != null) {
                player.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("AreaDiffWorld"));
            }
            return false;
        }
        if (parent == null) {
            final String collideResidence = plugin.getResidenceManager().checkAreaCollision(area, this);
            if (collideResidence != null) {
                if (player != null) {
                    player.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("AreaCollision", ChatColor.YELLOW + collideResidence));
                }
                return false;
            }
        } else {
            final String[] szs = parent.listSubzones();
            for (final String sz : szs) {
                final ClaimedResidence res = parent.getSubzone(sz);
                if (res != null && res != this) {
                    if (res.checkCollision(area)) {
                        if (player != null) {
                            player.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("AreaSubzoneCollision", ChatColor.YELLOW + sz));
                        }
                        return false;
                    }
                }
            }
        }
        if (!resadmin && player != null) {
            if (!this.perms.hasResidencePermission(player, true)) {
                player.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("NoPermission"));
                return false;
            }
            if (parent != null) {
                if (!parent.containsLoc(area.getHighLoc()) || !parent.containsLoc(area.getLowLoc())) {
                    player.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("AreaNotWithinParent"));
                    return false;
                }
                if (!parent.getPermissions().hasResidencePermission(player, true) && !parent.getPermissions().playerHas(player.getName(), "subzone", true)) {
                    player.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("ParentNoPermission"));
                    return false;
                }
            }
            final PermissionGroup group = plugin.getPermissionManager().getGroup(player);
            if (!group.canCreateResidences() && !player.hasPermission("residence.create")) {
                player.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("NoPermission"));
                return false;
            }
            if (areas.size() >= group.getMaxPhysicalPerResidence()) {
                player.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("AreaMaxPhysical"));
                return false;
            }
            if (!group.inLimits(area)) {
                player.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("AreaSizeLimit"));
                return false;
            }
            if (group.getMinHeight() > area.getLowLoc().getBlockY()) {
                player.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("AreaLowLimit", ChatColor.YELLOW + String.format("%d", group.getMinHeight())));
                return false;
            }
            if (group.getMaxHeight() < area.getHighLoc().getBlockY()) {
                player.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("AreaHighLimit", ChatColor.YELLOW + String.format("%d", group.getMaxHeight())));
                return false;
            }
            if (parent == null && plugin.getConfigManager().enableEconomy()) {
                final int chargeamount = (int) Math.ceil(area.getSize() * group.getCostPerBlock());
                if (!TransactionManager.chargeEconomyMoney(plugin, player, chargeamount)) {
                    return false;
                }
            }
        }
        plugin.getResidenceManager().removeChunkList(getName());
        areas.put(name, area);
        plugin.getResidenceManager().calculateChunks(getName());
        if (player != null) {
            player.sendMessage(ChatColor.GREEN + plugin.getLanguage().getPhrase("AreaCreate", ChatColor.YELLOW + name));
        }
        return true;
    }

    public boolean addSubzone(final Player player, final Location loc1, final Location loc2, final String name, final boolean resadmin) {
        if (player == null) {
            return this.addSubzone(null, "Server Land", loc1, loc2, name, resadmin);
        }
        return this.addSubzone(player, player.getName(), loc1, loc2, name, resadmin);
    }

    public boolean addSubzone(final Player player, final String owner, final Location loc1, final Location loc2, final String name, final boolean resadmin) {
        if (!plugin.validName(name)) {
            if (player != null) {
                player.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("InvalidNameCharacters"));
            }
            return false;
        }
        if (!(containsLoc(loc1) && containsLoc(loc2))) {
            if (player != null) {
                player.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("SubzoneSelectInside"));
            }
            return false;
        }
        if (subzones.containsKey(name)) {
            if (player != null) {
                player.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("SubzoneExists", ChatColor.YELLOW + name));
            }
            return false;
        }
        if (!resadmin && player != null) {
            if (!this.perms.hasResidencePermission(player, true)) {
                if (!this.perms.playerHas(player.getName(), "subzone", this.perms.playerHas(player.getName(), "admin", false))) {
                    player.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("NoPermission"));
                    return false;
                }
            }
            final PermissionGroup group = plugin.getPermissionManager().getGroup(player);
            if (getZoneDepth() >= group.getMaxSubzoneDepth()) {
                player.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("SubzoneMaxDepth"));
                return false;
            }
        }
        final CuboidArea newArea = new CuboidArea(loc1, loc2);
        final Set<Entry<String, ClaimedResidence>> set = subzones.entrySet();
        for (final Entry<String, ClaimedResidence> resEntry : set) {
            final ClaimedResidence res = resEntry.getValue();
            if (res.checkCollision(newArea)) {
                if (player != null) {
                    player.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("SubzoneCollide", ChatColor.YELLOW + resEntry.getKey()));
                }
                return false;
            }
        }
        ClaimedResidence newres;
        if (player != null) {
            newres = new ClaimedResidence(plugin, owner, perms.getWorld(), this);
            newres.addArea(player, newArea, name, resadmin);
        } else {
            newres = new ClaimedResidence(plugin, owner, perms.getWorld(), this);
            newres.addArea(newArea, name);
        }
        if (newres.getAreaCount() != 0) {
            newres.getPermissions().applyDefaultFlags();
            if (player != null) {
                final PermissionGroup group = plugin.getPermissionManager().getGroup(player);
                newres.setEnterMessage(group.getDefaultEnterMessage());
                newres.setLeaveMessage(group.getDefaultLeaveMessage());
            }
            if (plugin.getConfigManager().flagsInherit()) {
                newres.getPermissions().setParent(perms);
            }
            subzones.put(name, newres);
            if (player != null) {
                player.sendMessage(ChatColor.GREEN + plugin.getLanguage().getPhrase("SubzoneCreate", ChatColor.YELLOW + name));
            }
            return true;
        }
        if (player != null) {
            player.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("SubzoneCreateFail", ChatColor.YELLOW + name));
        }
        return false;
    }

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

    public boolean checkCollision(final CuboidArea area) {
        final Set<String> set = areas.keySet();
        for (final String key : set) {
            final CuboidArea checkarea = areas.get(key);
            if (checkarea != null) {
                if (checkarea.checkCollision(area)) {
                    return true;
                }
            }
        }
        return false;
    }

    public boolean containsLoc(final Location loc) {
        final Collection<CuboidArea> keys = areas.values();
        for (final CuboidArea key : keys) {
            if (key.containsLoc(loc)) {
                if (parent != null) {
                    return parent.containsLoc(loc);
                }
                return true;
            }
        }
        return false;
    }

    public CuboidArea getArea(final String name) {
        return areas.get(name);
    }

    public CuboidArea[] getAreaArray() {
        final CuboidArea[] temp = new CuboidArea[areas.size()];
        int i = 0;
        for (final CuboidArea area : areas.values()) {
            temp[i] = area;
            i++;
        }
        return temp;
    }

    public CuboidArea getAreaByLoc(final Location loc) {
        for (final CuboidArea thisarea : areas.values()) {
            if (thisarea.containsLoc(loc)) {
                return thisarea;
            }
        }
        return null;
    }

    public int getAreaCount() {
        return areas.size();
    }

    public String getAreaIDbyLoc(final Location loc) {
        for (final Entry<String, CuboidArea> area : areas.entrySet()) {
            if (area.getValue().containsLoc(loc)) {
                return area.getKey();
            }
        }
        return null;
    }

    public String[] getAreaList() {
        final String arealist[] = new String[areas.size()];
        int i = 0;
        for (final Entry<String, CuboidArea> entry : areas.entrySet()) {
            arealist[i] = entry.getKey();
            i++;
        }
        return arealist;
    }

    public ResidenceBank getBank() {
        return bank;
    }

    public String getEnterMessage() {
        return enterMessage;
    }

    public ResidenceItemList getItemBlacklist() {
        return blacklist;
    }

    public ResidenceItemList getItemIgnoreList() {
        return ignorelist;
    }

    public String getLeaveMessage() {
        return leaveMessage;
    }

    public String getName() {
        return plugin.getResidenceManager().getNameByRes(this);
    }

    @SuppressWarnings("deprecation")
    public Location getOutsideFreeLoc(final Location insideLoc) {
        final int maxIt = 100;
        final CuboidArea area = getAreaByLoc(insideLoc);
        if (area == null) {
            return insideLoc;
        }
        final Location highLoc = area.getHighLoc();
        final Location newLoc = new Location(highLoc.getWorld(), highLoc.getBlockX(), highLoc.getBlockY(), highLoc.getBlockZ());
        boolean found = false;
        int it = 0;
        while (!found && it < maxIt) {
            it++;
            Location lowLoc;
            newLoc.setX(newLoc.getBlockX() + 1);
            newLoc.setZ(newLoc.getBlockZ() + 1);
            lowLoc = new Location(newLoc.getWorld(), newLoc.getBlockX(), 254, newLoc.getBlockZ());
            newLoc.setY(255);
            while ((newLoc.getBlock().getTypeId() != 0 || lowLoc.getBlock().getTypeId() == 0) && lowLoc.getBlockY() > -126) {
                newLoc.setY(newLoc.getY() - 1);
                lowLoc.setY(lowLoc.getY() - 1);
            }
            if (newLoc.getBlock().getTypeId() == 0 && lowLoc.getBlock().getTypeId() != 0) {
                found = true;
            }
        }
        if (found) {
            return newLoc;
        }
        final World world = Bukkit.getServer().getWorld(perms.getWorld());
        if (world != null) {
            return world.getSpawnLocation();
        }
        return insideLoc;
    }

    public String getOwner() {
        return perms.getOwner();
    }

    public ClaimedResidence getParent() {
        return parent;
    }

    public ResidencePermissions getPermissions() {
        return perms;
    }

    public ArrayList<Player> getPlayersInResidence() {
        final ArrayList<Player> within = new ArrayList<>();
        for (final Player player : CompatibleUtil.getOnlinePlayers()) {
            if (containsLoc(player.getLocation())) {
                within.add(player);
            }
        }
        return within;
    }

    public ClaimedResidence getSubzone(final String subzonename) {
        if (!subzonename.contains(".")) {
            return subzones.get(subzonename);
        }
        final String split[] = subzonename.split("\\.");
        ClaimedResidence get = subzones.get(split[0]);
        for (int i = 1; i < split.length; i++) {
            if (get == null) {
                return null;
            }
            get = get.getSubzone(split[i]);
        }
        return get;
    }

    public ClaimedResidence getSubzoneByLoc(final Location loc) {
        final Set<Entry<String, ClaimedResidence>> set = subzones.entrySet();
        boolean found = false;
        ClaimedResidence res = null;
        for (final Entry<String, ClaimedResidence> entry : set) {
            res = entry.getValue();
            if (res.containsLoc(loc)) {
                found = true;
                break;
            }
        }
        if (!found) {
            return null;
        }
        if (res == null) {
            return null;
        }
        final ClaimedResidence subrez = res.getSubzoneByLoc(loc);
        if (subrez == null) {
            return res;
        }
        return subrez;
    }

    public String[] getSubzoneList() {
        final ArrayList<String> zones = new ArrayList<>();
        final Set<String> set = subzones.keySet();
        for (final String key : set) {
            if (key != null) {
                zones.add(key);
            }
        }
        return zones.toArray(new String[zones.size()]);
    }

    public String getSubzoneNameByLoc(final Location loc) {
        final Set<Entry<String, ClaimedResidence>> set = subzones.entrySet();
        ClaimedResidence res = null;
        String key = null;
        for (final Entry<String, ClaimedResidence> entry : set) {
            res = entry.getValue();
            if (res.containsLoc(loc)) {
                key = entry.getKey();
                break;
            }
        }
        if (key == null) {
            return null;
        }
        if (res == null) {
            return null;
        }
        final String subname = res.getSubzoneNameByLoc(loc);
        if (subname != null) {
            return key + "." + subname;
        }
        return key;
    }

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

    public ClaimedResidence getTopParent() {
        if (parent == null) {
            return this;
        }
        return parent.getTopParent();
    }

    public long getTotalSize() {
        final Collection<CuboidArea> set = areas.values();
        long size = 0;
        for (final CuboidArea entry : set) {
            size = size + entry.getSize();
        }
        return size;
    }

    public String getWorld() {
        return perms.getWorld();
    }

    public int getZoneDepth() {
        int count = 0;
        ClaimedResidence res = parent;
        while (res != null) {
            count++;
            res = res.getParent();
        }
        return count;
    }

    public String[] listSubzones() {
        final String list[] = new String[subzones.size()];
        int i = 0;
        for (final String res : subzones.keySet()) {
            list[i] = res;
            i++;
        }
        return list;
    }

    public void printAdvancedAreaList(final Player player, final int page) {
        final ArrayList<String> temp = new ArrayList<>();
        for (final Entry<String, CuboidArea> entry : areas.entrySet()) {
            final CuboidArea a = entry.getValue();
            final Location h = a.getHighLoc();
            final Location l = a.getLowLoc();
            temp.add(ChatColor.GREEN + "{" + ChatColor.YELLOW + "ID:" + ChatColor.RED + entry.getKey() + " " + ChatColor.YELLOW + "P1:" + ChatColor.RED + "(" + h.getBlockX() + "," + h.getBlockY()
                    + "," + h.getBlockZ() + ") " + ChatColor.YELLOW + "P2:" + ChatColor.RED + "(" + l.getBlockX() + "," + l.getBlockY() + "," + l.getBlockZ() + ") " + ChatColor.YELLOW + "(Size:"
                    + ChatColor.RED + a.getSize() + ChatColor.YELLOW + ")" + ChatColor.GREEN + "} ");
        }
        InformationPager.printInfo(plugin, player, plugin.getLanguage().getPhrase("PhysicalAreas"), temp, page);
    }

    public void printAreaList(final Player player, final int page) {
        final ArrayList<String> temp = new ArrayList<>();
        for (final String area : areas.keySet()) {
            temp.add(area);
        }
        InformationPager.printInfo(plugin, player, plugin.getLanguage().getPhrase("PhysicalAreas"), temp, page);
    }

    public void printSubzoneList(final Player player, final int page) {
        final ArrayList<String> temp = new ArrayList<>();
        for (final Entry<String, ClaimedResidence> sz : subzones.entrySet()) {
            temp.add(ChatColor.GREEN + sz.getKey() + ChatColor.YELLOW + " - " + plugin.getLanguage().getPhrase("Owner") + ": " + sz.getValue().getOwner());
        }
        InformationPager.printInfo(plugin, player, plugin.getLanguage().getPhrase("Subzones"), temp, page);
    }

    public void remove() {
        final String name = getName();
        if (name != null) {
            plugin.getResidenceManager().removeResidence(name);
            plugin.getResidenceManager().removeChunkList(name);
        }
    }

    public void removeArea(final Player player, final String id, final boolean resadmin) {

        if (getPermissions().hasResidencePermission(player, true) || resadmin) {
            if (!areas.containsKey(id)) {
                player.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("AreaNonExist"));
                return;
            }
            if (areas.size() == 1 && !plugin.getConfigManager().allowEmptyResidences()) {
                player.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("AreaRemoveLast"));
                return;
            }
            removeArea(id);
            player.sendMessage(ChatColor.GREEN + plugin.getLanguage().getPhrase("AreaRemove"));
        } else {
            player.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("NoPermission"));
        }
    }

    public void removeArea(final String id) {
        plugin.getResidenceManager().removeChunkList(getName());
        areas.remove(id);
        plugin.getResidenceManager().calculateChunks(getName());
    }

    public boolean removeSubzone(final Player player, final String name, final boolean resadmin) {
        final ClaimedResidence res = subzones.get(name);
        if (player != null && !res.perms.hasResidencePermission(player, true) && !resadmin) {
            player.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("NoPermission"));
            return false;
        }
        subzones.remove(name);
        if (player != null) {
            player.sendMessage(ChatColor.GREEN + plugin.getLanguage().getPhrase("SubzoneRemove", ChatColor.YELLOW + name + ChatColor.GREEN));
        }
        return true;
    }

    public boolean removeSubzone(final String name) {
        return this.removeSubzone(null, name, true);
    }

    public boolean renameArea(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;
        }
        if (player == null || perms.hasResidencePermission(player, true) || resadmin) {
            if (areas.containsKey(newName)) {
                if (player != null) {
                    player.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("AreaExists"));
                }
                return false;
            }
            final CuboidArea area = areas.get(oldName);
            if (area == null) {
                if (player != null) {
                    player.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("AreaInvalidName"));
                }
                return false;
            }
            areas.put(newName, area);
            areas.remove(oldName);
            if (player != null) {
                player.sendMessage(ChatColor.GREEN + plugin.getLanguage().getPhrase("AreaRename", oldName + "." + newName));
            }
            return true;
        }
        player.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("NoPermission"));
        return false;
    }

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

    public boolean renameSubzone(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 = subzones.get(oldName);
        if (res == null) {
            if (player != null) {
                player.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("InvalidSubzone"));
            }
            return false;
        }
        if (player != null && !res.getPermissions().hasResidencePermission(player, true) && !resadmin) {
            player.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("NoPermission"));
            return false;
        }
        if (subzones.containsKey(newName)) {
            if (player != null) {
                player.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("SubzoneExists", ChatColor.YELLOW + newName));
            }
            return false;
        }
        subzones.put(newName, res);
        subzones.remove(oldName);
        if (player != null) {
            player.sendMessage(ChatColor.GREEN + plugin.getLanguage().getPhrase("SubzoneRename", oldName + "." + newName));
        }
        return true;
    }

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

    public boolean replaceArea(final CuboidArea neware, final String name) {
        return this.replaceArea(null, neware, name, true);
    }

    public boolean replaceArea(final Player player, final CuboidArea newarea, final String name, final boolean resadmin) {
        if (!areas.containsKey(name)) {
            if (player != null) {
                player.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("AreaNonExist"));
            }
            return false;
        }
        final CuboidArea oldarea = areas.get(name);
        if (!newarea.getWorld().getName().equalsIgnoreCase(perms.getWorld())) {
            if (player != null) {
                player.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("AreaDiffWorld"));
            }
            return false;
        }
        if (parent == null) {
            final String collideResidence = plugin.getResidenceManager().checkAreaCollision(newarea, this);
            if (collideResidence != null) {
                if (player != null) {
                    player.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("AreaCollision", ChatColor.YELLOW + collideResidence));
                }
                return false;
            }
        } else {
            final String[] szs = parent.listSubzones();
            for (final String sz : szs) {
                final ClaimedResidence res = parent.getSubzone(sz);
                if (res != null && res != this) {
                    if (res.checkCollision(newarea)) {
                        if (player != null) {
                            player.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("AreaSubzoneCollision", ChatColor.YELLOW + sz));
                        }
                        return false;
                    }
                }
            }
        }
        // Remove subzones that are not in the area anymore
        final String[] szs = listSubzones();
        for (final String sz : szs) {
            final ClaimedResidence res = getSubzone(sz);
            if (res != null && res != this) {
                final String[] areas = res.getAreaList();
                for (final String area : areas) {
                    if (!newarea.isAreaWithinArea(res.getArea(area))) {
                        boolean good = false;
                        for (final CuboidArea arae : getAreaArray()) {
                            if (arae != oldarea && arae.isAreaWithinArea(res.getArea(area))) {
                                good = true;
                            }
                        }
                        if (!good) {
                            res.removeArea(area);
                        }
                    }
                }
                if (res.getAreaArray().length == 0) {
                    removeSubzone(sz);
                }
            }
        }
        if (!resadmin && player != null) {
            if (!this.perms.hasResidencePermission(player, true)) {
                player.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("NoPermission"));
                return false;
            }
            if (parent != null) {
                if (!parent.containsLoc(newarea.getHighLoc()) || !parent.containsLoc(newarea.getLowLoc())) {
                    player.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("AreaNotWithinParent"));
                    return false;
                }
                if (!parent.getPermissions().hasResidencePermission(player, true) && !parent.getPermissions().playerHas(player.getName(), "subzone", true)) {
                    player.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("ParentNoPermission"));
                    return false;
                }
            }
            final PermissionGroup group = plugin.getPermissionManager().getGroup(player);
            if (!group.canCreateResidences() && !player.hasPermission("residence.create")) {
                player.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("NoPermission"));
                return false;
            }
            if (!group.inLimits(newarea)) {
                player.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("AreaSizeLimit"));
                return false;
            }
            if (group.getMinHeight() > newarea.getLowLoc().getBlockY()) {
                player.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("AreaLowLimit", ChatColor.YELLOW + String.format("%d", group.getMinHeight())));
                return false;
            }
            if (group.getMaxHeight() < newarea.getHighLoc().getBlockY()) {
                player.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("AreaHighLimit", ChatColor.YELLOW + String.format("%d", group.getMaxHeight())));
                return false;
            }
            if (parent == null && plugin.getConfigManager().enableEconomy()) {
                final int chargeamount = (int) Math.ceil((newarea.getSize() - oldarea.getSize()) * group.getCostPerBlock());
                if (chargeamount > 0) {
                    if (!TransactionManager.chargeEconomyMoney(plugin, player, chargeamount)) {
                        return false;
                    }
                }
            }

        }
        plugin.getResidenceManager().removeChunkList(getName());
        areas.remove(name);
        areas.put(name, newarea);
        plugin.getResidenceManager().calculateChunks(getName());
        player.sendMessage(ChatColor.GREEN + plugin.getLanguage().getPhrase("AreaUpdate"));
        return true;
    }

    public Map<String, Object> save() {
        final Map<String, Object> root = new HashMap<>();
        final Map<String, Object> areamap = new HashMap<>();
        root.put("EnterMessage", enterMessage);
        root.put("LeaveMessage", leaveMessage);
        root.put("StoredMoney", bank.getStoredMoney());
        root.put("BlackList", blacklist.save());
        root.put("IgnoreList", ignorelist.save());
        for (final Entry<String, CuboidArea> entry : areas.entrySet()) {
            areamap.put(entry.getKey(), entry.getValue().save());
        }
        root.put("Areas", areamap);
        final Map<String, Object> subzonemap = new HashMap<>();
        for (final Entry<String, ClaimedResidence> sz : subzones.entrySet()) {
            subzonemap.put(sz.getKey(), sz.getValue().save());
        }
        root.put("Subzones", subzonemap);
        root.put("Permissions", perms.save());
        if (tpLoc != null) {
            final Map<String, Object> tpmap = new HashMap<>();
            tpmap.put("X", tpLoc.getBlockX());
            tpmap.put("Y", tpLoc.getBlockY());
            tpmap.put("Z", tpLoc.getBlockZ());
            root.put("TPLoc", tpmap);
        }
        return root;
    }

    public void setEnterLeaveMessage(final Player player, String message, final boolean enter, final boolean resadmin) {
        if (message != null) {
            if (message.equals("")) {
                message = null;
            }
        }
        final PermissionGroup group = plugin.getPermissionManager().getGroup(perms.getOwner(), perms.getWorld());
        if (!group.canSetEnterLeaveMessages() && !resadmin) {
            player.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("OwnerNoPermission"));
            return;
        }
        if (!perms.hasResidencePermission(player, false) && !resadmin) {
            player.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("NoPermission"));
            return;
        }
        if (enter) {
            setEnterMessage(message);
        } else {
            setLeaveMessage(message);
        }
        player.sendMessage(ChatColor.GREEN + plugin.getLanguage().getPhrase("MessageChange"));
    }

    public void setEnterMessage(final String message) {
        enterMessage = message;
    }

    public void setLeaveMessage(final String message) {
        leaveMessage = message;
    }

    public void setTpLoc(final Player player, final boolean resadmin) {
        if (!this.perms.hasResidencePermission(player, false) && !resadmin) {
            player.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("NoPermission"));
            return;
        }
        if (!containsLoc(player.getLocation())) {
            player.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("NotInResidence"));
            return;
        }
        tpLoc = player.getLocation();
        player.sendMessage(ChatColor.GREEN + plugin.getLanguage().getPhrase("SetTeleportLocation"));
    }

    public void tpToResidence(final Player reqPlayer, final Player targetPlayer, final boolean resadmin) {
        if (!resadmin && !(reqPlayer.hasPermission("residence.admin.tp") && reqPlayer.hasPermission("residence.admin.move"))) {
            final PermissionGroup group = plugin.getPermissionManager().getGroup(reqPlayer);
            if (!group.hasTpAccess()) {
                reqPlayer.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("TeleportDeny"));
                return;
            }
            if (!reqPlayer.equals(targetPlayer)) {
                reqPlayer.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("NoPermission"));
                return;
            }
            if (!this.perms.playerHas(reqPlayer.getName(), "tp", true)) {
                reqPlayer.sendMessage(ChatColor.RED + plugin.getLanguage().getPhrase("TeleportNoFlag"));
                return;
            }
        }
        if (tpLoc != null) {
            final ResidenceTPEvent tpevent = new ResidenceTPEvent(this, tpLoc, targetPlayer, reqPlayer);
            Bukkit.getServer().getPluginManager().callEvent(tpevent);
            if (!tpevent.isCancelled()) {
                targetPlayer.teleport(tpLoc);
                targetPlayer.sendMessage(ChatColor.GREEN + plugin.getLanguage().getPhrase("TeleportSuccess"));
            }
        } else {
            final CuboidArea area = areas.values().iterator().next();
            if (area == null) {
                reqPlayer.sendMessage(ChatColor.RED + "未找到可传送的安全区域...");
                return;
            }
            final Location targloc = getOutsideFreeLoc(area.getHighLoc());
            final ResidenceTPEvent tpevent = new ResidenceTPEvent(this, targloc, targetPlayer, reqPlayer);
            Bukkit.getServer().getPluginManager().callEvent(tpevent);
            if (!tpevent.isCancelled()) {
                targetPlayer.teleport(targloc);
                targetPlayer.sendMessage(ChatColor.YELLOW + plugin.getLanguage().getPhrase("TeleportNear"));
            }
        }
    }
}
