/*
 * Forge Mod Loader
 * Copyright (c) 2012-2014 cpw.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Lesser Public License v2.1
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 *
 * Contributors (this class):
 *     bspkrs - implementation
 */

package net.minecraftforge.fml.client.config;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiListExtended;
import net.minecraft.client.gui.GuiTextField;
import net.minecraft.client.resources.I18n;
import net.minecraft.util.EnumChatFormatting;
import net.minecraftforge.fml.client.config.GuiConfigEntries.ArrayEntry;
import net.minecraftforge.fml.common.FMLLog;

import org.lwjgl.input.Keyboard;

import static net.minecraftforge.fml.client.config.GuiUtils.INVALID;
import static net.minecraftforge.fml.client.config.GuiUtils.VALID;

/**
 * This class implements the scrolling list functionality of the GuiEditList screen. It also provides all the default controls
 * for editing array-type properties.
 *
 * @author bspkrs
 */
@SuppressWarnings("rawtypes")
public class GuiEditArrayEntries extends GuiListExtended
{
    protected GuiEditArray owningGui;
    public Minecraft mc;
    public IConfigElement configElement;
    public List<IArrayEntry> listEntries;
    public boolean isDefault;
    public boolean isChanged;
    public boolean canAddMoreEntries;
    public final int controlWidth;
    public final Object[] beforeValues;
    public Object[] currentValues;

    public GuiEditArrayEntries(GuiEditArray parent, Minecraft mc, IConfigElement configElement, Object[] beforeValues, Object[] currentValues)
    {
        super(mc, parent.width, parent.height, parent.titleLine2 != null ? (parent.titleLine3 != null ? 43 : 33) : 23, parent.height - 32, 20);
        this.owningGui = parent;
        this.mc = mc;
        this.configElement = configElement;
        this.beforeValues = beforeValues;
        this.currentValues = currentValues;
        this.setShowSelectionBox(false);
        this.isChanged = !Arrays.deepEquals(beforeValues, currentValues);
        this.isDefault = Arrays.deepEquals(currentValues, configElement.getDefaults());
        this.canAddMoreEntries = !configElement.isListLengthFixed() && (configElement.getMaxListLength() == -1 || currentValues.length < configElement.getMaxListLength());

        listEntries = new ArrayList<IArrayEntry>();

        controlWidth = (parent.width / 2) - (configElement.isListLengthFixed() ? 0 : 48);

        if (configElement.isList() && configElement.getArrayEntryClass() != null)
        {
            Class<? extends IArrayEntry> clazz = configElement.getArrayEntryClass();
            for (Object value : currentValues)
            {
                try
                {
                    listEntries.add(clazz.getConstructor(GuiEditArray.class, GuiEditArrayEntries.class, IConfigElement.class, Object.class)
                            .newInstance(this.owningGui, this, configElement, value.toString()));
                }
                catch (Throwable e)
                {
                    FMLLog.severe("There was a critical error instantiating the custom IGuiEditListEntry for property %s.", configElement.getName());
                    e.printStackTrace();
                }
            }
        }
        else if (configElement.isList() && configElement.getType().equals(ConfigGuiType.BOOLEAN))
            for (Object value : currentValues)
                listEntries.add(new BooleanEntry(this.owningGui, this, configElement, Boolean.valueOf(value.toString())));
        else if (configElement.isList() && configElement.getType().equals(ConfigGuiType.INTEGER))
            for (Object value : currentValues)
                listEntries.add(new IntegerEntry(this.owningGui, this, configElement, Integer.parseInt(value.toString())));
        else if (configElement.isList() && configElement.getType().equals(ConfigGuiType.DOUBLE))
            for (Object value : currentValues)
                listEntries.add(new DoubleEntry(this.owningGui, this, configElement, Double.parseDouble(value.toString())));
        else if (configElement.isList())
            for (Object value : currentValues)
                listEntries.add(new StringEntry(this.owningGui, this, configElement, value.toString()));

        if (!configElement.isListLengthFixed())
            listEntries.add(new BaseEntry(this.owningGui, this, configElement));

    }

    @Override
    protected int getScrollBarX()
    {
        return width - (width / 4);
    }

    /**
     * Gets the width of the list
     */
    /**
     * Gets the width of the list
     */
    @Override
    public int getListWidth()
    {
        return owningGui.width;
    }

    /**
     * Gets the IGuiListEntry object for the given index
     */
    @Override
    public IArrayEntry getListEntry(int index)
    {
        return listEntries.get(index);
    }

    @Override
    protected int getSize()
    {
        return listEntries.size();
    }

    public void addNewEntry(int index)
    {
        if (configElement.isList() && configElement.getType() == ConfigGuiType.BOOLEAN)
            listEntries.add(index, new BooleanEntry(this.owningGui, this, this.configElement, Boolean.valueOf(true)));
        else if (configElement.isList() && configElement.getType() == ConfigGuiType.INTEGER)
            listEntries.add(index, new IntegerEntry(this.owningGui, this, this.configElement, 0));
        else if (configElement.isList() && configElement.getType() == ConfigGuiType.DOUBLE)
            listEntries.add(index, new DoubleEntry(this.owningGui, this, this.configElement, 0.0D));
        else if (configElement.isList())
            listEntries.add(index, new StringEntry(this.owningGui, this, this.configElement, ""));
        this.canAddMoreEntries = !configElement.isListLengthFixed()
                && (configElement.getMaxListLength() == -1 || this.listEntries.size() - 1 < configElement.getMaxListLength());
        keyTyped((char) Keyboard.CHAR_NONE, Keyboard.KEY_END);
    }

    public void removeEntry(int index)
    {
        this.listEntries.remove(index);
        this.canAddMoreEntries = !configElement.isListLengthFixed()
                && (configElement.getMaxListLength() == -1 || this.listEntries.size() - 1 < configElement.getMaxListLength());
        keyTyped((char) Keyboard.CHAR_NONE, Keyboard.KEY_END);
    }

    public boolean isChanged()
    {
        return isChanged;
    }

    public boolean isDefault()
    {
        return isDefault;
    }

    public void recalculateState()
    {
        isDefault = true;
        isChanged = false;

        int listLength = configElement.isListLengthFixed() ? listEntries.size() : listEntries.size() - 1;

        if (listLength != configElement.getDefaults().length)
        {
            isDefault = false;
        }

        if (listLength != beforeValues.length)
        {
            isChanged = true;
        }

        if (isDefault)
            for (int i = 0; i < listLength; i++)
                if (!configElement.getDefaults()[i].equals(listEntries.get(i).getValue()))
                    isDefault = false;

        if (!isChanged)
            for (int i = 0; i < listLength; i++)
                if (!beforeValues[i].equals(listEntries.get(i).getValue()))
                    isChanged = true;
    }

    protected void keyTyped(char eventChar, int eventKey)
    {
        for (IArrayEntry entry : this.listEntries)
            entry.keyTyped(eventChar, eventKey);

        recalculateState();
    }

    protected void updateScreen()
    {
        for (IArrayEntry entry : this.listEntries)
            entry.updateCursorCounter();
    }

    protected void mouseClickedPassThru(int x, int y, int mouseEvent)
    {
        for (IArrayEntry entry : this.listEntries)
            entry.mouseClicked(x, y, mouseEvent);
    }

    protected boolean isListSavable()
    {
        for (IArrayEntry entry : this.listEntries)
            if (!entry.isValueSavable())
                return false;

        return true;
    }

    protected void saveListChanges()
    {
        int listLength = configElement.isListLengthFixed() ? listEntries.size() : listEntries.size() - 1;

        if (owningGui.slotIndex != -1 && owningGui.parentScreen != null
                && owningGui.parentScreen instanceof GuiConfig
                && ((GuiConfig) owningGui.parentScreen).entryList.getListEntry(owningGui.slotIndex) instanceof ArrayEntry)
        {
            ArrayEntry entry = (ArrayEntry) ((GuiConfig) owningGui.parentScreen).entryList.getListEntry(owningGui.slotIndex);

            Object[] ao = new Object[listLength];
            for (int i = 0; i < listLength; i++)
                ao[i] = listEntries.get(i).getValue();

            entry.setListFromChildScreen(ao);
        }
        else
        {
            if (configElement.isList() && configElement.getType() == ConfigGuiType.BOOLEAN)
            {
                Boolean[] abol = new Boolean[listLength];
                for (int i = 0; i < listLength; i++)
                    abol[i] = Boolean.valueOf(listEntries.get(i).getValue().toString());

                configElement.set(abol);
            }
            else if (configElement.isList() && configElement.getType() == ConfigGuiType.INTEGER)
            {
                Integer[] ai = new Integer[listLength];
                for (int i = 0; i < listLength; i++)
                    ai[i] = Integer.valueOf(listEntries.get(i).getValue().toString());

                configElement.set(ai);
            }
            else if (configElement.isList() && configElement.getType() == ConfigGuiType.DOUBLE)
            {
                Double[] ad = new Double[listLength];
                for (int i = 0; i < listLength; i++)
                    ad[i] = Double.valueOf(listEntries.get(i).getValue().toString());

                configElement.set(ad);
            }
            else if (configElement.isList())
            {
                String[] as = new String[listLength];
                for (int i = 0; i < listLength; i++)
                    as[i] = listEntries.get(i).getValue().toString();

                configElement.set(as);
            }
        }
    }

    protected void drawScreenPost(int mouseX, int mouseY, float f)
    {
        for (IArrayEntry entry : this.listEntries)
            entry.drawToolTip(mouseX, mouseY);
    }

    /**
     * IGuiListEntry Inner Classes
     */

    public static class DoubleEntry extends StringEntry
    {
        public DoubleEntry(GuiEditArray owningScreen, GuiEditArrayEntries owningEntryList, IConfigElement configElement, Double value)
        {
            super(owningScreen, owningEntryList, configElement, value);
            this.isValidated = true;
        }

        @Override
        public void keyTyped(char eventChar, int eventKey)
        {
            if (owningScreen.enabled || eventKey == Keyboard.KEY_LEFT || eventKey == Keyboard.KEY_RIGHT
                    || eventKey == Keyboard.KEY_HOME || eventKey == Keyboard.KEY_END)
            {
                String validChars = "0123456789";
                String before = this.textFieldValue.getText();
                if (validChars.contains(String.valueOf(eventChar)) ||
                        (!before.startsWith("-") && this.textFieldValue.getCursorPosition() == 0 && eventChar == '-')
                        || (!before.contains(".") && eventChar == '.')
                        || eventKey == Keyboard.KEY_BACK || eventKey == Keyboard.KEY_DELETE || eventKey == Keyboard.KEY_LEFT || eventKey == Keyboard.KEY_RIGHT
                        || eventKey == Keyboard.KEY_HOME || eventKey == Keyboard.KEY_END)
                    this.textFieldValue.textboxKeyTyped((owningScreen.enabled ? eventChar : Keyboard.CHAR_NONE), eventKey);

                if (!textFieldValue.getText().trim().isEmpty() && !textFieldValue.getText().trim().equals("-"))
                {
                    try
                    {
                        double value = Double.parseDouble(textFieldValue.getText().trim());
                        if (value < Double.valueOf(configElement.getMinValue().toString()) || value > Double.valueOf(configElement.getMaxValue().toString()))
                            this.isValidValue = false;
                        else
                            this.isValidValue = true;
                    }
                    catch (Throwable e)
                    {
                        this.isValidValue = false;
                    }
                }
                else
                    this.isValidValue = false;
            }
        }

        @Override
        public Double getValue()
        {
            try
            {
                return Double.valueOf(this.textFieldValue.getText().trim());
            }
            catch (Throwable e)
            {
                return Double.MAX_VALUE;
            }
        }
    }

    public static class IntegerEntry extends StringEntry
    {
        public IntegerEntry(GuiEditArray owningScreen, GuiEditArrayEntries owningEntryList, IConfigElement configElement, Integer value)
        {
            super(owningScreen, owningEntryList, configElement, value);
            this.isValidated = true;
        }

        @Override
        public void keyTyped(char eventChar, int eventKey)
        {
            if (owningScreen.enabled || eventKey == Keyboard.KEY_LEFT || eventKey == Keyboard.KEY_RIGHT
                    || eventKey == Keyboard.KEY_HOME || eventKey == Keyboard.KEY_END)
            {
                String validChars = "0123456789";
                String before = this.textFieldValue.getText();
                if (validChars.contains(String.valueOf(eventChar))
                        || (!before.startsWith("-") && this.textFieldValue.getCursorPosition() == 0 && eventChar == '-')
                        || eventKey == Keyboard.KEY_BACK || eventKey == Keyboard.KEY_DELETE
                        || eventKey == Keyboard.KEY_LEFT || eventKey == Keyboard.KEY_RIGHT || eventKey == Keyboard.KEY_HOME || eventKey == Keyboard.KEY_END)
                    this.textFieldValue.textboxKeyTyped((owningScreen.enabled ? eventChar : Keyboard.CHAR_NONE), eventKey);

                if (!textFieldValue.getText().trim().isEmpty() && !textFieldValue.getText().trim().equals("-"))
                {
                    try
                    {
                        long value = Long.parseLong(textFieldValue.getText().trim());
                        if (value < Integer.valueOf(configElement.getMinValue().toString()) || value > Integer.valueOf(configElement.getMaxValue().toString()))
                            this.isValidValue = false;
                        else
                            this.isValidValue = true;
                    }
                    catch (Throwable e)
                    {
                        this.isValidValue = false;
                    }
                }
                else
                    this.isValidValue = false;
            }
        }

        @Override
        public Integer getValue()
        {
            try
            {
                return Integer.valueOf(this.textFieldValue.getText().trim());
            }
            catch (Throwable e)
            {
                return Integer.MAX_VALUE;
            }
        }
    }

    public static class StringEntry extends BaseEntry
    {
        protected final GuiTextField textFieldValue;

        public StringEntry(GuiEditArray owningScreen, GuiEditArrayEntries owningEntryList, IConfigElement configElement, Object value)
        {
            super(owningScreen, owningEntryList, configElement);
            this.textFieldValue = new GuiTextField(0, owningEntryList.mc.fontRendererObj, owningEntryList.width / 4 + 1, 0, owningEntryList.controlWidth - 3, 16);
            this.textFieldValue.setMaxStringLength(10000);
            this.textFieldValue.setText(value.toString());
            this.isValidated = configElement.getValidationPattern() != null;

            if (configElement.getValidationPattern() != null)
            {
                if (configElement.getValidationPattern().matcher(this.textFieldValue.getText().trim()).matches())
                    isValidValue = true;
                else
                    isValidValue = false;
            }
        }

        @Override
        public void drawEntry(int slotIndex, int x, int y, int listWidth, int slotHeight, int mouseX, int mouseY, boolean isSelected)
        {
            super.drawEntry(slotIndex, x, y, listWidth, slotHeight, mouseX, mouseY, isSelected);
            if (configElement.isListLengthFixed() || slotIndex != owningEntryList.listEntries.size() - 1)
            {
                this.textFieldValue.setVisible(true);
                this.textFieldValue.yPosition = y + 1;
                this.textFieldValue.drawTextBox();
            }
            else
                this.textFieldValue.setVisible(false);
        }

        @Override
        public void keyTyped(char eventChar, int eventKey)
        {
            if (owningScreen.enabled || eventKey == Keyboard.KEY_LEFT || eventKey == Keyboard.KEY_RIGHT
                    || eventKey == Keyboard.KEY_HOME || eventKey == Keyboard.KEY_END)
            {
                this.textFieldValue.textboxKeyTyped((owningScreen.enabled ? eventChar : Keyboard.CHAR_NONE), eventKey);

                if (configElement.getValidationPattern() != null)
                {
                    if (configElement.getValidationPattern().matcher(this.textFieldValue.getText().trim()).matches())
                        isValidValue = true;
                    else
                        isValidValue = false;
                }
            }
        }

        @Override
        public void updateCursorCounter()
        {
            this.textFieldValue.updateCursorCounter();
        }

        @Override
        public void mouseClicked(int x, int y, int mouseEvent)
        {
            this.textFieldValue.mouseClicked(x, y, mouseEvent);
        }

        @Override
        public Object getValue()
        {
            return this.textFieldValue.getText().trim();
        }

    }

    public static class BooleanEntry extends BaseEntry
    {
        protected final GuiButtonExt btnValue;
        private boolean value;

        public BooleanEntry(GuiEditArray owningScreen, GuiEditArrayEntries owningEntryList, IConfigElement configElement, boolean value)
        {
            super(owningScreen, owningEntryList, configElement);
            this.value = value;
            this.btnValue = new GuiButtonExt(0, 0, 0, owningEntryList.controlWidth, 18, I18n.format(String.valueOf(value)));
            this.btnValue.enabled = owningScreen.enabled;
            this.isValidated = false;
        }

        @Override
        public void drawEntry(int slotIndex, int x, int y, int listWidth, int slotHeight, int mouseX, int mouseY, boolean isSelected)
        {
            super.drawEntry(slotIndex, x, y, listWidth, slotHeight, mouseX, mouseY, isSelected);
            this.btnValue.xPosition = listWidth / 4;
            this.btnValue.yPosition = y;

            String trans = I18n.format(String.valueOf(value));
            if (!trans.equals(String.valueOf(value)))
                this.btnValue.displayString = trans;
            else
                this.btnValue.displayString = String.valueOf(value);
            btnValue.packedFGColour = value ? GuiUtils.getColorCode('2', true) : GuiUtils.getColorCode('4', true);

            this.btnValue.drawButton(owningEntryList.mc, mouseX, mouseY);
        }

        /**
         * Returns true if the mouse has been pressed on this control.
         */
        @Override
        public boolean mousePressed(int index, int x, int y, int mouseEvent, int relativeX, int relativeY)
        {
            if (this.btnValue.mousePressed(owningEntryList.mc, x, y))
            {
                btnValue.playPressSound(owningEntryList.mc.getSoundHandler());
                value = !value;
                owningEntryList.recalculateState();
                return true;
            }

            return super.mousePressed(index, x, y, mouseEvent, relativeX, relativeY);
        }

        /**
         * Fired when the mouse button is released. Arguments: index, x, y, mouseEvent, relativeX, relativeY
         */
        @Override
        public void mouseReleased(int index, int x, int y, int mouseEvent, int relativeX, int relativeY)
        {
            this.btnValue.mouseReleased(x, y);
            super.mouseReleased(index, x, y, mouseEvent, relativeX, relativeY);
        }

        @Override
        public Object getValue()
        {
            return Boolean.valueOf(value);
        }
    }

    public static class BaseEntry implements IArrayEntry
    {
        protected final GuiEditArray owningScreen;
        protected final GuiEditArrayEntries owningEntryList;
        protected final IConfigElement configElement;
        protected final GuiButtonExt btnAddNewEntryAbove;
        private final HoverChecker addNewEntryAboveHoverChecker;
        protected final GuiButtonExt btnRemoveEntry;
        private final HoverChecker removeEntryHoverChecker;
        private List addNewToolTip, removeToolTip;
        protected boolean isValidValue = true;
        protected boolean isValidated = false;

        @SuppressWarnings({ "unchecked" })
        public BaseEntry(GuiEditArray owningScreen, GuiEditArrayEntries owningEntryList, IConfigElement configElement)
        {
            this.owningScreen = owningScreen;
            this.owningEntryList = owningEntryList;
            this.configElement = configElement;
            this.btnAddNewEntryAbove = new GuiButtonExt(0, 0, 0, 18, 18, "+");
            this.btnAddNewEntryAbove.packedFGColour = GuiUtils.getColorCode('2', true);
            this.btnAddNewEntryAbove.enabled = owningScreen.enabled;
            this.btnRemoveEntry = new GuiButtonExt(0, 0, 0, 18, 18, "x");
            this.btnRemoveEntry.packedFGColour = GuiUtils.getColorCode('c', true);
            this.btnRemoveEntry.enabled = owningScreen.enabled;
            this.addNewEntryAboveHoverChecker = new HoverChecker(this.btnAddNewEntryAbove, 800);
            this.removeEntryHoverChecker = new HoverChecker(this.btnRemoveEntry, 800);
            this.addNewToolTip = new ArrayList();
            this.removeToolTip = new ArrayList();
            addNewToolTip.add(I18n.format("fml.configgui.tooltip.addNewEntryAbove"));
            removeToolTip.add(I18n.format("fml.configgui.tooltip.removeEntry"));
        }

        @Override
        public void drawEntry(int slotIndex, int x, int y, int listWidth, int slotHeight, int mouseX, int mouseY, boolean isSelected)
        {
            if (this.getValue() != null && this.isValidated)
                owningEntryList.mc.fontRendererObj.drawString(
                        isValidValue ? EnumChatFormatting.GREEN + VALID : EnumChatFormatting.RED + INVALID,
                        listWidth / 4 - owningEntryList.mc.fontRendererObj.getStringWidth(VALID) - 2,
                        y + slotHeight / 2 - owningEntryList.mc.fontRendererObj.FONT_HEIGHT / 2,
                        16777215);

            int half = listWidth / 2;
            if (owningEntryList.canAddMoreEntries)
            {
                this.btnAddNewEntryAbove.visible = true;
                this.btnAddNewEntryAbove.xPosition = half + ((half / 2) - 44);
                this.btnAddNewEntryAbove.yPosition = y;
                this.btnAddNewEntryAbove.drawButton(owningEntryList.mc, mouseX, mouseY);
            }
            else
                this.btnAddNewEntryAbove.visible = false;

            if (!configElement.isListLengthFixed() && slotIndex != owningEntryList.listEntries.size() - 1)
            {
                this.btnRemoveEntry.visible = true;
                this.btnRemoveEntry.xPosition = half + ((half / 2) - 22);
                this.btnRemoveEntry.yPosition = y;
                this.btnRemoveEntry.drawButton(owningEntryList.mc, mouseX, mouseY);
            }
            else
                this.btnRemoveEntry.visible = false;
        }

        @Override
        public void drawToolTip(int mouseX, int mouseY)
        {
            boolean canHover = mouseY < owningEntryList.bottom && mouseY > owningEntryList.top;
            if (this.btnAddNewEntryAbove.visible && this.addNewEntryAboveHoverChecker.checkHover(mouseX, mouseY, canHover))
                owningScreen.drawToolTip(this.addNewToolTip, mouseX, mouseY);
            if (this.btnRemoveEntry.visible && this.removeEntryHoverChecker.checkHover(mouseX, mouseY, canHover))
                owningScreen.drawToolTip(this.removeToolTip, mouseX, mouseY);
        }

        /**
         * Returns true if the mouse has been pressed on this control.
         */
        @Override
        public boolean mousePressed(int index, int x, int y, int mouseEvent, int relativeX, int relativeY)
        {
            if (this.btnAddNewEntryAbove.mousePressed(owningEntryList.mc, x, y))
            {
                btnAddNewEntryAbove.playPressSound(owningEntryList.mc.getSoundHandler());
                owningEntryList.addNewEntry(index);
                owningEntryList.recalculateState();
                return true;
            }
            else if (this.btnRemoveEntry.mousePressed(owningEntryList.mc, x, y))
            {
                btnRemoveEntry.playPressSound(owningEntryList.mc.getSoundHandler());
                owningEntryList.removeEntry(index);
                owningEntryList.recalculateState();
                return true;
            }

            return false;
        }

        /**
         * Fired when the mouse button is released. Arguments: index, x, y, mouseEvent, relativeX, relativeY
         */
        @Override
        public void mouseReleased(int index, int x, int y, int mouseEvent, int relativeX, int relativeY)
        {
            this.btnAddNewEntryAbove.mouseReleased(x, y);
            this.btnRemoveEntry.mouseReleased(x, y);
        }

        @Override
        public void keyTyped(char eventChar, int eventKey)
        {}

        @Override
        public void updateCursorCounter()
        {}

        @Override
        public void mouseClicked(int x, int y, int mouseEvent)
        {}

        @Override
        public boolean isValueSavable()
        {
            return isValidValue;
        }

        @Override
        public Object getValue()
        {
            return null;
        }

        @Override
        public void setSelected(int p_178011_1_, int p_178011_2_, int p_178011_3_){}
    }

    public static interface IArrayEntry extends GuiListExtended.IGuiListEntry
    {
        public void keyTyped(char eventChar, int eventKey);

        public void updateCursorCounter();

        public void mouseClicked(int x, int y, int mouseEvent);

        public void drawToolTip(int mouseX, int mouseY);

        public boolean isValueSavable();

        public Object getValue();
    }
}