public class ConfigurationSettings {

    private ConfigurationValues configValues;

    public ConfigurationSettings(ConfigurationValues configValues) {
        this.configValues = configValues;
        autoDetectBlocksToggle = new boolean[ToolType.values().length];
        //noinspection unchecked
        autoDetectBlocksList = new HashSet[ToolType.values().length];
        for (ToolType tool : ToolType.values()) {
            autoDetectBlocksToggle[tool.ordinal()] = false;
            autoDetectBlocksList[tool.ordinal()] = new HashSet<String>();
        }
        blockCongruenceList = new ArrayList<Set<BlockID>>();
        blockCongruenceMap = new HashMap<BlockID, Integer>();

        toolsAndBlocks = new HashMap<String, Tool>();

        parseConfigValues();
    }

    private void parseConfigValues() {
        try {
            JsonObject toolsJson = configValues.toolsAndBlocks.getAsJsonObject().getAsJsonObject("tools");
            for(Map.Entry<String, JsonElement> entry: toolsJson.entrySet()) {
                String toolName = entry.getKey();
                Gson gson = new Gson();
                ToolStruct toolInstance = gson.fromJson(entry.getValue().getAsJsonObject(), ToolStruct.class);
                if(toolInstance.name == null) toolInstance.name = toolName;
                if(toolInstance.icon == null) toolInstance.icon = "";
                if(toolInstance.blocklist == null) toolInstance.blocklist = new String[]{};
                if(toolInstance.toollist == null) toolInstance.toollist = new String[]{};
                toolsAndBlocks.put(toolName, new Tool(toolInstance));
            }
        }
        catch (Exception ignored) {

        }

        boolean defaultsAdded = false;
        for(String defaultTool : configValues.defaultTools.keySet()) {
            if(!toolsAndBlocks.containsKey(defaultTool)) {
                toolsAndBlocks.put(defaultTool, configValues.defaultTools.get(defaultTool));
                defaultsAdded = true;
            }
        }

        for(ToolType toolType : ToolType.values()) {
            setAutodetectBlocksToggle(toolType, configValues.toolConfig.get(toolType).autodetectToggle.value);
            setAutodetectBlocksList(toolType, configValues.toolConfig.get(toolType).autodetectList.value);
        }

        setBlockLimit(configValues.BLOCK_LIMIT);
        setBlocksPerTick(configValues.BLOCKS_PER_TICK);
        setRadiusLimit(configValues.RADIUS_LIMIT);

        setBlockCongruenceList(configValues.BLOCK_EQUIVALENCY_LIST);
        setHungerModifier(configValues.HUNGER_MULTIPLIER);
        setExperienceModifier(configValues.EXPERIENCE_MULTIPLIER);

        setEnableAllBlocks(configValues.ENABLE_ALL_BLOCKS);
        setEnableAllTools(configValues.ENABLE_ALL_TOOLS);

        setPreferredMode(configValues.CLIENT_PREFERRED_MODE, ConfigurationValues.CLIENT_PREFERRED_MODE_DEFAULT);

        if(defaultsAdded) {
            saveConfigs();
        }
    }

    public void reloadConfigFile() {
        configValues.loadConfigFile();
        parseConfigValues();
        saveConfigs();
    }

    public boolean getEnableAllBlocks() {
        return enableAllBlocks;
    }

    void setEnableAllBlocks(boolean enableAllBlocks) {
        this.enableAllBlocks = enableAllBlocks;
    }

    public boolean getEnableAllTools() {
        return enableAllTools;
    }

    void setEnableAllTools(boolean enableAllTools) {
        this.enableAllTools = enableAllTools;
    }

    private boolean[] autoDetectBlocksToggle;
    private HashSet<String>[] autoDetectBlocksList;

    /**
     * Groups congruent block IDs together in an array.
     * Congruent blocks are blocks that should be treated as the same block.
     */
    private ArrayList<Set<BlockID>> blockCongruenceList;

    /**
     * A map to easily get the list of congruent block IDS for a specified block ID.
     */
    private HashMap<BlockID, Integer> blockCongruenceMap;

    private int blockLimit;

    private int radiusLimit;

    private int blocksPerTick;

    private boolean enableAllBlocks;

    private boolean enableAllTools;

    private int preferredMode;

    private Map<String, Tool> toolsAndBlocks;

    void setAutodetectBlocksToggle(ToolType tool, boolean value) {
        autoDetectBlocksToggle[tool.ordinal()] = value;
    }

    public boolean getAutodetectBlocksToggle(ToolType tool) {
        return autoDetectBlocksToggle[tool.ordinal()];
    }

    void setAutodetectBlocksList(ToolType tool, String list) {
        String[] parts = list.split(",");

        for(String part : parts) {
            if(!part.isEmpty()) {
                autoDetectBlocksList[tool.ordinal()].add(part);
            }
        }
    }

    public Set<String> getAutodetectBlocksList(ToolType tool) {
        return autoDetectBlocksList[tool.ordinal()];
    }

    public void addToolType(String newType, String name, String icon) {
        if(!newType.isEmpty() && !toolsAndBlocks.containsKey(newType)) {
            Tool newTool = new Tool(name, icon, new String[]{}, new String[]{});
            toolsAndBlocks.put(newType, newTool);
        }
    }

    public void addBlockToWhitelist(String tool, BlockID block) {
        if(!block.name.isEmpty()) {
            BlockID testBlock = new BlockID(block.name, -1);
            if(!toolsAndBlocks.get(tool).blocklist.contains(block) && !toolsAndBlocks.get(tool).blocklist.contains(testBlock)) {
                block.metadata = block.metadata == OreDictionary.WILDCARD_VALUE ? -1 : block.metadata;
                toolsAndBlocks.put(tool, toolsAndBlocks.get(tool).addBlock(block));
            }
        }
    }

    public void removeBlockFromWhitelist(String tool, BlockID block) {
        BlockID blockNoMeta = new BlockID(block.name, block.metadata);
        blockNoMeta.metadata = -1;
        if(toolsAndBlocks.get(tool).blocklist.contains(block)) {
            toolsAndBlocks.put(tool, toolsAndBlocks.get(tool).removeBlock(block));
        }
        else if(toolsAndBlocks.get(tool).blocklist.contains(blockNoMeta)) {
            toolsAndBlocks.put(tool, toolsAndBlocks.get(tool).removeBlock(blockNoMeta));
        }
    }

    public ArrayList<String> getBlockIDArray(String toolType) {
        ArrayList<String> output = new ArrayList<String>();
        for(BlockID blockID : toolsAndBlocks.get(toolType).blocklist) {
            output.add(blockID.toString());
        }
        return output;
    }

    public boolean whiteListHasBlockId(String tool, BlockID targetBlock) {
        BlockID targetBlockNoMeta = new BlockID(targetBlock.name, targetBlock.metadata);
        targetBlockNoMeta.metadata = -1;
        return toolsAndBlocks.get(tool).blocklist.contains(targetBlock) || toolsAndBlocks.get(tool).blocklist.contains(targetBlockNoMeta);
    }

    public void addCongruentBlocks(String existingBlock, String newBlock) {
        setBlockCongruenceList(String.format("%s=%s", existingBlock, newBlock));
    }

    /**
     * Add groups of blocks mentioned in congruenceList to a set of blocks to be considered equal.
     * @param congruenceList String of groups of blocks with metadata value to be added to congruence list. Format is
     *                       'modName:block_name/metadata'. 'minecraft' is the modName for vanilla.
     *                       Use ',' to separate blocks in whitelist.
     *                       See {@link ConfigurationValues}
     */
    void setBlockCongruenceList(String congruenceList) {
        String[] blocksString = congruenceList.split(",");

        for (String congruentBlocks : blocksString) {
            int newId = blockCongruenceList.size();
            //List<BlockID> newCongruentBlocks = new ArrayList<BlockID>();
            Set<BlockID> newCongruentBlocks = new HashSet<BlockID>();

            if (!congruentBlocks.contains("=")) {
                 continue;
            }

            for (String blockString : congruentBlocks.split("=")) {
                BlockID newBlockId = new BlockID(blockString);

                newCongruentBlocks.add(newBlockId);
                if (blockCongruenceMap.containsKey(newBlockId)) {
                    newId = blockCongruenceMap.get(newBlockId);
                }

                blockCongruenceMap.put(newBlockId, newId);
            }

            newCongruentBlocks.addAll(newCongruentBlocks);
            if(newId < blockCongruenceList.size()) {
                blockCongruenceList.get(newId).addAll(newCongruentBlocks);
            }
            else {
                blockCongruenceList.add(newId, newCongruentBlocks);
            }
        }
    }

    public String getBlockCongruenceList() {
        ArrayList<String> congruenceGroups = new ArrayList<String>();
        for(Set<BlockID> group : blockCongruenceList) {
            congruenceGroups.add(Joiner.on('=').join(group));
        }
        return Joiner.on(',').join(congruenceGroups);
    }

    @SuppressWarnings("UnusedDeclaration")
    public Set<BlockID> getCongruentBlocks(BlockID targetBlock) {
        int listId = blockCongruenceMap.get(targetBlock);
        return blockCongruenceList.get(listId);
    }

    public boolean areBlocksCongruent(BlockID block1, BlockID block2) {
        BlockID block1NoMeta = new BlockID(block1.name, block1.metadata);
        block1NoMeta.metadata = -1;
        BlockID block2NoMeta = new BlockID(block2.name, block2.metadata);
        block2NoMeta.metadata = -1;

        int targetBlock1;
        int targetBlock2;

        if(blockCongruenceMap.containsKey(block1) || blockCongruenceMap.containsKey(block1NoMeta)) {
            targetBlock1 = blockCongruenceMap.containsKey(block1) ? blockCongruenceMap.get(block1) : blockCongruenceMap.get(block1NoMeta);
        }
        else {
            return false;
        }
        if(blockCongruenceMap.containsKey(block2) || blockCongruenceMap.containsKey(block2NoMeta)) {
            targetBlock2 = blockCongruenceMap.containsKey(block2) ? blockCongruenceMap.get(block2) : blockCongruenceMap.get(block2NoMeta);
        }
        else {
            return false;
        }

        return targetBlock1 == targetBlock2;
    }

    public void addTool(String tool, String name) {
        toolsAndBlocks.put(tool, toolsAndBlocks.get(tool).addTool(name));
    }

    public void removeTool(String tool, String name) {
        toolsAndBlocks.put(tool, toolsAndBlocks.get(tool).removeTool(name));
    }

    public Set<String> getToolTypeNames() {
        return toolsAndBlocks.keySet();
    }

    public String getToolTypeName(String toolType) {
        return toolsAndBlocks.get(toolType).name;
    }

    public String getToolTypeIcon(String toolType) {
        return toolsAndBlocks.get(toolType).icon;
    }

    public ArrayList<String> getToolIdArray(String tool) {
        return new ArrayList<String>(toolsAndBlocks.get(tool).toollist);
    }

    public int getBlockLimit() {
        return blockLimit;
    }

    public void setBlockLimit(int blockLimit) {
        if(blockLimit < -1) {
            radiusLimit = -1;
        }
        this.blockLimit = blockLimit;
    }

    public int getRadiusLimit() {
        return radiusLimit;
    }

    public void setRadiusLimit(int radiusLimit) {
        if (radiusLimit < -1) {
            radiusLimit = -1;
        }
        else if (radiusLimit > 1000) {
            radiusLimit = 1000;
        }

        this.radiusLimit = radiusLimit;
    }

    public int getBlocksPerTick() {
        return blocksPerTick;
    }

    public void setBlocksPerTick(int blocksPerTick) {
        if (blocksPerTick < -1) {
            blocksPerTick = -1;
        }
        else if (blocksPerTick > 1000) {
            blocksPerTick = 100;
        }

        this.blocksPerTick = blocksPerTick;
    }

    private int hungerModifier;
    private int experienceModifier;

    public int getHungerMultiplier() {
        return hungerModifier;
    }

    public void setHungerModifier(int hungerModifier) {
        this.hungerModifier = hungerModifier < 0 ? 0 : hungerModifier;
    }

    public int getExperienceMultiplier() {
        return experienceModifier;
    }

    public void setExperienceModifier(int experienceModifier) {
        this.experienceModifier = experienceModifier < 0 ? 0 : experienceModifier;
    }

    public boolean toolIsOfType(ItemStack tool, String type) {
        return tool == null || toolsAndBlocks.get(type).toollist.contains(Item.itemRegistry.getNameForObject(tool.getItem()).toString());
    }

    /**
     * Sets the preferred mode to the modeString if valid or fallback if modeString is not valid
     * @param modeString one of 'auto', 'sneak', 'no_sneak'
     * @param fallback one of 'auto', 'sneak', 'no_sneak'
     * @return If modestring is valid
     */
    boolean setPreferredMode(String modeString, String fallback) {
        if("disabled".equals(modeString)) {
            preferredMode = PreferredMode.DISABLED;
            return true;
        }
        else if("pressed".equals(modeString)) {
            preferredMode = PreferredMode.PRESSED;
            return true;
        }
        else if("released".equals(modeString)) {
            preferredMode = PreferredMode.RELEASED;
            return true;
        }
        else if("sneak".equals(modeString)) {
            preferredMode = PreferredMode.SNEAK_ACTIVE;
            return true;
        }
        else if("no_sneak".equals(modeString)) {
            preferredMode = PreferredMode.SNEAK_INACTIVE;
            return true;
        }

        // No valid option.
        // Set to fallback
        if(fallback != null) {
            setPreferredMode(fallback, null);
        }
        return false;
    }

    public void setPreferredMode(int mode) {
        preferredMode = mode;
    }

    /**
     * Returns the preferred mode.
     * @return One of PreferredMode.AUTO, PreferredMode.SNEAK, PreferredMode.NO_SNEAK
     */
    public int getPreferredMode() {
        return preferredMode;
    }

    public String getPreferredModeString() {
        switch (preferredMode) {
            case PreferredMode.DISABLED:
                return "disabled";
            case PreferredMode.PRESSED:
                return "pressed";
            case PreferredMode.RELEASED:
                return "released";
            case PreferredMode.SNEAK_ACTIVE:
                return "sneak";
            case PreferredMode.SNEAK_INACTIVE:
                return "no_sneak";
        }
        return "";
    }

    public void saveConfigs() {
        configValues.toolsAndBlocks = getToolsAndBlocksJson();

        configValues.BLOCK_LIMIT = getBlockLimit();
        configValues.BLOCKS_PER_TICK = getBlocksPerTick();
        configValues.RADIUS_LIMIT = getRadiusLimit();

        configValues.BLOCK_EQUIVALENCY_LIST = getBlockCongruenceList();
        configValues.HUNGER_MULTIPLIER = getHungerMultiplier();
        configValues.EXPERIENCE_MULTIPLIER = getExperienceMultiplier();

        configValues.ENABLE_ALL_BLOCKS = getEnableAllBlocks();
        configValues.ENABLE_ALL_TOOLS = getEnableAllTools();

        configValues.CLIENT_PREFERRED_MODE = getPreferredModeString();

        configValues.saveConfigFile();
    }

    public JsonObject getToolsAndBlocksJson() {
        JsonObject jsonTools = new JsonObject();

        List<String> sortedToolNames = new ArrayList<String>(toolsAndBlocks.keySet());
        Collections.sort(sortedToolNames);
        for(String toolName : sortedToolNames) {
            Tool tool = toolsAndBlocks.get(toolName);

            JsonArray toolList = new JsonArray();
            List<String> sortedToolList = new ArrayList<String>(tool.toollist);
            Collections.sort(sortedToolList);
            for(String whiteListTool : sortedToolList) {
                toolList.add(new JsonPrimitive(whiteListTool));
            }
            JsonArray blockList = new JsonArray();
            List<BlockID> sortedBlockList= new ArrayList<BlockID>(tool.blocklist);
            Collections.sort(sortedBlockList);
            for(BlockID whitelistBlock : sortedBlockList) {
                blockList.add(new JsonPrimitive(whitelistBlock.toString()));
            }

            JsonObject jsonTool = new JsonObject();
            jsonTool.add("name", new JsonPrimitive(tool.name));
            jsonTool.add("icon", new JsonPrimitive(tool.icon));
            jsonTool.add("toollist", toolList);
            jsonTool.add("blocklist", blockList);

            jsonTools.add(toolName, jsonTool);
        }

        JsonObject jsonObject = new JsonObject();
        jsonObject.add("tools", jsonTools);

        return jsonObject;
    }
} public class ConfigurationValues {

    private Configuration configFile;
    private File configFileJson;

    public static final String CONFIG_AUTODETECT = "autodetect";
    public static final String CONFIG_AUTODETECT_COMMENT = "Autodetect items and blocks during game start-up.";
    public static final String CONFIG_LIMITS = "limit";
    public static final String CONFIG_MISC = "misc";
    public static final String CONFIG_OVERRIDE = "overrides";
    public static final String CONFIG_CLIENT = "client";
    public static final String CONFIG_CLIENT_COMMENT = "These settings are used client side, so they have no impact on servers.";

    public HashMap<ToolType, ConfigToolValue> toolConfig = new HashMap<ToolType, ConfigToolValue>(ToolType.values().length);

    public String BLOCK_EQUIVALENCY_LIST;
    public static final String BLOCK_EQUIVALENCY_LIST_DEFAULT = "minecraft:redstone_ore=minecraft:lit_redstone_ore";
    public static final String BLOCK_EQUIVALENCY_LIST_CONFIGNAME = "equalBlocks";
    public static final String BLOCK_EQUIVALENCY_LIST_DESCRIPTION = String.format("Block IDs (with metadata) to consider equivalent.\nNames are formatted like 'modName:block_name/metadata'. Separate names (with metadata) with ','. Use 'minecraft' as the mod name for vanilla blocks.\n[default: '%s']", BLOCK_EQUIVALENCY_LIST_DEFAULT);

    public int BLOCK_LIMIT;
    public static final int BLOCK_LIMIT_DEFAULT = 800;
    public static final String BLOCK_LIMIT_CONFIGNAME = "limit.blocks";
    public static final String BLOCK_LIMIT_DESCRIPTION = String.format("Limit of blocks to be destroyed at once. Use -1 for infinite. [range: -1 to 2147483647, default: %d]", BLOCK_LIMIT_DEFAULT);

    public int RADIUS_LIMIT;
    public static final int RADIUS_LIMIT_DEFAULT = 20;
    public static final String RADIUS_LIMIT_CONFIGNAME = "limit.radius";
    public static final String RADIUS_LIMIT_DESCRIPTION = String.format("Maximum distance from the first block to search for blocks to destroy. [range: -1 to 1000, default: %d]", RADIUS_LIMIT_DEFAULT);

    public int BLOCKS_PER_TICK;
    public static final int BLOCKS_PER_TICK_DEFAULT = 10;
    public static final String BLOCKS_PER_TICK_CONFIGNAME = "limit.blocksPerTick";
    public static final String BLOCKS_PER_TICK_DESCRIPTION = String.format("Maximum number of blocks to be removed per game tick (1/20 seconds). Using a low number will keep the game from getting huge performance drops but also decreases the speed at which blocks are destroyed. [range: 1 ~ 1000, default: %d]", BLOCKS_PER_TICK_DEFAULT);

    public int HUNGER_MULTIPLIER;
    public static final int HUNGER_MULTIPLIER_DEFAULT = 0;
    public static final String HUNGER_MULTIPLIER_CONFIGNAME = "hungermodifier";
    public static final String HUNGER_MULTIPLIER_DESCRIPTION = String.format("Change how much additional hunger/exhaustion is used for every block. Every point is 0.025 hunger/exhaustion per block. [range: 0 to 2147483647, default: %d]", HUNGER_MULTIPLIER_DEFAULT);

    public int EXPERIENCE_MULTIPLIER;
    public static final int EXPERIENCE_MULTIPLIER_DEFAULT = 0;
    public static final String EXPERIENCE_MULTIPLIER_CONFIGNAME = "expmodifier";
    public static final String EXPERIENCE_MULTIPLIER_DESCRIPTION = String.format("Change how much experience is required to Veinmine and how much experience each block uses. [range: 0 to 2147483647, default: %d]", EXPERIENCE_MULTIPLIER_DEFAULT);

    public boolean ENABLE_ALL_BLOCKS;
    public static final boolean ENABLE_ALL_BLOCKS_DEFAULT = false;
    public static final String ENABLE_ALL_BLOCKS_CONFIGNAME = "override.allBlocks";
    public static final String ENABLE_ALL_BLOCKS_DESCRIPTION = "Mine all blocks with all registered tools that can harvest blocks.";

    public boolean ENABLE_ALL_TOOLS;
    public static final boolean ENABLE_ALL_TOOLS_DEFAULT = false;
    public static final String ENABLE_ALL_TOOLS_CONFIGNAME = "override.allTools";
    public static final String ENABLE_ALL_TOOLS_DESCRIPTION = "Allow all tools, including the open hand, to be used to mine blocks.";

    public String CLIENT_PREFERRED_MODE;
    public static final String CLIENT_PREFERRED_MODE_DEFAULT = "pressed";
    public static final String CLIENT_PREFERRED_MODE_CONFIGNAME = "client.preferredMode";
    public static final String CLIENT_PREFERRED_MODE_DESCRIPTION = "What mode should the client use when joining a game.\nValid modes: [default: pressed]\n  'disabled' = don't enable, even when keybind pressed\n  'pressed' = enables when keybind is pressed\n  'released' = enables when keybind is released\n  'sneak' = enables when sneaking (ignores keybind)\n  'no_sneak' = enables when not sneaking (ignores keybind)";

    public JsonElement toolsAndBlocks;
    public Map<String, Tool> defaultTools;

    public ConfigurationValues(File defaultConfig, File toolsJson) {
        configFile = new Configuration(defaultConfig);
        configFileJson = toolsJson;

        defaultTools = new HashMap<String, Tool>();
        defaultTools.put("axe", new Tool("Axe", "minecraft:diamond_axe",
                new String[] {"minecraft:wooden_axe", "minecraft:stone_axe", "minecraft:golden_axe", "minecraft:iron_axe", "minecraft:diamond_axe"},
                new String[] {"minecraft:log", "minecraft:log2", "minecraft:leaves", "minecraft:leaves2", "minecraft:fence"}));
        defaultTools.put("hoe", new Tool("Hoe", "minecraft:diamond_hoe",
                new String[] {"minecraft:wooden_hoe", "minecraft:stone_hoe", "minecraft:golden_hoe", "minecraft:iron_hoe", "minecraft:diamond_hoe"},
                new String[] {"minecraft:wheat", "minecraft:pumpkin", "minecraft:melon_block", "minecraft:carrots", "minecraft:potatoes"}));
        defaultTools.put("pickaxe", new Tool("Pickaxe", "minecraft:diamond_pickaxe",
                new String[] {"minecraft:wooden_pickaxe", "minecraft:stone_pickaxe", "minecraft:golden_pickaxe", "minecraft:iron_pickaxe", "minecraft:diamond_pickaxe"},
                new String[] {"minecraft:coal_ore", "minecraft:gold_ore", "minecraft:iron_ore", "minecraft:diamond_ore", "minecraft:lapis_ore", "minecraft:emerald_ore", "minecraft:quartz_ore", "minecraft:redstone_ore", "minecraft:lit_redstone_ore", "minecraft:mossy_cobblestone", "minecraft:glowstone", "minecraft:obsidian", "minecraft:nether_brick_fence", "minecraft:cobblestone_wall"}));
        defaultTools.put("shears", new Tool("Shears", "minecraft:shears",
                new String[] {"minecraft:shears"},
                new String[] {"minecraft:leaves", "minecraft:leaves2", "minecraft:web", "minecraft:tallgrass", "minecraft:deadbush", "minecraft:wool", "minecraft:vine"}));
        defaultTools.put("shovel", new Tool("Shovel", "minecraft:diamond_shovel",
                new String[] {"minecraft:wooden_shovel", "minecraft:stone_shovel", "minecraft:golden_shovel", "minecraft:iron_shovel", "minecraft:diamond_shovel"},
                new String[] {"minecraft:clay", "minecraft:gravel"}));

        toolConfig.put(ToolType.AXE, new ConfigToolValue("axe", true, "log,treeLeaves", "", ""));
        toolConfig.put(ToolType.HOE, new ConfigToolValue("hoe", false, "", "", ""));
        toolConfig.put(ToolType.PICKAXE, new ConfigToolValue("pickaxe", true, "ore", "", ""));
        toolConfig.put(ToolType.SHEARS, new ConfigToolValue("shears", true, "treeLeaves", "", ""));
        toolConfig.put(ToolType.SHOVEL, new ConfigToolValue("shovel", false, "", "", ""));

        toolsAndBlocks = new JsonObject();

        loadConfigFile();
        saveConfigFile();
    }

    public void loadConfigFile() {
        try {
            if(configFileJson.exists()) {
                String toolsAndBlocksString = Files.toString(configFileJson, Charset.defaultCharset());
                toolsAndBlocks = new JsonParser().parse(toolsAndBlocksString);
            }
            else {
                VeinMiner.instance.logger.info("tools-and-blocks.json missing. Creating.");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        catch(JsonParseException e) {
            VeinMiner.instance.logger.error(String.format("Error parsing %s; Json error: %s", configFileJson.getName(), e.getLocalizedMessage()));
            VeinMiner.instance.logger.error("The above line number may be inexact.");
            VeinMiner.instance.logger.error("Asking java to exit");
            FMLCommonHandler.instance().exitJava(1, false);
        }

        configFile.load();

        configFile.addCustomCategoryComment(CONFIG_AUTODETECT, CONFIG_AUTODETECT_COMMENT);

        for(ToolType toolType : ToolType.values()) {
            ConfigOptionBoolean autoToggle = toolConfig.get(toolType).autodetectToggle;
            autoToggle.value = configFile.get(CONFIG_AUTODETECT, autoToggle.configName, autoToggle.valueDefault, autoToggle.description).getBoolean(autoToggle.valueDefault);
            ConfigOptionString autoList = toolConfig.get(toolType).autodetectList;
            autoList.value = configFile.get(CONFIG_AUTODETECT, autoList.configName, autoList.valueDefault, autoList.description).getString();
        }

        BLOCK_LIMIT = configFile.get(CONFIG_LIMITS, BLOCK_LIMIT_CONFIGNAME, BLOCK_LIMIT_DEFAULT, BLOCK_LIMIT_DESCRIPTION).getInt(BLOCK_LIMIT_DEFAULT);
        RADIUS_LIMIT = configFile.get(CONFIG_LIMITS, RADIUS_LIMIT_CONFIGNAME, RADIUS_LIMIT_DEFAULT, RADIUS_LIMIT_DESCRIPTION).getInt(RADIUS_LIMIT_DEFAULT);
        BLOCKS_PER_TICK = configFile.get(CONFIG_LIMITS, BLOCKS_PER_TICK_CONFIGNAME, BLOCKS_PER_TICK_DEFAULT, BLOCKS_PER_TICK_DESCRIPTION).getInt(BLOCKS_PER_TICK_DEFAULT);

        BLOCK_EQUIVALENCY_LIST = configFile.get(CONFIG_MISC, BLOCK_EQUIVALENCY_LIST_CONFIGNAME, BLOCK_EQUIVALENCY_LIST_DEFAULT, BLOCK_EQUIVALENCY_LIST_DESCRIPTION).getString();
        HUNGER_MULTIPLIER = configFile.get(CONFIG_MISC, HUNGER_MULTIPLIER_CONFIGNAME, HUNGER_MULTIPLIER_DEFAULT, HUNGER_MULTIPLIER_DESCRIPTION).getInt();
        EXPERIENCE_MULTIPLIER = configFile.get(CONFIG_MISC, EXPERIENCE_MULTIPLIER_CONFIGNAME, EXPERIENCE_MULTIPLIER_DEFAULT, EXPERIENCE_MULTIPLIER_DESCRIPTION).getInt();

        ENABLE_ALL_BLOCKS = configFile.get(CONFIG_OVERRIDE, ENABLE_ALL_BLOCKS_CONFIGNAME, ENABLE_ALL_BLOCKS_DEFAULT, ENABLE_ALL_BLOCKS_DESCRIPTION).getBoolean(ENABLE_ALL_BLOCKS_DEFAULT);
        ENABLE_ALL_TOOLS = configFile.get(CONFIG_OVERRIDE, ENABLE_ALL_TOOLS_CONFIGNAME, ENABLE_ALL_TOOLS_DEFAULT, ENABLE_ALL_TOOLS_DESCRIPTION).getBoolean(ENABLE_ALL_TOOLS_DEFAULT);

        configFile.addCustomCategoryComment(CONFIG_CLIENT, CONFIG_CLIENT_COMMENT);
        CLIENT_PREFERRED_MODE = configFile.get(CONFIG_CLIENT, CLIENT_PREFERRED_MODE_CONFIGNAME, CLIENT_PREFERRED_MODE_DEFAULT, CLIENT_PREFERRED_MODE_DESCRIPTION).getString();

        configFile.save();
    }

    public void saveConfigFile() {
        //configFile.load();

        configFile.getCategory(CONFIG_LIMITS).get(BLOCK_LIMIT_CONFIGNAME).set(BLOCK_LIMIT);
        configFile.getCategory(CONFIG_LIMITS).get(RADIUS_LIMIT_CONFIGNAME).set(RADIUS_LIMIT);
        configFile.getCategory(CONFIG_LIMITS).get(BLOCKS_PER_TICK_CONFIGNAME).set(BLOCKS_PER_TICK);

        configFile.getCategory(CONFIG_MISC).get(BLOCK_EQUIVALENCY_LIST_CONFIGNAME).set(BLOCK_EQUIVALENCY_LIST);
        configFile.getCategory(CONFIG_MISC).get(HUNGER_MULTIPLIER_CONFIGNAME).set(HUNGER_MULTIPLIER);
        configFile.getCategory(CONFIG_MISC).get(EXPERIENCE_MULTIPLIER_CONFIGNAME).set(EXPERIENCE_MULTIPLIER);

        configFile.getCategory(CONFIG_OVERRIDE).get(ENABLE_ALL_BLOCKS_CONFIGNAME).set(ENABLE_ALL_BLOCKS);
        configFile.getCategory(CONFIG_OVERRIDE).get(ENABLE_ALL_TOOLS_CONFIGNAME).set(ENABLE_ALL_TOOLS);

        configFile.getCategory(CONFIG_CLIENT).get(CLIENT_PREFERRED_MODE_CONFIGNAME).set(CLIENT_PREFERRED_MODE);

        if(configFile.hasChanged()) {
            configFile.save();
        }

        Gson gson = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
        String outputJson = gson.toJson(toolsAndBlocks);
        try {
            Files.write(outputJson, configFileJson, Charset.defaultCharset());
        } catch (IOException e) {
            VeinMiner.instance.logger.error("Error writing file %s!", configFileJson.toString());
        }
    }
} public enum ToolType {
    AXE,
    HOE,
    PICKAXE,
    SHEARS,
    SHOVEL
} public class CoreEvents {
    @SubscribeEvent(priority = EventPriority.LOWEST)
    public void blockBreakEvent(BlockEvent.BreakEvent event) {
        if(event.getWorld().isRemote) {
            // I am officially lost.
            // I am a server method but I find myself on the client.
            return;
        }

        Point breakPont = Compatibility.getPoint(event);
        MinerServer server = VeinMiner.instance.minerServer;
        if(server == null || server.pointIsBlacklisted(breakPont)) {
           return;
        }

        if(event.getPlayer() != null && server.getInstance(event.getPlayer()) != null) {
            // Ignore additional blocks for the player whilst still veinmining.
            return;
        }

        ConfigurationSettings configurationSettings = VeinMiner.instance.minerServer.getConfigurationSettings();
        int radiusLimit = configurationSettings.getRadiusLimit();
        int blockLimit = configurationSettings.getBlockLimit();

        VeinminerInitalToolCheck startConfig = new VeinminerInitalToolCheck(event.getPlayer(), breakPont, radiusLimit, blockLimit, configurationSettings.getRadiusLimit(), configurationSettings.getBlockLimit());
        MinecraftForge.EVENT_BUS.post(startConfig);
        if(startConfig.allowVeinminerStart.isAllowed()) {
            radiusLimit = Math.min(startConfig.radiusLimit, radiusLimit);
            blockLimit = Math.min(startConfig.blockLimit, blockLimit);
            //MinerInstance instance = new MinerInstance(event.world, (EntityPlayerMP) event.getPlayer(), Compatibility.getPoint(event), new BlockID(Block.blockRegistry.getNameForObject(event.block), event.blockMetadata), server, radiusLimit, blockLimit);
            MinerInstance instance = new MinerInstance(event.getWorld(), (EntityPlayerMP) event.getPlayer(), Compatibility.getPoint(event), new BlockID(event.getState()), server, radiusLimit, blockLimit);

            if (instance.mineBlock(breakPont) > 0) {
                event.setCanceled(true);
            }
        }
    }

    public void collectDrops(BlockEvent.HarvestDropsEvent event) {

    }
} public class MinerLogger {
    public static void debug(String format, Object... data) {
        if(ModInfo.DEBUG_MODE) {
            LogManager.getLogger(ModInfo.MODID).info(String.format(format, data));
        }
    }
} public class ModInfo {
    public static boolean DEBUG_MODE = false;

    public static final String MODID = "VeinMiner";
    public static final String COREMOD_ID = "VeinMiner_coremod";
    public static final String COREMOD_NAME = "Core mod";
    public static final String PROXY_SERVER_CLASS = "portablejim.veinminer.proxy.CommonProxy";
    public static final String PROXY_CLIENT_CLASS = "portablejim.veinminer.proxy.ClientProxy";
    public static final String CHANNEL = "VeinMiner";
} public class PacketChangeMode implements IMessage {
    public short mode;

    @SuppressWarnings("UnusedDeclaration")
    public PacketChangeMode() {
        mode = 0;
    }

    public PacketChangeMode(int clientMode) {
        switch(clientMode) {
            case PreferredMode.DISABLED:
            case PreferredMode.PRESSED:
            case PreferredMode.RELEASED:
            case PreferredMode.SNEAK_ACTIVE:
            case PreferredMode.SNEAK_INACTIVE:
                mode = (short) clientMode;
                break;
            default:
                mode = 0;
        }
    }

    @Override
    public void toBytes(ByteBuf buffer) {
        buffer.writeShort(mode);
    }

    @Override
    public void fromBytes(ByteBuf buffer) {
        mode = buffer.readShort();
    }

    public static class Handler extends GenericHandler<PacketChangeMode> {
        @Override
        public void processMessage(PacketChangeMode packetClientPresent, MessageContext context) {
            if(context.side == Side.CLIENT) {
                VeinMiner.instance.currentMode = packetClientPresent.mode;
                VeinMiner.instance.logger.info(String.format("Received mode change %d", packetClientPresent.mode));
                switch(packetClientPresent.mode) {
                    case PreferredMode.DISABLED:
                    case PreferredMode.SNEAK_ACTIVE:
                    case PreferredMode.SNEAK_INACTIVE:
                        Minecraft.getMinecraft().thePlayer.addChatMessage(new TextComponentTranslation("command.veinminerc.set.disabled"));
                        break;
                    case PreferredMode.PRESSED:
                        Minecraft.getMinecraft().thePlayer.addChatMessage(new TextComponentTranslation("command.veinminerc.set.pressed"));
                        break;
                    case PreferredMode.RELEASED:
                        Minecraft.getMinecraft().thePlayer.addChatMessage(new TextComponentTranslation("command.veinminerc.set.released"));
                        break;
                }
            }
            else if (context.side == Side.SERVER) { // Server Side
                EntityPlayerMP player = context.getServerHandler().playerEntity;
                UUID playerName = player.getUniqueID();

                MinerServer minerServer = VeinMiner.instance.minerServer;
                minerServer.addClientPlayer(playerName);
                switch(packetClientPresent.mode) {
                    case PreferredMode.DISABLED:
                    case PreferredMode.PRESSED:
                    case PreferredMode.RELEASED:
                        minerServer.setPlayerStatus(playerName, PlayerStatus.INACTIVE);
                        player.addChatMessage(new TextComponentTranslation("mod.veinminer.preferredmode.auto"));
                        break;
                    case PreferredMode.SNEAK_ACTIVE:
                        minerServer.setPlayerStatus(playerName, PlayerStatus.SNEAK_ACTIVE);
                        player.addChatMessage(new TextComponentTranslation("mod.veinminer.preferredmode.sneak"));
                        break;
                    case PreferredMode.SNEAK_INACTIVE:
                        minerServer.setPlayerStatus(playerName, PlayerStatus.SNEAK_INACTIVE);
                        player.addChatMessage(new TextComponentTranslation("mod.veinminer.preferredmode.nosneak"));
                        break;

                }
            }
        }
    }
} public class PacketClientPresent implements IMessage {
    public short mode;

    @SuppressWarnings("UnusedDeclaration")
    public PacketClientPresent() {
        mode = 0;
    }

    public PacketClientPresent(int clientMode) {
        switch(clientMode) {
            case PreferredMode.PRESSED:
                mode = 1;
                break;
            case PreferredMode.RELEASED:
                mode = 2;
                break;
            case PreferredMode.SNEAK_ACTIVE:
                mode = 3;
                break;
            case PreferredMode.SNEAK_INACTIVE:
                mode = 4;
                break;
            default:
                mode = 0;
        }
    }

    @Override
    public void toBytes(ByteBuf buffer) {
        buffer.writeShort(mode);
    }

    @Override
    public void fromBytes(ByteBuf buffer) {
        mode = buffer.readShort();
    }

    public static class Handler extends GenericHandler<PacketClientPresent> {
        @Override
        public void processMessage(PacketClientPresent packetClientPresent, MessageContext context) {
            EntityPlayerMP player = context.getServerHandler().playerEntity;
            MinerLogger.debug("Received a PacketClientPresent");
            UUID playerName = player.getUniqueID();

            MinerServer minerServer = VeinMiner.instance.minerServer;
            minerServer.addClientPlayer(playerName);
            switch (packetClientPresent.mode) {
                case 3:
                    minerServer.setPlayerStatus(playerName, PlayerStatus.SNEAK_ACTIVE);
                    player.addChatMessage(new TextComponentTranslation("mod.veinminer.preferredmode.sneak"));
                    break;
                case 4:
                    minerServer.setPlayerStatus(playerName, PlayerStatus.SNEAK_INACTIVE);
                    player.addChatMessage(new TextComponentTranslation("mod.veinminer.preferredmode.nosneak"));
                    break;
                case 1:
                case 2:
                    player.addChatMessage(new TextComponentTranslation("mod.veinminer.preferredmode.auto"));
                default:
                    minerServer.setPlayerStatus(playerName, PlayerStatus.INACTIVE);
            }

            VeinMiner.instance.networkWrapper.sendTo(new PacketChangeMode(packetClientPresent.mode), player);
        }
    }
} public class PacketMinerActivate implements IMessage {
    public boolean keyActive;

    @SuppressWarnings("UnusedDeclaration")
    public PacketMinerActivate() {}

    public PacketMinerActivate(boolean keyActive) {
        this.keyActive = keyActive;
    }

    @Override
    public void toBytes(ByteBuf buffer) {
        buffer.writeBoolean(keyActive);
    }

    @Override
    public void fromBytes(ByteBuf buffer) {
        keyActive = buffer.readBoolean();
    }

    public static class Handler extends GenericHandler<PacketMinerActivate> {
        @Override
        public void processMessage(PacketMinerActivate packetMinerActivate, MessageContext context) {
            EntityPlayerMP player = context.getServerHandler().playerEntity;
            UUID playerName = player.getUniqueID();

            PlayerStatus status = VeinMiner.instance.minerServer.getPlayerStatus(playerName);
            if (packetMinerActivate.keyActive) {
                if (status == PlayerStatus.INACTIVE) {
                    VeinMiner.instance.minerServer.setPlayerStatus(playerName, PlayerStatus.ACTIVE);
                }
            } else {
                if (status == PlayerStatus.ACTIVE) {
                    VeinMiner.instance.minerServer.setPlayerStatus(playerName, PlayerStatus.INACTIVE);
                }
            }
        }
    }
} public class PacketPingClient implements IMessage {

    public PacketPingClient() {}

    @Override
    public void fromBytes(ByteBuf buf) { }

    @Override
    public void toBytes(ByteBuf buf) { }

    public static class Handler extends GenericHandler<PacketPingClient> {
        @Override
        public void processMessage(PacketPingClient message, MessageContext context) {
            PacketClientPresent p = new PacketClientPresent(VeinMiner.instance.configurationSettings.getPreferredMode());
            VeinMiner.instance.networkWrapper.sendToServer(p);
            VeinMiner.proxy.resetKeybindPacketCount();
        }
    }
} @SuppressWarnings("UnusedDeclaration")
public class CommonProxy {
    private PlayerServerEvent playerServerEvent;
    private EntityDropHook entityDropHook;

    public void registerClientEvents () { }

    public void registerCommonEvents () {
        playerServerEvent = new PlayerServerEvent();
        entityDropHook = new EntityDropHook();
    }

    public void setMinerServer(MinerServer server) {
        playerServerEvent.setServer(server);
        entityDropHook.setServer(server);
    }

    public void resetKeybindPacketCount() { }

    public void registerPostinitCommands() { }
} public class MinerCommand extends CommandBase {
    private MinerServer minerServer = null;

    public static final int COMMAND_MODE = 0;
    public static final int COMMAND_BLOCKLIST = 1;
    public static final int COMMAND_TOOLLIST = 2;
    public static final int COMMAND_BLOCKLIMIT = 3;
    public static final int COMMAND_RANGE = 4;
    public static final int COMMAND_PER_TICK = 5;
    public static final int COMMAND_SAVE = 6;
    public static final int COMMAND_RELOAD = 7;
    public static final int COMMAND_HELP = 8;
    private static final String[] commands = new String[]{"mode", "blocklist", "toollist", "blocklimit", "radius", "per_tick", "saveconfig", "reloadconfig", "help"};
    private static final String[] modes = new String[] {"auto", "sneak", "no_sneak"};

    public MinerCommand(MinerServer minerServerInstance) {
        minerServer = minerServerInstance;
    }

    @Override
    public String getCommandName() {
        return "veinminer";
    }

    public int getRequiredPermissionLevel()
    {
        return 0;
    }

    public boolean checkPermission(MinecraftServer server, ICommandSender sender)
    {
        return true;
    }

    @Override
    public void execute(MinecraftServer server, ICommandSender icommandsender, String[] astring) throws CommandException {
        ICustomCommandSender senderPlayer;
        if(icommandsender instanceof EntityPlayerMP) {
            senderPlayer = new CommandSenderPlayer(minerServer, (EntityPlayerMP)icommandsender);
        }
        else if(icommandsender instanceof DedicatedServer) {
            senderPlayer = new CommandSenderServer((DedicatedServer)icommandsender);
        }
        else {
            String message = I18n.translateToLocal("command.veinminer.cannotuse");
            throw new CommandException(message);
        }

        if(astring.length > 0) {
            if(astring[0].equals(commands[COMMAND_MODE])) {
                runCommandMode(senderPlayer, astring);
            }
            else if(astring[0].equals(commands[COMMAND_BLOCKLIST])) {
                needAdmin(senderPlayer);
                runCommandBlocklist(senderPlayer, astring);
            }
            else if(astring[0].equals(commands[COMMAND_TOOLLIST])) {
                needAdmin(senderPlayer);
                runCommandToollist(senderPlayer, astring);
            }
            else if(astring[0].equals(commands[COMMAND_BLOCKLIMIT])) {
                needAdmin(senderPlayer);
                runCommandBlocklimit(senderPlayer, astring);
            }
            else if(astring[0].equals(commands[COMMAND_RANGE])) {
                needAdmin(senderPlayer);
                runCommandRange(senderPlayer, astring);
            }
            else if(astring[0].equals(commands[COMMAND_PER_TICK])) {
                needAdmin(senderPlayer);
                runCommandPerTick(senderPlayer, astring);
            }
            else if(astring[0].equals(commands[COMMAND_SAVE])) {
                needAdmin(senderPlayer);
                runCommandSave(senderPlayer);
            }
            else if(astring[0].equals(commands[COMMAND_RELOAD])) {
                needAdmin(senderPlayer);
                runCommandReload(senderPlayer);
            }
            else if(astring[0].equals(commands[COMMAND_HELP])) {
                runCommandHelp(senderPlayer, astring);
            }
            else
            {
                showUsageError("command.veinminer");
            }
        }
        else
        {
            showUsageError("command.veinminer");
        }
    }

    private void sendProperChatToPlayer(ICommandSender player, String incomingMessage, Object... params) {
        ITextComponent message;
        if(minerServer.playerHasClient(player.getCommandSenderEntity().getPersistentID())) {
            message = new TextComponentTranslation(incomingMessage, params);
        }
        else {
            String rawMessage = I18n.translateToLocal(incomingMessage);
            message = new TextComponentString(String.format(rawMessage, params));
        }
        player.addChatMessage(message);
    }

    private void showUsageError(String errorKey) throws WrongUsageException {
        String message = I18n.translateToLocal(errorKey);
        throw new WrongUsageException(message);
    }

    private void showUsageError(String errorKey, Object... params) throws WrongUsageException {
        String message = I18n.translateToLocalFormatted(errorKey, params);
        throw new WrongUsageException(message);
    }

    private void needAdmin(ICustomCommandSender sender) throws CommandException {
        if(sender instanceof CommandSenderPlayer) {
            CommandSenderPlayer player = (CommandSenderPlayer) sender;
            MinecraftServer server = player.getPlayer().mcServer;
            if (server.isDedicatedServer() && !player.getPlayer().canCommandSenderUseCommand(server.getOpPermissionLevel(), "veinminer.admin")) {
                boolean playerNoClient = !minerServer.playerHasClient(player.getPlayer().getUniqueID());
                String message = "command.veinminer.permissionDenied";
                if (playerNoClient) {
                    message = I18n.translateToFallback(message);
                }
                throw new CommandException(message);
            }
        }
    }

    private void commandAction(String[] commandString, String commandName) throws WrongUsageException {
        if (commandString.length < 3 || (!"add".equals(commandString[2]) && !"remove".equals(commandString[2]))) {
            showUsageError("command.veinminer." + commandName + ".actionerror", commandString[1]);
        }
    }

    private void runCommandMode(ICustomCommandSender sender, String[] astring) throws CommandException {
        if(sender instanceof CommandSenderPlayer) {
            CommandSenderPlayer senderPlayer = ((CommandSenderPlayer) sender);
            UUID player = senderPlayer.getPlayer().getPersistentID();

            if(astring.length == 1) {
                showUsageError("command.veinminer.enable");
            }
            else if(minerServer.playerHasClient(player)) {
                showUsageError("command.veinminer.hasclient");
            }
            else if(astring[1].equals(modes[0])) {
                minerServer.setPlayerStatus(player, PlayerStatus.INACTIVE);
                senderPlayer.sendProperChat("command.veinminer.set.auto");
            }
            else if(astring[1].equals(modes[1])) {
                minerServer.setPlayerStatus(player, PlayerStatus.SNEAK_ACTIVE);
                senderPlayer.sendProperChat("command.veinminer.set.sneak");
            }
            else if(astring[1].equals(modes[2])) {
                minerServer.setPlayerStatus(player, PlayerStatus.SNEAK_INACTIVE);
                senderPlayer.sendProperChat("command.veinminer.set.nosneak");
            }
        }
        else if(sender instanceof CommandSenderServer) {
            String message = sender.localise("command.veinminer.permissionDenied");
            throw new CommandException(message);
        }
    }

    private void runCommandBlocklist(ICustomCommandSender senderPlayer, String[] astring) throws WrongUsageException {
        ConfigurationSettings configSettings = minerServer.getConfigurationSettings();

        ConfigurationSettings settings = minerServer.getConfigurationSettings();
        Set<String> toolsSet = settings.getToolTypeNames();
        String toolsSlashed = Joiner.on("/").join(toolsSet);

        if(astring.length == 1) {
            showUsageError("command.veinminer.blocklist", toolsSlashed);
        }

        String tool;
        if(toolsSet.contains(astring[1])) {
            tool = astring[1];
        }
        else {
            showUsageError("command.veinminer.blocklist", toolsSlashed);
            return;
        }

        String toolString = settings.getToolTypeName(tool);

        commandAction(astring, "blocklist");
        String action = astring[2];

        if(astring.length < 4) {
            showUsageError("command.veinminer.blocklist.itemerror", toolString, action);
        }

        int metadata = -1;
        if(astring.length >= 5) {
            try {
                metadata = Integer.parseInt(astring[4]);
            }
            catch (NumberFormatException ignored) {}
        }

        BlockID blockID = new BlockID(astring[3], metadata);
        if(blockID.name.isEmpty()) {
            // String is not in proper format
            showUsageError("command.veinminer.blocklist.itemerror", toolString, action);
        }

        if("add".equals(action)) {
            configSettings.addBlockToWhitelist(tool, blockID);
            senderPlayer.sendProperChat("command.veinminer.blocklist.add", blockID.name, blockID.metadata, toolString);
        }
        else if("remove".equals(action)) {
            configSettings.removeBlockFromWhitelist(tool, blockID);
            senderPlayer.sendProperChat("command.veinminer.blocklist.remove", blockID.name, blockID.metadata, toolString);
        }
    }

    private void runCommandToollist(ICustomCommandSender senderPlayer, String[] astring) throws WrongUsageException {
        ConfigurationSettings configSettings = minerServer.getConfigurationSettings();

        ConfigurationSettings settings = minerServer.getConfigurationSettings();
        Set<String> toolsSet = settings.getToolTypeNames();
        String toolsSlashed = Joiner.on("/").join(toolsSet);

        if(astring.length == 1) {
            showUsageError("command.veinminer.toollist", toolsSlashed);
        }

        String tool;
        if(toolsSet.contains(astring[1])) {
            tool = astring[1];
        }
        else {
            showUsageError("command.veinminer.toollist", toolsSlashed);
            return;
        }

        String toolString = settings.getToolTypeName(tool);

        commandAction(astring, "toollist");
        String action = astring[2];

        if(astring.length < 4) {
            showUsageError("command.veinminer.toollist.itemerror", toolString, action);
        }

        String toolId = astring[3];

        if(toolId.isEmpty()) {
            // String is not in proper format
            showUsageError("command.veinminer.toollist.itemerror", toolString, action);
        }

        if("add".equals(action)) {
            configSettings.addTool(tool, toolId);
            senderPlayer.sendProperChat("command.veinminer.toollist.add", toolId, toolString);
        }
        else if("remove".equals(action)) {
            configSettings.removeTool(tool, toolId);
            senderPlayer.sendProperChat("command.veinminer.toollist.remove", toolId, toolString);
        }
    }

    private void runCommandBlocklimit(ICustomCommandSender senderPlayer, String[] astring) throws WrongUsageException {
        if(astring.length == 1) {
            showUsageError("command.veinminer.blocklimit");
        }

        int newBlockPerTick = 0;
        try {
            newBlockPerTick = Integer.parseInt(astring[1]);
        }
        catch (NumberFormatException e) {
            showUsageError("command.veinminer.blocklimit");
        }

        minerServer.getConfigurationSettings().setBlockLimit(newBlockPerTick);

        int actualBlockPerTick = minerServer.getConfigurationSettings().getBlockLimit();
        senderPlayer.sendProperChat("command.veinminer.blocklimit.set", actualBlockPerTick);
    }

    private void runCommandRange(ICustomCommandSender senderPlayer, String[] astring) throws WrongUsageException {
        if(astring.length == 1) {
            showUsageError("command.veinminer.range");
        }

        int newRange = 0;
        try {
            newRange = Integer.parseInt(astring[1]);
        }
        catch (NumberFormatException e) {
            showUsageError("command.veinminer.range");
        }

        minerServer.getConfigurationSettings().setRadiusLimit(newRange);

        int actualRange = minerServer.getConfigurationSettings().getRadiusLimit();
        senderPlayer.sendProperChat("command.veinminer.range.set", actualRange);
    }

    private void runCommandPerTick(ICustomCommandSender senderPlayer, String[] astring) throws WrongUsageException {
        if(astring.length == 1) {
            showUsageError("command.veinminer.pertick");
        }

        int newRate = 0;
        try {
            newRate = Integer.parseInt(astring[1]);
        }
        catch (NumberFormatException e) {
            showUsageError("command.veinminer.pertick");
        }

        minerServer.getConfigurationSettings().setBlocksPerTick(newRate);

        int actualRate = minerServer.getConfigurationSettings().getBlocksPerTick();
        senderPlayer.sendProperChat("command.veinminer.pertick.set", actualRate);
    }

    private void runCommandSave(ICustomCommandSender senderPlayer) {
        minerServer.getConfigurationSettings().saveConfigs();
        senderPlayer.sendProperChat("command.veinminer.saveconfig");
    }

    private void runCommandReload(ICustomCommandSender senderPlayer) {
        minerServer.getConfigurationSettings().reloadConfigFile();
        senderPlayer.sendProperChat("command.veinminer.loadconfig");
    }

    private void runCommandHelp(ICustomCommandSender senderPlayer, String[] astring) {
        if(astring.length > 1) {
            if(astring[1].equals(commands[COMMAND_MODE])) {
                senderPlayer.sendProperChat("command.veinminer.help.enable1");
                senderPlayer.sendProperChat("command.veinminer.help.enable2");
                senderPlayer.sendProperChat("command.veinminer.help.enable3");
                senderPlayer.sendProperChat("command.veinminer.help.enable4");
                //sendProperChatToPlayer(senderPlayer, "command.veinminer.help.enable5");
            }
        }
        else {
            senderPlayer.sendProperChat("command.veinminer.help1");
            senderPlayer.sendProperChat("command.veinminer.help2");
            senderPlayer.sendProperChat("command.veinminer.help3");
            senderPlayer.sendProperChat("command.veinminer.help4");
            senderPlayer.sendProperChat("command.veinminer.help5");
            senderPlayer.sendProperChat("command.veinminer.help6");
            senderPlayer.sendProperChat("command.veinminer.help7");
            senderPlayer.sendProperChat("command.veinminer.help8");
            senderPlayer.sendProperChat("command.veinminer.help9");
        }
    }

    @Override
    @SuppressWarnings("UnusedDeclaration")
    public List<String> getTabCompletionOptions(MinecraftServer server, ICommandSender par1ICommandSender, String[] arguments, BlockPos pos) {
        switch (arguments.length) {
            case 1:
                return getListOfStringsMatchingLastWord(arguments, commands);
            case 2:
                if(arguments[0].equals(commands[COMMAND_MODE])) {
                    return getListOfStringsMatchingLastWord(arguments, modes);
                }
                else if(arguments[0].equals(commands[COMMAND_BLOCKLIST]) || arguments[0].equals(commands[COMMAND_TOOLLIST])) {
                    Set<String> toolsSet = minerServer.getConfigurationSettings().getToolTypeNames();
                    String[] tools = new String[] {};
                    tools = toolsSet.toArray(tools);
                    Arrays.sort(tools);

                    return getListOfStringsMatchingLastWord(arguments, tools);
                }
                else if(arguments[0].equals(commands[COMMAND_TOOLLIST])) {
                    return getListOfStringsMatchingLastWord(arguments, commands);
                }
            case 3:
                if(arguments[0].equals(commands[COMMAND_BLOCKLIST]) || arguments[0].equals(commands[COMMAND_TOOLLIST])) {
                    String[] actions = { "add", "remove" };

                    return getListOfStringsMatchingLastWord(arguments, actions);
                }
        }
        return null;
    }

    @Override
    public String getCommandUsage(ICommandSender par1ICommandSender) {
        return I18n.translateToLocal("command.veinminer");
    }

    @SuppressWarnings("UnusedDeclaration")
    public int compareTo(MinerCommand par1ICommand)
    {
        return this.getCommandName().compareTo(par1ICommand.getCommandName());
    }
} public class MinerServer {

    private final Set<MinerInstance> minerInstances;
    private ConcurrentHashMap<EntityPlayerMP, MinerInstance> playerMinerInstances;
    private HashSet<UUID> clientPlayers;
    private ConcurrentHashMap<UUID, PlayerStatus> players;
    private ConfigurationSettings settings;

    public MinerServer(ConfigurationValues configValues) {
        minerInstances = Collections.synchronizedSet(new HashSet<MinerInstance>());
        playerMinerInstances = new ConcurrentHashMap<EntityPlayerMP, MinerInstance>();
        clientPlayers = new HashSet<UUID>();
        players = new ConcurrentHashMap<UUID, PlayerStatus>();
        settings = new ConfigurationSettings(configValues);
    }

    public void setPlayerStatus(UUID player, PlayerStatus status) {
            players.put(player, status);
    }

    public PlayerStatus getPlayerStatus(UUID player) {
        if(players.containsKey(player)) {
            return players.get(player);
        }
        else {
            return PlayerStatus.INACTIVE;
        }
    }

    public void addEntity(Entity entity) {
        int eX = (int) Math.floor(entity.posX);
        int eY = (int) Math.floor(entity.posY);
        int eZ = (int) Math.floor(entity.posZ);
        Point p = new Point(eX, eY, eZ);

        if(!EntityItem.class.isInstance(entity)) {
            return;
        }
        EntityItem entityItem = (EntityItem) entity;

        synchronized (minerInstances) {
            for (MinerInstance minerInstance : minerInstances) {
                if (minerInstance.isRegistered(p)) {
                    minerInstance.addDrop(entityItem);
                }
            }
        }
    }

    public boolean awaitingDrop(Point p) {
        synchronized (minerInstances) {
            for (MinerInstance minerInstance : minerInstances) {
                if (minerInstance.isRegistered(p)) {
                    return true;
                }
            }
        }
        return false;
    }

    public boolean pointIsBlacklisted(Point point) {
        synchronized (minerInstances) {
            for (MinerInstance minerInstance : minerInstances) {
                if (minerInstance.pointIsBlacklisted(point)) {
                    return true;
                }
            }
        }
        return false;
    }

    public void removeFromBlacklist(Point point) {
        synchronized (minerInstances) {
            for (MinerInstance minerInstance : minerInstances) {
                if (minerInstance.pointIsBlacklisted(point)) {
                    minerInstance.removeFromBlacklist(point);
                }
            }
        }
    }

    public void addInstance(MinerInstance ins) {
        synchronized (minerInstances) {
            minerInstances.add(ins);
        }
        playerMinerInstances.put(ins.getPlayer(), ins);
    }

    public MinerInstance getInstance(EntityPlayer playerMP) {
        if(playerMinerInstances.containsKey(playerMP)) {
            return playerMinerInstances.get(playerMP);
        }
        return null;
    }

    public void removeInstance(MinerInstance ins) {
        synchronized (minerInstances) {
            minerInstances.remove(ins);
        }
        if(playerMinerInstances.containsKey(ins.getPlayer())) {
            playerMinerInstances.remove(ins.getPlayer());
        }
    }

    public ConfigurationSettings getConfigurationSettings() {
        return settings;
    }

    public boolean playerHasClient(UUID playerName) {
        return clientPlayers.contains(playerName);
    }

    public void addClientPlayer(UUID playerName) {
        clientPlayers.add(playerName);
        setPlayerStatus(playerName, PlayerStatus.INACTIVE);
    }

    public void removeClientPlayer(UUID playerName) {
        clientPlayers.remove(playerName);
    }
} public class BlockID implements Comparable<BlockID>
{
    public String name;
    public int metadata;
    public IBlockState state = null;
    
    public BlockID(String fullDescription) {
        int preMeta = -1;
        Pattern p = Pattern.compile("([^/]+)(?:/([-]?\\d{1,2}))?");
        Matcher m = p.matcher(fullDescription);

        if(m.matches()) {
            name = m.group(1);
            if(m.group(2) != null) {
                try {
                    preMeta = Integer.parseInt(m.group(2));
                }
                catch (NumberFormatException e) {
                    preMeta = -1;
                }
            }
        }
        else {
            name = "";
        }
        metadata = preMeta >= -1 ? preMeta : -1;
    }

    public BlockID(String name, int meta) {
        this.name = name;
        this.metadata =  meta < -1 || meta == OreDictionary.WILDCARD_VALUE ? -1 : meta;
    }

    @Deprecated
    public BlockID(World world, BlockPos position) {
        this(world.getBlockState(position));
    }

    public BlockID(IBlockState state) {
        this(Block.blockRegistry.getNameForObject(state.getBlock()).toString(), state.getBlock().getMetaFromState(state));
        this.state = state;
    }

    @Override
    public boolean equals(Object obj)
    {
        if (this == obj)
            return true;
        
        if (!(obj instanceof BlockID))
            return false;

        BlockID o = (BlockID) obj;
        return name.equals(o.name) && metadata == o.metadata;
    }

    /**
     * Compare the objects using the metadata value of -1 to be a wildcard that matches all.
     * @param obj The object to compare to this one
     * @return Whether this object and obj are equal, using the wildcard value.
     */
    public boolean wildcardEquals(Object obj)
    {
        if (this == obj)
            return true;

        if (!(obj instanceof BlockID))
            return false;

        BlockID o = (BlockID) obj;
        if (o.metadata == -1 || metadata == -1)
            return this.equals(obj);
        else
            return name.equals(o.name) && metadata == o.metadata;
    }
    
    @Override
    public int hashCode()
    {
        return (this.name.hashCode() << 6) + this.metadata;
    }
    
    @Override
    public String toString()
    {
        return (metadata == -1 ? name + "" : name + "/" + metadata);
    }

    @Override
    public int compareTo(BlockID blockID) {
        if(name != null && !name.equals(blockID.name)) {
            int result = name.compareTo(blockID.name);
            if(result > 0) return 1;
            else if (result < 0) return -1;
            return 0;
        }
        else if(metadata < blockID.metadata) {
            return -1;
        }
        else if(metadata > blockID.metadata) {
            return 1;
        }
        else {
            return 0;
        }
    }
} public class PreferredMode {
    public static final int DISABLED = 0;
    public static final int PRESSED = 1;
    public static final int RELEASED = 2;
    public static final int SNEAK_ACTIVE = 3;
    public static final int SNEAK_INACTIVE = 4;

    public static int length() {
        return 5;
    }
}
import net.minecraft.block.Block;
import net.minecraft.command.ServerCommandManager;
import net.minecraft.item.Item;
import net.minecraft.item.ItemBlock;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.server.MinecraftServer;
import net.minecraftforge.fml.common.FMLCommonHandler;
import net.minecraftforge.fml.common.FMLLog;
import net.minecraftforge.fml.common.Loader;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.Mod.EventHandler;
import net.minecraftforge.fml.common.ModContainer;
import net.minecraftforge.fml.common.SidedProxy;
import net.minecraftforge.fml.common.event.FMLInitializationEvent;
import net.minecraftforge.fml.common.event.FMLInterModComms;
import net.minecraftforge.fml.common.event.FMLPostInitializationEvent;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
import net.minecraftforge.fml.common.event.FMLServerStartedEvent;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.network.NetworkCheckHandler;
import net.minecraftforge.fml.common.network.NetworkRegistry;
import net.minecraftforge.fml.common.network.simpleimpl.SimpleNetworkWrapper;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.oredict.OreDictionary;
import org.apache.logging.log4j.Logger;
import portablejim.veinminer.configuration.ConfigurationSettings;
import portablejim.veinminer.configuration.ConfigurationValues;
import portablejim.veinminer.configuration.ToolType;
import portablejim.veinminer.core.CoreEvents;
import portablejim.veinminer.lib.MinerLogger;
import portablejim.veinminer.lib.ModInfo;
import portablejim.veinminer.network.PacketChangeMode;
import portablejim.veinminer.network.PacketClientPresent;
import portablejim.veinminer.network.PacketMinerActivate;
import portablejim.veinminer.network.PacketPingClient;
import portablejim.veinminer.proxy.CommonProxy;
import portablejim.veinminer.server.MinerCommand;
import portablejim.veinminer.server.MinerServer;
import portablejim.veinminer.util.BlockID;
import portablejim.veinminer.util.PreferredMode;
import java.io.File;
import java.util.List;
import java.util.Map;
import java.util.Set;
/* This file is part of VeinMiner.
 *
 *    VeinMiner is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU Lesser General Public License as
 *    published by the Free Software Foundation, either version 3 of
 *     the License, or (at your option) any later version.
 *
 *    VeinMiner is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Lesser General Public License for more details.
 *
 *    You should have received a copy of the GNU Lesser General Public
 *    License along with VeinMiner.
 *    If not, see <http://www.gnu.org/licenses/>.
 */

package portablejim.veinminer;



/**
 * This class is the main mod class for VeinMiner. It is loaded as a mod
 * through ForgeModLoader.
 */

@Mod(modid = ModInfo.MODID, acceptedMinecraftVersions = "[1.9,1.11)",
        canBeDeactivated = true, guiFactory = "portablejim.veinminer.configuration.client.ConfigGuiFactory")
public class VeinMiner {

    @Mod.Instance(ModInfo.MODID)
    public static VeinMiner instance;

    @SidedProxy(clientSide = ModInfo.PROXY_CLIENT_CLASS, serverSide = ModInfo.PROXY_SERVER_CLASS)
    public static CommonProxy proxy;

    public SimpleNetworkWrapper networkWrapper;

    ConfigurationValues configurationValues;
    public ConfigurationSettings configurationSettings;

    public MinerServer minerServer = null;
    public int currentMode = PreferredMode.DISABLED;

    public Logger logger;

    @NetworkCheckHandler
    public boolean checkClientModVersion(Map<String, String> mods, Side side) {
        FMLLog.fine("Check Version");
        /*
         * Accept vanilla clients and servers.
         * Vanilla server: No check packet will come.
         * Vanilla client: Won't respond to ping packets. But it will work if forge works I think.
         */
        if(mods.size() == 0) {
            return true;
        }
        else {
            if(mods.containsKey(ModInfo.MODID)) {
                String clientVersion = mods.get(ModInfo.MODID);
                // Connect with matching versions or if one side is a dev build.
                String ourVersionString = Loader.instance().getIndexedModList().get(ModInfo.MODID).getVersion();
                if(ourVersionString.equals(clientVersion) || ourVersionString.startsWith("${version}") || clientVersion.startsWith("${version}")) {
                    return true;
                }
                int clientMajor = 0, clientMinor = 0, major = 0, minor = 0;
                String[] splitVersion = clientVersion.split("\\.");
                if (splitVersion.length >= 2) {
                    clientMajor = Integer.parseInt(splitVersion[0]);
                    clientMinor = Integer.parseInt(splitVersion[1]);
                }
                String[] splitOurVersion = ourVersionString.split("\\.");
                if(splitOurVersion.length >= 2) {
                    major = Integer.parseInt(splitOurVersion[0]);
                    minor = Integer.parseInt(splitOurVersion[1]);
                }

                // After version 1, don't check minor versions.
                return !(major != clientMajor || (major == 0 && minor != clientMinor) || (major == 0 && minor == 0));
            }
        }
        if(side == Side.CLIENT) {
            // Client side checks.
            if(!mods.containsKey(ModInfo.MODID)) {
                return true;
            }
        }
        if(side == Side.SERVER) {
            // Client side checks.
            if(!mods.containsKey(ModInfo.MODID)) {
                return true;
            }
        }
        return true;
    }

    @SuppressWarnings("UnusedDeclaration")
    @EventHandler
    public void preInit(FMLPreInitializationEvent event) {
        logger = event.getModLog();

        setupNetworking();

        File modDir = new File(event.getModConfigurationDirectory(), "veinminer");
        if(!modDir.exists()) {
            //noinspection ResultOfMethodCallIgnored
            modDir.mkdir();
        }

        configurationValues = new ConfigurationValues(new File(modDir, "general.cfg"), new File(modDir, "tools-and-blocks.json"));
        configurationValues.loadConfigFile();
        configurationSettings = new ConfigurationSettings(configurationValues);
        proxy.registerClientEvents();
        proxy.registerCommonEvents();
        MinecraftForge.EVENT_BUS.register(this);
        MinecraftForge.EVENT_BUS.register(new CoreEvents());
    }

    public void setupNetworking() {
        networkWrapper = NetworkRegistry.INSTANCE.newSimpleChannel(ModInfo.CHANNEL);
        networkWrapper.registerMessage(PacketPingClient.Handler.class, PacketPingClient.class, 0, Side.CLIENT);
        networkWrapper.registerMessage(PacketClientPresent.Handler.class, PacketClientPresent.class, 1, Side.SERVER);