/*
 * 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.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.Player;

import pw.yumc.Residence.ResidenceMain;

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

    protected static ResidenceMain plugin;

    protected static ArrayList<String> validAreaFlags = new ArrayList<String>();
    protected static HashMap<String, ArrayList<String>> validFlagGroups = new HashMap<String, ArrayList<String>>();
    protected static ArrayList<String> validFlags = new ArrayList<String>();
    protected static ArrayList<String> validPlayerFlags = new ArrayList<String>();
    final static Map<Material, String> matUseFlagList = new EnumMap<Material, String>(Material.class);
    protected Map<String, Boolean> cuboidFlags;
    protected Map<String, Map<String, Boolean>> groupFlags;
    protected FlagPermissions parent;
    protected Map<String, Map<String, Boolean>> playerFlags;

    public static void addFlag(String flag) {
        flag = flag.toLowerCase();
        if (!validFlags.contains(flag)) {
            validFlags.add(flag);
        }
        if (validFlagGroups.containsKey(flag)) {
            validFlagGroups.remove(flag);
        }
    }

    public static void addFlagToFlagGroup(final String group, final String flag) {
        if (!FlagPermissions.validFlags.contains(group) && !FlagPermissions.validAreaFlags.contains(group) && !FlagPermissions.validPlayerFlags.contains(group)) {
            if (!validFlagGroups.containsKey(group)) {
                validFlagGroups.put(group, new ArrayList<String>());
            }
            final ArrayList<String> flags = validFlagGroups.get(group);
            flags.add(flag);
        }
    }

    public static void addMaterialToUseFlag(final Material mat, final String flag) {
        matUseFlagList.put(mat, flag);
    }

    public static void addPlayerOrGroupOnlyFlag(String flag) {
        flag = flag.toLowerCase();
        if (!validPlayerFlags.contains(flag)) {
            validPlayerFlags.add(flag);
        }
        if (validFlagGroups.containsKey(flag)) {
            validFlagGroups.remove(flag);
        }
    }

    public static void addResidenceOnlyFlag(String flag) {
        flag = flag.toLowerCase();
        if (!validAreaFlags.contains(flag)) {
            validAreaFlags.add(flag);
        }
        if (validFlagGroups.containsKey(flag)) {
            validFlagGroups.remove(flag);
        }
    }

    public static boolean flagGroupExists(final String group) {
        return validFlagGroups.containsKey(group);
    }

    public static EnumMap<Material, String> getMaterialUseFlagList() {
        return (EnumMap<Material, String>) matUseFlagList;
    }

    public static void init(final ResidenceMain resmain) {
        plugin = resmain;
    }

    public static void initValidFlags() {
        validAreaFlags.clear();
        validPlayerFlags.clear();
        validFlags.clear();
        validFlagGroups.clear();
        addFlag("egg");
        addFlag("note");
        addFlag("pressure");
        addFlag("cake");
        addFlag("lever");
        addFlag("door");
        addFlag("button");
        addFlag("table");
        addFlag("brew");
        addFlag("bed");
        addFlag("commandblock");
        addFlag("anvil");
        addFlag("flowerpot");
        addFlag("enchant");
        addFlag("diode");
        addFlag("use");
        addFlag("move");
        addFlag("build");
        addFlag("tp");
        addFlag("ignite");
        addFlag("container");
        addFlag("subzone");
        addFlag("destroy");
        addFlag("place");
        addFlag("bucket");
        addFlag("bank");
        addFlag("beacon");

        /* New flags */
        addFlag("trade");

        addResidenceOnlyFlag("trample");
        addResidenceOnlyFlag("pvp");
        addResidenceOnlyFlag("fireball");
        addResidenceOnlyFlag("explode");
        addResidenceOnlyFlag("damage");
        addResidenceOnlyFlag("monsters");
        addResidenceOnlyFlag("firespread");
        addResidenceOnlyFlag("burn");
        addResidenceOnlyFlag("tnt");
        addResidenceOnlyFlag("creeper");
        addResidenceOnlyFlag("wither");
        addResidenceOnlyFlag("flow");
        addResidenceOnlyFlag("healing");
        addResidenceOnlyFlag("animals");
        addResidenceOnlyFlag("lavaflow");
        addResidenceOnlyFlag("waterflow");
        addResidenceOnlyFlag("physics");
        addResidenceOnlyFlag("piston");
        addResidenceOnlyFlag("spread");
        addResidenceOnlyFlag("hidden");
        addResidenceOnlyFlag("witherdamage");

        addPlayerOrGroupOnlyFlag("admin");

        addFlagToFlagGroup("redstone", "note");
        addFlagToFlagGroup("redstone", "pressure");
        addFlagToFlagGroup("redstone", "lever");
        addFlagToFlagGroup("redstone", "button");
        addFlagToFlagGroup("redstone", "diode");
        addFlagToFlagGroup("craft", "brew");
        addFlagToFlagGroup("craft", "table");
        addFlagToFlagGroup("craft", "enchant");
        addFlagToFlagGroup("trusted", "use");
        addFlagToFlagGroup("trusted", "tp");
        addFlagToFlagGroup("trusted", "build");
        addFlagToFlagGroup("trusted", "container");
        addFlagToFlagGroup("trusted", "bucket");
        addFlagToFlagGroup("trusted", "move");
        addFlagToFlagGroup("fire", "ignite");
        addFlagToFlagGroup("fire", "firespread");

        addMaterialToUseFlag(Material.DIODE, "diode");
        addMaterialToUseFlag(Material.DIODE_BLOCK_OFF, "diode");
        addMaterialToUseFlag(Material.DIODE_BLOCK_ON, "diode");
        addMaterialToUseFlag(Material.WORKBENCH, "table");
        addMaterialToUseFlag(Material.WOODEN_DOOR, "door");

        if (plugin.isGt1_8()) {
            /* 1.8 Doors */
            addMaterialToUseFlag(Material.SPRUCE_DOOR, "door");
            addMaterialToUseFlag(Material.BIRCH_DOOR, "door");
            addMaterialToUseFlag(Material.JUNGLE_DOOR, "door");
            addMaterialToUseFlag(Material.ACACIA_DOOR, "door");
            addMaterialToUseFlag(Material.DARK_OAK_DOOR, "door");

            /* 1.8 Fence Gates */
            addMaterialToUseFlag(Material.SPRUCE_FENCE_GATE, "door");
            addMaterialToUseFlag(Material.BIRCH_FENCE_GATE, "door");
            addMaterialToUseFlag(Material.JUNGLE_FENCE_GATE, "door");
            addMaterialToUseFlag(Material.ACACIA_FENCE_GATE, "door");
            addMaterialToUseFlag(Material.DARK_OAK_FENCE_GATE, "door");
        }

        addMaterialToUseFlag(Material.FENCE_GATE, "door");
        addMaterialToUseFlag(Material.NETHER_FENCE, "door");
        addMaterialToUseFlag(Material.TRAP_DOOR, "door");
        addMaterialToUseFlag(Material.ENCHANTMENT_TABLE, "enchant");
        addMaterialToUseFlag(Material.STONE_BUTTON, "button");
        addMaterialToUseFlag(Material.LEVER, "lever");
        addMaterialToUseFlag(Material.BED_BLOCK, "bed");
        addMaterialToUseFlag(Material.BREWING_STAND, "brew");
        addMaterialToUseFlag(Material.CAKE, "cake");
        addMaterialToUseFlag(Material.NOTE_BLOCK, "note");
        addMaterialToUseFlag(Material.DRAGON_EGG, "egg");
        addMaterialToUseFlag(Material.COMMAND, "commandblock");
        addMaterialToUseFlag(Material.WOOD_BUTTON, "button");
        addMaterialToUseFlag(Material.ANVIL, "anvil");
        addMaterialToUseFlag(Material.FLOWER_POT, "flowerpot");
        addMaterialToUseFlag(Material.BEACON, "beacon");
        addMaterialToUseFlag(Material.JUKEBOX, "container");
        addMaterialToUseFlag(Material.CHEST, "container");
        addMaterialToUseFlag(Material.TRAPPED_CHEST, "container");
        addMaterialToUseFlag(Material.HOPPER, "container");
        addMaterialToUseFlag(Material.DROPPER, "container");
        addMaterialToUseFlag(Material.FURNACE, "container");
        addMaterialToUseFlag(Material.BURNING_FURNACE, "container");
        addMaterialToUseFlag(Material.DISPENSER, "container");
        addMaterialToUseFlag(Material.CAKE_BLOCK, "cake");
    }

    public static FlagPermissions load(final Map<String, Object> root) throws Exception {
        final FlagPermissions newperms = new FlagPermissions();
        return FlagPermissions.load(root, newperms);
    }

    public static FlagPermissions parseFromConfigNode(final String name, final ConfigurationSection node) {
        final FlagPermissions list = new FlagPermissions();
        final Set<String> keys = node.getConfigurationSection(name).getKeys(false);
        if (keys != null) {
            for (String key : keys) {
                final boolean state = node.getBoolean(name + "." + key, false);
                key = key.toLowerCase();
                if (state) {
                    list.setFlag(key, FlagState.TRUE);
                } else {
                    list.setFlag(key, FlagState.FALSE);
                }
            }
        }
        return list;
    }

    public static void removeFlagFromFlagGroup(final String group, final String flag) {
        if (validFlagGroups.containsKey(group)) {
            final ArrayList<String> flags = validFlagGroups.get(group);
            flags.remove(flag);
            if (flags.isEmpty()) {
                validFlagGroups.remove(group);
            }
        }
    }

    public static void removeMaterialFromUseFlag(final Material mat) {
        matUseFlagList.remove(mat);
    }

    public static FlagState stringToFlagState(final String flagstate) {
        if (flagstate.equalsIgnoreCase("true") || flagstate.equalsIgnoreCase("t")) {
            return FlagState.TRUE;
        } else if (flagstate.equalsIgnoreCase("false") || flagstate.equalsIgnoreCase("f")) {
            return FlagState.FALSE;
        } else if (flagstate.equalsIgnoreCase("remove") || flagstate.equalsIgnoreCase("r")) {
            return FlagState.NEITHER;
        } else {
            return FlagState.INVALID;
        }
    }

    @SuppressWarnings("unchecked")
    protected static FlagPermissions load(final Map<String, Object> root, final FlagPermissions newperms) throws Exception {
        if (root.containsKey("LastKnownPlayerNames")) {
            final Map<String, String> uuids = (Map<String, String>) root.get("LastKnownPlayerNames");
            final Iterator<Entry<String, Map<String, Boolean>>> mpi = ((Map<String, Map<String, Boolean>>) root.get("PlayerFlags")).entrySet().iterator();
            final Map<String, Map<String, Boolean>> newperm = new LinkedHashMap<>();
            while (mpi.hasNext()) {
                final Entry<String, Map<String, Boolean>> pmap = mpi.next();
                newperm.put(uuids.get(pmap.getKey()), pmap.getValue());
                mpi.remove();
            }
            newperms.playerFlags = newperm;
        } else {
            newperms.playerFlags = (Map<String, Map<String, Boolean>>) root.get("PlayerFlags");
        }
        newperms.groupFlags = (Map<String, Map<String, Boolean>>) root.get("GroupFlags");
        newperms.cuboidFlags = (Map<String, Boolean>) root.get("AreaFlags");
        if (newperms.playerFlags == null) {
            throw new IllegalArgumentException("错误的PlayerFlags数据...");
        }
        if (newperms.groupFlags == null) {
            throw new IllegalArgumentException("错误的GroupFlags数据...");
        }
        if (newperms.cuboidFlags == null) {
            throw new IllegalArgumentException("错误的CuboidFlags数据...");
        }
        return newperms;
    }

    public FlagPermissions() {
        cuboidFlags = Collections.synchronizedMap(new HashMap<String, Boolean>());
        playerFlags = Collections.synchronizedMap(new HashMap<String, Map<String, Boolean>>());
        groupFlags = Collections.synchronizedMap(new HashMap<String, Map<String, Boolean>>());
    }

    public boolean checkValidFlag(final String flag, final boolean globalflag) {
        if (validFlags.contains(flag)) {
            return true;
        }
        if (globalflag) {
            if (validAreaFlags.contains(flag)) {
                return true;
            }
        } else if (validPlayerFlags.contains(flag)) {
            return true;
        }
        return false;
    }

    public void clearFlags() {
        groupFlags.clear();
        playerFlags.clear();
        cuboidFlags.clear();
    }

    public void clearPlayersFlags(final String user) {
        if (playerFlags.containsKey(user)) {
            playerFlags.remove(user);
        }
    }

    public void copyUserPermissions(String fromUser, String toUser) {
        fromUser = fromUser.toLowerCase();
        toUser = toUser.toLowerCase();
        final Map<String, Boolean> get = playerFlags.get(fromUser);
        if (get != null) {
            Map<String, Boolean> targ = playerFlags.get(toUser);
            if (targ == null) {
                targ = new HashMap<String, Boolean>();
                playerFlags.put(toUser, targ);
            }
            for (final Entry<String, Boolean> entry : get.entrySet()) {
                targ.put(entry.getKey(), entry.getValue());
            }
        }
    }

    public Map<String, Boolean> getFlags() {
        return cuboidFlags;
    }

    public FlagPermissions getParent() {
        return parent;
    }

    public boolean groupHas(final String group, final String flag, final boolean def) {
        return this.groupCheck(group, flag, this.has(flag, def));
    }

    public boolean has(final String flag, final boolean def) {
        if (cuboidFlags.containsKey(flag)) {
            return cuboidFlags.get(flag);
        }
        if (parent != null) {
            return parent.has(flag, def);
        }
        return def;
    }

    public boolean inheritanceIsGroupSet(String group, final String flag) {
        group = group.toLowerCase();
        final Map<String, Boolean> flags = groupFlags.get(group);
        if (flags == null) {
            return parent == null ? false : parent.inheritanceIsGroupSet(group, flag);
        }
        return flags.containsKey(flag) ? true : parent == null ? false : parent.inheritanceIsGroupSet(group, flag);
    }

    public boolean inheritanceIsPlayerSet(String player, final String flag) {
        player = player.toLowerCase();
        final Map<String, Boolean> flags = playerFlags.get(player);
        if (flags == null) {
            return parent == null ? false : parent.inheritanceIsPlayerSet(player, flag);
        }
        return flags.containsKey(flag) ? true : parent == null ? false : parent.inheritanceIsPlayerSet(player, flag);
    }

    public boolean inheritanceIsSet(final String flag) {
        return cuboidFlags.containsKey(flag) ? true : parent == null ? false : parent.inheritanceIsSet(flag);
    }

    public boolean isGroupSet(String group, final String flag) {
        group = group.toLowerCase();
        final Map<String, Boolean> flags = groupFlags.get(group);
        if (flags == null) {
            return false;
        }
        return flags.containsKey(flag);
    }

    public boolean isPlayerSet(String player, final String flag) {
        player = player.toLowerCase();
        final Map<String, Boolean> flags = playerFlags.get(player);
        if (flags == null) {
            return false;
        }
        return flags.containsKey(flag);
    }

    public boolean isSet(final String flag) {
        return cuboidFlags.containsKey(flag);
    }

    public String listFlags() {
        final StringBuilder sbuild = new StringBuilder();
        final Set<Entry<String, Boolean>> set = cuboidFlags.entrySet();
        synchronized (cuboidFlags) {
            final Iterator<Entry<String, Boolean>> it = set.iterator();
            while (it.hasNext()) {
                final Entry<String, Boolean> next = it.next();
                if (next.getValue()) {
                    sbuild.append("+").append(next.getKey());
                    if (it.hasNext()) {
                        sbuild.append(" ");
                    }
                } else {
                    sbuild.append("-").append(next.getKey());
                    if (it.hasNext()) {
                        sbuild.append(" ");
                    }
                }
            }
        }
        if (sbuild.length() == 0) {
            sbuild.append("none");
        }
        return sbuild.toString();
    }

    public String listGroupFlags() {
        final StringBuilder sbuild = new StringBuilder();
        final Set<String> set = groupFlags.keySet();
        synchronized (groupFlags) {
            final Iterator<String> it = set.iterator();
            while (it.hasNext()) {
                final String next = it.next();
                final String perms = listGroupFlags(next);
                if (!perms.equals("none")) {
                    sbuild.append(next).append("[" + ChatColor.DARK_AQUA).append(perms).append(ChatColor.RED + "] ");
                }
            }
        }
        return sbuild.toString();
    }

    public String listGroupFlags(String group) {
        group = group.toLowerCase();
        if (groupFlags.containsKey(group)) {
            final StringBuilder sbuild = new StringBuilder();
            final Map<String, Boolean> get = groupFlags.get(group);
            final Set<Entry<String, Boolean>> set = get.entrySet();
            synchronized (get) {
                final Iterator<Entry<String, Boolean>> it = set.iterator();
                while (it.hasNext()) {
                    final Entry<String, Boolean> next = it.next();
                    if (next.getValue()) {
                        sbuild.append("+").append(next.getKey());
                        if (it.hasNext()) {
                            sbuild.append(" ");
                        }
                    } else {
                        sbuild.append("-").append(next.getKey());
                        if (it.hasNext()) {
                            sbuild.append(" ");
                        }
                    }
                }
            }
            if (sbuild.length() == 0) {
                groupFlags.remove(group);
                sbuild.append("none");
            }
            return sbuild.toString();
        }
        return "none";
    }

    public String listOtherPlayersFlags(String player) {
        player = player.toLowerCase();
        final StringBuilder sbuild = new StringBuilder();
        final Set<String> set = playerFlags.keySet();
        synchronized (playerFlags) {
            final Iterator<String> it = set.iterator();
            while (it.hasNext()) {
                final String next = it.next();
                if (!next.equals(player)) {
                    final String perms = listPlayerFlags(next);
                    if (!perms.equals("none")) {
                        sbuild.append(next).append("[" + ChatColor.DARK_AQUA).append(perms).append(ChatColor.RED + "] ");
                    }
                }
            }
        }
        return sbuild.toString();
    }

    public String listPlayerFlags(String player) {
        player = player.toLowerCase();
        if (playerFlags.containsKey(player)) {
            final StringBuilder sbuild = new StringBuilder();
            final Map<String, Boolean> get = playerFlags.get(player);
            final Set<Entry<String, Boolean>> set = get.entrySet();
            synchronized (get) {
                final Iterator<Entry<String, Boolean>> it = set.iterator();
                while (it.hasNext()) {
                    final Entry<String, Boolean> next = it.next();
                    if (next.getValue()) {
                        sbuild.append("+").append(next.getKey());
                        if (it.hasNext()) {
                            sbuild.append(" ");
                        }
                    } else {
                        sbuild.append("-").append(next.getKey());
                        if (it.hasNext()) {
                            sbuild.append(" ");
                        }
                    }
                }
            }
            if (sbuild.length() == 0) {
                playerFlags.remove(player);
                sbuild.append("none");
            }
            return sbuild.toString();
        }
        return "none";
    }

    public boolean playerHas(final String player, final String world, final String flag, final boolean def) {
        final String group = plugin.getPermissionManager().getGroupNameByPlayer(player, world);
        return this.playerCheck(player, flag, this.groupCheck(group, flag, this.has(flag, def)));
    }

    public void printFlags(final Player player) {
        player.sendMessage(ChatColor.YELLOW + plugin.getLanguage().getPhrase("Flags") + ":" + ChatColor.BLUE + " " + listFlags());
        player.sendMessage(ChatColor.YELLOW + plugin.getLanguage().getPhrase("Your.Flags") + ":" + ChatColor.GREEN + " " + listPlayerFlags(player.getName()));
        player.sendMessage(ChatColor.YELLOW + plugin.getLanguage().getPhrase("Group.Flags") + ":" + ChatColor.RED + " " + listGroupFlags());
        player.sendMessage(ChatColor.YELLOW + plugin.getLanguage().getPhrase("Others.Flags") + ":" + ChatColor.RED + " " + listOtherPlayersFlags(player.getName()));
    }

    public void removeAllGroupFlags(final String group) {
        groupFlags.remove(group);
    }

    public void removeAllPlayerFlags(final String player) {
        playerFlags.remove(player);
    }

    public Map<String, Object> save() {
        final Map<String, Object> root = new LinkedHashMap<String, Object>();
        root.put("PlayerFlags", playerFlags);
        root.put("GroupFlags", groupFlags);
        root.put("AreaFlags", cuboidFlags);
        return root;
    }

    public boolean setFlag(final String flag, final FlagState state) {
        if (state == FlagState.FALSE) {
            cuboidFlags.put(flag, false);
        } else if (state == FlagState.TRUE) {
            cuboidFlags.put(flag, true);
        } else if (state == FlagState.NEITHER) {
            if (cuboidFlags.containsKey(flag)) {
                cuboidFlags.remove(flag);
            }
        }
        return true;
    }

    public boolean setGroupFlag(String group, final String flag, final FlagState state) {
        group = group.toLowerCase();
        if (!groupFlags.containsKey(group)) {
            groupFlags.put(group, Collections.synchronizedMap(new HashMap<String, Boolean>()));
        }
        final Map<String, Boolean> map = groupFlags.get(group);
        if (state == FlagState.FALSE) {
            map.put(flag, false);
        } else if (state == FlagState.TRUE) {
            map.put(flag, true);
        } else if (state == FlagState.NEITHER) {
            if (map.containsKey(flag)) {
                map.remove(flag);
            }
        }
        if (map.isEmpty()) {
            groupFlags.remove(group);
        }
        return true;
    }

    public void setParent(final FlagPermissions p) {
        parent = p;
    }

    public boolean setPlayerFlag(String player, final String flag, final FlagState state) {
        player = player.toLowerCase();
        if (!playerFlags.containsKey(player)) {
            playerFlags.put(player, Collections.synchronizedMap(new HashMap<String, Boolean>()));
        }
        final Map<String, Boolean> map = playerFlags.get(player);
        if (state == FlagState.FALSE) {
            map.put(flag, false);
        } else if (state == FlagState.TRUE) {
            map.put(flag, true);
        } else if (state == FlagState.NEITHER) {
            if (map.containsKey(flag)) {
                map.remove(flag);
            }
        }
        if (map.isEmpty()) {
            playerFlags.remove(player);
        }
        return true;
    }

    private boolean groupCheck(final String group, final String flag, final boolean def) {
        if (groupFlags.containsKey(group)) {
            final Map<String, Boolean> gmap = groupFlags.get(group);
            if (gmap.containsKey(flag)) {
                return gmap.get(flag);
            }
        }
        if (parent != null) {
            return parent.groupCheck(group, flag, def);
        }
        return def;
    }

    private boolean playerCheck(String player, final String flag, final boolean def) {
        player = player.toLowerCase();
        if (playerFlags.containsKey(player)) {
            final Map<String, Boolean> pmap = playerFlags.get(player);
            if (pmap.containsKey(flag)) {
                return pmap.get(flag);
            }
        }
        if (parent != null) {
            return parent.playerCheck(player, flag, def);
        }
        return def;
    }

    public static enum FlagState {
        FALSE,
        INVALID,
        NEITHER,
        TRUE
    }
}
