package codechicken.nei;

import codechicken.core.*;
import codechicken.lib.config.ConfigFile;
import codechicken.lib.config.ConfigTag;
import codechicken.lib.config.ConfigTagParent;
import codechicken.nei.api.*;
import codechicken.nei.config.*;
import codechicken.nei.recipe.RecipeInfo;
import codechicken.obfuscator.ObfuscationRun;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.gui.inventory.GuiContainer;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.world.World;
import net.minecraft.world.storage.SaveFormatComparator;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.lwjgl.input.Keyboard;

import java.io.File;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map.Entry;

public class NEIClientConfig
{
    private static boolean configLoaded;
    private static boolean enabledOverride;

    public static Logger logger = LogManager.getLogger("NotEnoughItems");
    public static File configDir = new File(CommonUtils.getMinecraftDir(), "config/NEI/");
    public static ConfigSet global = new ConfigSet(
            new File("saves/NEI/client.dat"),
            new ConfigFile(new File(configDir, "client.cfg")));
    public static ConfigSet world;

    public static ItemStack creativeInv[];
    private static boolean statesSaved[] = new boolean[7];

    public static boolean hasSMPCounterpart;
    public static HashSet<String> permissableActions = new HashSet<String>();
    public static HashSet<String> disabledActions = new HashSet<String>();
    public static HashSet<String> enabledActions = new HashSet<String>();

    public static ItemStackSet bannedBlocks = new ItemStackSet();

    static {
        if (global.config.getTag("checkUpdates").getBooleanValue(true))
            CCUpdateChecker.updateCheck("NotEnoughItems");
        linkOptionList();
        setDefaults();
    }

    private static void setDefaults() {
        ConfigTagParent tag = global.config;
        tag.setComment("Main configuration of NEI.\nMost of these options can be changed ingame.\nDeleting any element will restore it to it's default value");

        tag.getTag("command").useBraces().setComment("Change these options if you have a different mod installed on the server that handles the commands differently, Eg. Bukkit Essentials");
        tag.setNewLineMode(1);

        tag.getTag("inventory.widgetsenabled").getBooleanValue(true);
        API.addOption(new OptionToggleButton("inventory.widgetsenabled"));

        tag.getTag("inventory.hidden").getBooleanValue(false);
        tag.getTag("inventory.cheatmode").getIntValue(2);
        tag.getTag("inventory.lockmode").setComment("For those who can't help themselves.\nSet this to a mode and you will be unable to change it ingame").getIntValue(-1);
        API.addOption(new OptionCycled("inventory.cheatmode", 3)
        {
            @Override
            public boolean optionValid(int index) {
                return getLockedMode() == -1 || getLockedMode() == index && NEIInfo.isValidMode(index);
            }
        });
        checkCheatMode();

        tag.getTag("inventory.utilities").setDefaultValue("delete, magnet");
        API.addOption(new OptionUtilities("inventory.utilities"));

        tag.getTag("inventory.gamemodes").setDefaultValue("creative, creative+, adventure");
        API.addOption(new OptionGamemodes("inventory.gamemodes"));

        tag.getTag("inventory.layoutstyle").getIntValue(0);
        API.addOption(new OptionCycled("inventory.layoutstyle", 0)
        {
            @Override
            public String getPrefix() {
                return translateN(name);
            }

            @Override
            public String getButtonText() {
                return NEIClientUtils.translate("layoutstyle." +
                        LayoutManager.getLayoutStyle(renderTag().getIntValue()).getName());
            }

            @Override
            public boolean cycle() {
                LinkedList<Integer> list = new LinkedList<Integer>();
                for (Entry<Integer, LayoutStyle> entry : LayoutManager.layoutStyles.entrySet())
                    list.add(entry.getKey());

                Collections.sort(list);

                int nextLayout = renderTag().getIntValue();
                if (nextLayout == list.getLast())//loop list
                    nextLayout = -1;
                for (Integer i : list) {
                    if (i > nextLayout) {
                        nextLayout = i;
                        break;
                    }
                }

                getTag().setIntValue(nextLayout);
                return true;
            }
        });

        ItemSorter.initConfig(tag);

        tag.getTag("inventory.itemIDs").getIntValue(1);
        API.addOption(new OptionCycled("inventory.itemIDs", 3, true));

        tag.getTag("inventory.searchmode").getIntValue(1);
        API.addOption(new OptionCycled("inventory.searchmode", 3, true));

        tag.getTag("world.highlight_tips").getBooleanValue(false);
        tag.getTag("world.highlight_tips.x").getIntValue(5000);
        tag.getTag("world.highlight_tips.y").getIntValue(100);
        API.addOption(new OptionOpenGui("world.highlight_tips", GuiHighlightTips.class));

        tag.getTag("inventory.profileRecipes").getBooleanValue(false);
        API.addOption(new OptionToggleButton("inventory.profileRecipes", true));

        tag.getTag("command.creative").setDefaultValue("/gamemode {0} {1}");
        API.addOption(new OptionTextField("command.creative"));
        tag.getTag("command.item").setDefaultValue("/give {0} {1} {2} {3} {4}");
        API.addOption(new OptionTextField("command.item"));
        tag.getTag("command.time").setDefaultValue("/time set {0}");
        API.addOption(new OptionTextField("command.time"));
        tag.getTag("command.rain").setDefaultValue("/toggledownfall");
        API.addOption(new OptionTextField("command.rain"));
        tag.getTag("command.heal").setDefaultValue("");
        API.addOption(new OptionTextField("command.heal"));

        setDefaultKeyBindings();
    }

    private static void linkOptionList() {
        OptionList.setOptionList(new OptionList("nei.options")
        {
            @Override
            public ConfigSet globalConfigSet() {
                return global;
            }

            @Override
            public ConfigSet worldConfigSet() {
                return world;
            }

            @Override
            public OptionList configBase() {
                return this;
            }

            @Override
            public GuiOptionList getGui(GuiScreen parent, OptionList list, boolean world) {
                return new GuiNEIOptionList(parent, list, world);
            }
        });
    }

    private static void setDefaultKeyBindings() {
        API.addKeyBind("gui.recipe", Keyboard.KEY_R);
        API.addKeyBind("gui.usage", Keyboard.KEY_U);
        API.addKeyBind("gui.back", Keyboard.KEY_BACK);
        API.addKeyBind("gui.enchant", Keyboard.KEY_X);
        API.addKeyBind("gui.potion", Keyboard.KEY_P);
        API.addKeyBind("gui.prev", Keyboard.KEY_PRIOR);
        API.addKeyBind("gui.next", Keyboard.KEY_NEXT);
        API.addKeyBind("gui.hide", Keyboard.KEY_O);
        API.addKeyBind("gui.search", Keyboard.KEY_F);
        API.addKeyBind("world.chunkoverlay", Keyboard.KEY_F9);
        API.addKeyBind("world.moboverlay", Keyboard.KEY_F7);
        API.addKeyBind("world.highlight_tips", Keyboard.KEY_NUMPAD0);
        API.addKeyBind("world.dawn", 0);
        API.addKeyBind("world.noon", 0);
        API.addKeyBind("world.dusk", 0);
        API.addKeyBind("world.midnight", 0);
        API.addKeyBind("world.rain", 0);
        API.addKeyBind("world.heal", 0);
        API.addKeyBind("world.creative", 0);
    }

    public static OptionList getOptionList() {
        return OptionList.getOptionList("nei.options");
    }

    public static void loadWorld(String saveName) {
        setInternalEnabled(true);
        logger.debug("Loading "+(Minecraft.getMinecraft().isSingleplayer() ? "Local" : "Remote")+" World");
        bootNEI(ClientUtils.getWorld());

        File saveDir = new File(CommonUtils.getMinecraftDir(), "saves/NEI/" + saveName);
        boolean newWorld = !saveDir.exists();
        if (newWorld)
            saveDir.mkdirs();

        world = new ConfigSet(new File(saveDir, "NEI.dat"), new ConfigFile(new File(saveDir, "NEI.cfg")));
        onWorldLoad(newWorld);
    }

    private static void onWorldLoad(boolean newWorld) {
        world.config.setComment("World based configuration of NEI.\nMost of these options can be changed ingame.\nDeleting any element will restore it to it's default value");

        setWorldDefaults();
        creativeInv = new ItemStack[54];
        LayoutManager.searchField.setText(getSearchExpression());
        LayoutManager.quantity.setText(Integer.toString(getItemQuantity()));
        SubsetWidget.loadHidden();

        if (newWorld && Minecraft.getMinecraft().isSingleplayer())
            world.config.getTag("inventory.cheatmode").setIntValue(Minecraft.getMinecraft().playerController.isInCreativeMode() ? 2 : 0);

        NEIInfo.load(ClientUtils.getWorld());
    }

    private static void setWorldDefaults() {
        NBTTagCompound nbt = world.nbt;
        if (!nbt.hasKey("search")) nbt.setString("search", "");
        if (!nbt.hasKey("quantity")) nbt.setInteger("quantity", 0);
        if (!nbt.hasKey("validateenchantments")) nbt.setBoolean("validateenchantments", false);

        world.saveNBT();
    }

    public static int getKeyBinding(String string) {
        return getSetting("keys." + string).getIntValue();
    }

    public static void setDefaultKeyBinding(String string, int key) {
        getSetting("keys." + string).getIntValue(key);
    }

    public static void bootNEI(World world) {
        if (configLoaded)
            return;

        loadStates();
        //ItemVisibilityHash.loadStates();
        //vishash = new ItemVisibilityHash();
        ItemInfo.load(world);
        GuiInfo.load();
        RecipeInfo.load();
        LayoutManager.load();
        NEIController.load();

        configLoaded = true;

        ClassDiscoverer classDiscoverer = new ClassDiscoverer(new IStringMatcher()
        {
            public boolean matches(String test) {
                return test.startsWith("NEI") && test.endsWith("Config.class");
            }
        }, IConfigureNEI.class);

        classDiscoverer.findClasses();

        for (Class<?> clazz : classDiscoverer.classes) {
            try {
                IConfigureNEI config = (IConfigureNEI) clazz.newInstance();
                config.loadConfig();
                NEIModContainer.plugins.add(config);
                logger.debug("Loaded " + clazz.getName());
            } catch (Exception e) {
                logger.error("Failed to Load " + clazz.getName(), e);
            }
        }

        ItemSorter.loadConfig();
    }

    public static void loadStates() {
        for (int state = 0; state < 7; state++)
            statesSaved[state] = !global.nbt.getCompoundTag("save" + state).hasNoTags();
    }

    public static boolean isWorldSpecific(String setting) {
        if(world == null) return false;
        ConfigTag tag = world.config.getTag(setting, false);
        return tag != null && tag.value != null;
    }

    public static boolean isStateSaved(int i) {
        return statesSaved[i];
    }

    public static ConfigTag getSetting(String s) {
        return isWorldSpecific(s) ? world.config.getTag(s) : global.config.getTag(s);
    }

    public static boolean getBooleanSetting(String s) {
        return getSetting(s).getBooleanValue();
    }

    public static boolean isHidden() {
        return !enabledOverride || getBooleanSetting("inventory.hidden");
    }

    public static boolean isEnabled() {
        return enabledOverride && getBooleanSetting("inventory.widgetsenabled");
    }

    public static void setEnabled(boolean flag) {
        getSetting("inventory.widgetsenabled").setBooleanValue(flag);
    }

    public static int getItemQuantity() {
        return world.nbt.getInteger("quantity");
    }

    public static int getCheatMode() {
        return getIntSetting("inventory.cheatmode");
    }

    private static void checkCheatMode() {
        if (getLockedMode() != -1)
            setIntSetting("inventory.cheatmode", getLockedMode());
    }

    public static int getLockedMode() {
        return getIntSetting("inventory.lockmode");
    }

    public static int getLayoutStyle() {
        return getIntSetting("inventory.layoutstyle");
    }

    public static String getStringSetting(String s) {
        return getSetting(s).getValue();
    }

    public static boolean showIDs() {
        int i = getIntSetting("inventory.itemIDs");
        return i == 2 || (i == 1 && isEnabled() && !isHidden());
    }

    public static void toggleBooleanSetting(String setting) {
        ConfigTag tag = getSetting(setting);
        tag.setBooleanValue(!tag.getBooleanValue());
    }

    public static void cycleSetting(String setting, int max) {
        ConfigTag tag = getSetting(setting);
        tag.setIntValue((tag.getIntValue() + 1) % max);
    }

    public static int getIntSetting(String setting) {
        return getSetting(setting).getIntValue();
    }

    public static void setIntSetting(String setting, int val) {
        getSetting(setting).setIntValue(val);
    }

    public static String getSearchExpression() {
        return world.nbt.getString("search");
    }

    public static void setSearchExpression(String expression) {
        world.nbt.setString("search", expression);
        world.saveNBT();
    }

    public static boolean getMagnetMode() {
        return enabledActions.contains("magnet");
    }

    public static boolean invCreativeMode() {
        return enabledActions.contains("creative+") && canPerformAction("creative+");
    }

    public static boolean areDamageVariantsShown() {
        return hasSMPCounterPart() || getSetting("command.item").getValue().contains("{3}");
    }

    public static void clearState(int state) {
        statesSaved[state] = false;
        global.nbt.setTag("save" + state, new NBTTagCompound());
        global.saveNBT();
    }

    public static void loadState(int state) {
        if (!statesSaved[state])
            return;

        NBTTagCompound statesave = global.nbt.getCompoundTag("save" + state);
        GuiContainer currentContainer = NEIClientUtils.getGuiContainer();
        LinkedList<TaggedInventoryArea> saveAreas = new LinkedList<TaggedInventoryArea>();
        saveAreas.add(new TaggedInventoryArea(Minecraft.getMinecraft().thePlayer.inventory));

        for (INEIGuiHandler handler : GuiInfo.guiHandlers) {
            List<TaggedInventoryArea> areaList = handler.getInventoryAreas(currentContainer);
            if (areaList != null)
                saveAreas.addAll(areaList);
        }

        for (TaggedInventoryArea area : saveAreas) {
            if (!statesave.hasKey(area.tagName))
                continue;

            for (int slot : area.slots) {
                NEIClientUtils.setSlotContents(slot, null, area.isContainer());
            }

            NBTTagList areaTag = statesave.getTagList(area.tagName, 10);
            for (int i = 0; i < areaTag.tagCount(); i++) {
                NBTTagCompound stacksave = areaTag.getCompoundTagAt(i);
                int slot = stacksave.getByte("Slot") & 0xFF;
                if (!area.slots.contains(slot))
                    continue;

                NEIClientUtils.setSlotContents(slot, ItemStack.loadItemStackFromNBT(stacksave), area.isContainer());
            }
        }
    }

    public static void saveState(int state) {
        NBTTagCompound statesave = global.nbt.getCompoundTag("save" + state);
        GuiContainer currentContainer = NEIClientUtils.getGuiContainer();
        LinkedList<TaggedInventoryArea> saveAreas = new LinkedList<TaggedInventoryArea>();
        saveAreas.add(new TaggedInventoryArea(Minecraft.getMinecraft().thePlayer.inventory));

        for (INEIGuiHandler handler : GuiInfo.guiHandlers) {
            List<TaggedInventoryArea> areaList = handler.getInventoryAreas(currentContainer);
            if (areaList != null)
                saveAreas.addAll(areaList);
        }

        for (TaggedInventoryArea area : saveAreas) {
            NBTTagList areaTag = new NBTTagList();

            for (int i : area.slots) {
                ItemStack stack = area.getStackInSlot(i);
                if (stack == null)
                    continue;
                NBTTagCompound stacksave = new NBTTagCompound();
                stacksave.setByte("Slot", (byte) i);
                stack.writeToNBT(stacksave);
                areaTag.appendTag(stacksave);
            }
            statesave.setTag(area.tagName, areaTag);
        }

        global.nbt.setTag("save" + state, statesave);
        global.saveNBT();
        statesSaved[state] = true;
    }

    public static boolean hasSMPCounterPart() {
        return hasSMPCounterpart;
    }

    public static void setHasSMPCounterPart(boolean flag) {
        hasSMPCounterpart = flag;
        permissableActions.clear();
        bannedBlocks.clear();
        disabledActions.clear();
        enabledActions.clear();
    }

    public static boolean canCheatItem(ItemStack stack) {
        return canPerformAction("item") && !bannedBlocks.contains(stack);
    }

    public static boolean canPerformAction(String name) {
        if (!isEnabled())
            return false;

        if (!modePermitsAction(name))
            return false;

        String base = NEIActions.base(name);
        if (hasSMPCounterpart)
            return permissableActions.contains(base);

        if (NEIActions.smpRequired(name))
            return false;

        String cmd = getStringSetting("command." + base);
        if (cmd == null || !cmd.startsWith("/"))
            return false;

        return true;
    }

    private static boolean modePermitsAction(String name) {
        if (getCheatMode() == 0) return false;
        if (getCheatMode() == 2) return true;

        String[] actions = getStringArrSetting("inventory.utilities");
        for (String action : actions)
            if (action.equalsIgnoreCase(name))
                return true;

        return false;
    }

    public static String[] getStringArrSetting(String s) {
        return getStringSetting(s).replace(" ", "").split(",");
    }

    public static void setInternalEnabled(boolean b) {
        enabledOverride = b;
    }

    public static void reloadSaves() {
        File saveDir = new File(CommonUtils.getMinecraftDir(), "saves/NEI/local");
        if (!saveDir.exists())
            return;

        List<SaveFormatComparator> saves;
        try {
            saves = Minecraft.getMinecraft().getSaveLoader().getSaveList();
        } catch (Exception e) {
            logger.error("Error loading saves", e);
            return;
        }
        HashSet<String> saveFileNames = new HashSet<String>();
        for (SaveFormatComparator save : saves)
            saveFileNames.add(save.getFileName());

        for (File file : saveDir.listFiles())
            if (file.isDirectory() && !saveFileNames.contains(file.getName()))
                ObfuscationRun.deleteDir(file, true);
    }
}