/*
 * 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 static net.minecraftforge.fml.client.config.GuiUtils.RESET_CHAR;
import static net.minecraftforge.fml.client.config.GuiUtils.UNDO_CHAR;

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

import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiListExtended;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.gui.GuiTextField;
import net.minecraft.client.resources.I18n;
import net.minecraft.util.EnumChatFormatting;
import net.minecraftforge.fml.common.FMLLog;
import net.minecraftforge.fml.common.Loader;
import net.minecraftforge.fml.common.ModContainer;

import org.lwjgl.input.Keyboard;

/**
 * This class implements the scrolling list functionality of the config GUI screens. It also provides all the default control handlers
 * for the various property types.
 *
 * @author bspkrs
 */
public class GuiConfigEntries extends GuiListExtended
{
    public final GuiConfig owningScreen;
    public final Minecraft mc;
    public List<IConfigEntry> listEntries;
    /**
     * The max width of the label of all IConfigEntry objects.
     */
    public int maxLabelTextWidth  = 0;
    /**
     * The max x boundary of all IConfigEntry objects.
     */
    public int maxEntryRightBound = 0;
    /**
     * The x position where the label should be drawn.
     */
    public int labelX;
    /**
     * The x position where the control should be drawn.
     */
    public int controlX;
    /**
     * The width of the control.
     */
    public int controlWidth;
    /**
     * The minimum x position where the Undo/Default buttons will start
     */
    public int resetX;
    /**
     * The x position of the scroll bar.
     */
    public int scrollBarX;

    public GuiConfigEntries(GuiConfig parent, Minecraft mc)
    {
        super(mc, parent.width, parent.height, parent.titleLine2 != null ? 33 : 23, parent.height - 32, 20);
        this.owningScreen = parent;
        this.setShowSelectionBox(false);
        this.mc = mc;
        this.listEntries = new ArrayList<IConfigEntry>();

        for (IConfigElement configElement : parent.configElements)
        {
            if (configElement != null)
            {
                if (configElement.isProperty() && configElement.showInGui()) // as opposed to being a child category entry
                {
                    int length;

                    // protects against language keys that are not defined in the .lang file
                    if (!I18n.format(configElement.getLanguageKey()).equals(configElement.getLanguageKey()))
                        length = mc.fontRendererObj.getStringWidth(I18n.format(configElement.getLanguageKey()));
                    else
                        length = mc.fontRendererObj.getStringWidth(configElement.getName());

                    if (length > this.maxLabelTextWidth)
                        this.maxLabelTextWidth = length;
                }
            }
        }

        int viewWidth = this.maxLabelTextWidth + 8 + (width / 2);
        labelX = (this.width / 2) - (viewWidth / 2);
        controlX = labelX + maxLabelTextWidth + 8;
        resetX = (this.width / 2) + (viewWidth / 2) - 45;
        controlWidth = resetX - controlX - 5;
        scrollBarX = this.width;

        for (IConfigElement configElement : parent.configElements)
        {
            if (configElement != null && configElement.showInGui())
            {
                if (configElement.getConfigEntryClass() != null)
                    try
                    {
                        this.listEntries.add((IConfigEntry) configElement.getConfigEntryClass()
                                .getConstructor(GuiConfig.class, GuiConfigEntries.class, IConfigElement.class)
                                .newInstance(this.owningScreen, this, configElement));
                    }
                    catch (Throwable e)
                    {
                        FMLLog.severe("There was a critical error instantiating the custom IConfigEntry for config element %s.", configElement.getName());
                        e.printStackTrace();
                    }
                else if (configElement.isProperty())
                {
                    if (configElement.isList())
                        this.listEntries.add(new GuiConfigEntries.ArrayEntry(this.owningScreen, this, configElement));
                    else if (configElement.getType() == ConfigGuiType.BOOLEAN)
                        this.listEntries.add(new GuiConfigEntries.BooleanEntry(this.owningScreen, this, configElement));
                    else if (configElement.getType() == ConfigGuiType.INTEGER)
                        this.listEntries.add(new GuiConfigEntries.IntegerEntry(this.owningScreen, this, configElement));
                    else if (configElement.getType() == ConfigGuiType.DOUBLE)
                        this.listEntries.add(new GuiConfigEntries.DoubleEntry(this.owningScreen, this, configElement));
                    else if (configElement.getType() == ConfigGuiType.COLOR)
                    {
                        if (configElement.getValidValues() != null && configElement.getValidValues().length > 0)
                            this.listEntries.add(new GuiConfigEntries.ChatColorEntry(this.owningScreen, this, configElement));
                        else
                            this.listEntries.add(new GuiConfigEntries.StringEntry(this.owningScreen, this, configElement));
                    }
                    else if (configElement.getType() == ConfigGuiType.MOD_ID)
                    {
                        Map<Object, String> values = new TreeMap<Object, String>();
                        for (ModContainer mod : Loader.instance().getActiveModList())
                            values.put(mod.getModId(), mod.getName());
                        values.put("minecraft", "Minecraft");
                        this.listEntries.add(new SelectValueEntry(this.owningScreen, this, configElement, values));
                    }
                    else if (configElement.getType() == ConfigGuiType.STRING)
                    {
                        if (configElement.getValidValues() != null && configElement.getValidValues().length > 0)
                            this.listEntries.add(new GuiConfigEntries.CycleValueEntry(this.owningScreen, this, configElement));
                        else
                            this.listEntries.add(new GuiConfigEntries.StringEntry(this.owningScreen, this, configElement));
                    }
                }
                else if (configElement.getType() == ConfigGuiType.CONFIG_CATEGORY)
                    this.listEntries.add(new CategoryEntry(this.owningScreen, this, configElement));
            }
        }
    }

    protected void initGui()
    {
        this.width = owningScreen.width;
        this.height = owningScreen.height;

        this.maxLabelTextWidth = 0;
        for (IConfigEntry entry : this.listEntries)
            if (entry.getLabelWidth() > this.maxLabelTextWidth)
                this.maxLabelTextWidth = entry.getLabelWidth();

        this.top = owningScreen.titleLine2 != null ? 33 : 23;
        this.bottom = owningScreen.height - 32;
        this.left = 0;
        this.right = width;
        int viewWidth = this.maxLabelTextWidth + 8 + (width / 2);
        labelX = (this.width / 2) - (viewWidth / 2);
        controlX = labelX + maxLabelTextWidth + 8;
        resetX = (this.width / 2) + (viewWidth / 2) - 45;

        this.maxEntryRightBound = 0;
        for (IConfigEntry entry : this.listEntries)
            if (entry.getEntryRightBound() > this.maxEntryRightBound)
                this.maxEntryRightBound = entry.getEntryRightBound();

        scrollBarX = this.maxEntryRightBound + 5;
        controlWidth = maxEntryRightBound - controlX - 45;
    }

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

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

    @Override
    public int getScrollBarX()
    {
        return scrollBarX;
    }

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

    /**
     * This method is a pass-through for IConfigEntry objects that require keystrokes. Called from the parent GuiConfig screen.
     */
    public void keyTyped(char eventChar, int eventKey)
    {
        for (IConfigEntry entry : this.listEntries)
            entry.keyTyped(eventChar, eventKey);
    }

    /**
     * This method is a pass-through for IConfigEntry objects that contain GuiTextField elements. Called from the parent GuiConfig
     * screen.
     */
    public void updateScreen()
    {
        for (IConfigEntry entry : this.listEntries)
            entry.updateCursorCounter();
    }

    /**
     * This method is a pass-through for IConfigEntry objects that contain GuiTextField elements. Called from the parent GuiConfig
     * screen.
     */
    public void mouseClickedPassThru(int mouseX, int mouseY, int mouseEvent)
    {
        for (IConfigEntry entry : this.listEntries)
            entry.mouseClicked(mouseX, mouseY, mouseEvent);
    }

    /**
     * This method is a pass-through for IConfigEntry objects that need to perform actions when the containing GUI is closed.
     */
    public void onGuiClosed()
    {
        for (IConfigEntry entry : this.listEntries)
            entry.onGuiClosed();
    }

    /**
     * Saves all properties on this screen / child screens. This method returns true if any elements were changed that require
     * a restart for proper handling.
     */
    public boolean saveConfigElements()
    {
        boolean requiresRestart = false;
        for (IConfigEntry entry : this.listEntries)
            if (entry.saveConfigElement())
                requiresRestart = true;

        return requiresRestart;
    }

    /**
     * Returns true if all IConfigEntry objects on this screen are set to default. If includeChildren is true sub-category
     * objects are checked as well.
     */
    public boolean areAllEntriesDefault(boolean includeChildren)
    {
        for (IConfigEntry entry : this.listEntries)
            if ((includeChildren || !(entry instanceof CategoryEntry)) && !entry.isDefault())
                return false;

        return true;
    }

    /**
     * Sets all IConfigEntry objects on this screen to default. If includeChildren is true sub-category objects are set as
     * well.
     */
    public void setAllToDefault(boolean includeChildren)
    {
        for (IConfigEntry entry : this.listEntries)
            if ((includeChildren || !(entry instanceof CategoryEntry)))
                entry.setToDefault();
    }

    /**
     * Returns true if any IConfigEntry objects on this screen are changed. If includeChildren is true sub-category objects
     * are checked as well.
     */
    public boolean hasChangedEntry(boolean includeChildren)
    {
        for (IConfigEntry entry : this.listEntries)
            if ((includeChildren || !(entry instanceof CategoryEntry)) && entry.isChanged())
                return true;

        return false;
    }

    /**
     * Returns true if any IConfigEntry objects on this screen are enabled. If includeChildren is true sub-category objects
     * are checked as well.
     */
    public boolean areAnyEntriesEnabled(boolean includeChildren)
    {
        for (IConfigEntry entry : this.listEntries)
            if ((includeChildren || !(entry instanceof CategoryEntry)) && entry.enabled())
                return true;

        return false;
    }

    /**
     * Reverts changes to all IConfigEntry objects on this screen. If includeChildren is true sub-category objects are
     * reverted as well.
     */
    public void undoAllChanges(boolean includeChildren)
    {
        for (IConfigEntry entry : this.listEntries)
            if ((includeChildren || !(entry instanceof CategoryEntry)))
                entry.undoChanges();
    }

    /**
     * Calls the drawToolTip() method for all IConfigEntry objects on this screen. This is called from the parent GuiConfig screen
     * after drawing all other elements.
     */
    public void drawScreenPost(int mouseX, int mouseY, float partialTicks)
    {
        for (IConfigEntry entry : this.listEntries)
            entry.drawToolTip(mouseX, mouseY);
    }

    /**
     * BooleanPropEntry
     *
     * Provides a GuiButton that toggles between true and false.
     */
    public static class BooleanEntry extends ButtonEntry
    {
        protected final boolean beforeValue;
        protected boolean       currentValue;

        private BooleanEntry(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement configElement)
        {
            super(owningScreen, owningEntryList, configElement);
            this.beforeValue = Boolean.valueOf(configElement.get().toString());
            this.currentValue = beforeValue;
            this.btnValue.enabled = enabled();
            updateValueButtonText();
        }

        @Override
        public void updateValueButtonText()
        {
            this.btnValue.displayString = I18n.format(String.valueOf(currentValue));
            btnValue.packedFGColour = currentValue ? GuiUtils.getColorCode('2', true) : GuiUtils.getColorCode('4', true);
        }

        @Override
        public void valueButtonPressed(int slotIndex)
        {
            if (enabled())
                currentValue = !currentValue;
        }

        @Override
        public boolean isDefault()
        {
            return currentValue == Boolean.valueOf(configElement.getDefault().toString());
        }

        @Override
        public void setToDefault()
        {
            if (enabled())
            {
                currentValue = Boolean.valueOf(configElement.getDefault().toString());
                updateValueButtonText();
            }
        }

        @Override
        public boolean isChanged()
        {
            return currentValue != beforeValue;
        }

        @Override
        public void undoChanges()
        {
            if (enabled())
            {
                currentValue = beforeValue;
                updateValueButtonText();
            }
        }

        @Override
        public boolean saveConfigElement()
        {
            if (enabled() && isChanged())
            {
                configElement.set(currentValue);
                return configElement.requiresMcRestart();
            }
            return false;
        }

        @Override
        public Boolean getCurrentValue()
        {
            return currentValue;
        }

        @Override
        public Boolean[] getCurrentValues()
        {
            return new Boolean[] { getCurrentValue() };
        }
    }

    /**
     * CycleValueEntry
     *
     * Provides a GuiButton that cycles through the prop's validValues array. If the current prop value is not a valid value, the first
     * entry replaces the current value.
     */
    public static class CycleValueEntry extends ButtonEntry
    {
        protected final int beforeIndex;
        protected final int defaultIndex;
        protected int       currentIndex;

        private CycleValueEntry(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement configElement)
        {
            super(owningScreen, owningEntryList, configElement);
            beforeIndex = getIndex(configElement.get().toString());
            defaultIndex = getIndex(configElement.getDefault().toString());
            currentIndex = beforeIndex;
            this.btnValue.enabled = enabled();
            updateValueButtonText();
        }

        private int getIndex(String s)
        {
            for (int i = 0; i < configElement.getValidValues().length; i++)
                if (configElement.getValidValues()[i].equalsIgnoreCase(s))
                {
                    return i;
                }

            return 0;
        }

        @Override
        public void updateValueButtonText()
        {
            this.btnValue.displayString = I18n.format(configElement.getValidValues()[currentIndex]);
        }

        @Override
        public void valueButtonPressed(int slotIndex)
        {
            if (enabled())
            {
                if (++this.currentIndex >= configElement.getValidValues().length)
                    this.currentIndex = 0;

                updateValueButtonText();
            }
        }

        @Override
        public boolean isDefault()
        {
            return currentIndex == defaultIndex;
        }

        @Override
        public void setToDefault()
        {
            if (enabled())
            {
                currentIndex = defaultIndex;
                updateValueButtonText();
            }
        }

        @Override
        public boolean isChanged()
        {
            return currentIndex != beforeIndex;
        }

        @Override
        public void undoChanges()
        {
            if (enabled())
            {
                currentIndex = beforeIndex;
                updateValueButtonText();
            }
        }

        @Override
        public boolean saveConfigElement()
        {
            if (enabled() && isChanged())
            {
                configElement.set(configElement.getValidValues()[currentIndex]);
                return configElement.requiresMcRestart();
            }
            return false;
        }

        @Override
        public String getCurrentValue()
        {
            return configElement.getValidValues()[currentIndex];
        }

        @Override
        public String[] getCurrentValues()
        {
            return new String[] { getCurrentValue() };
        }
    }

    /**
     * ChatColorEntry
     *
     * Provides a GuiButton that cycles through the list of chat color codes.
     */
    public static class ChatColorEntry extends CycleValueEntry
    {
        ChatColorEntry(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement configElement)
        {
            super(owningScreen, owningEntryList, configElement);
            this.btnValue.enabled = enabled();
            updateValueButtonText();
        }

        @Override
        public void drawEntry(int slotIndex, int x, int y, int listWidth, int slotHeight, int mouseX, int mouseY, boolean isSelected)
        {
            this.btnValue.packedFGColour = GuiUtils.getColorCode(this.configElement.getValidValues()[currentIndex].charAt(0), true);
            super.drawEntry(slotIndex, x, y, listWidth, slotHeight, mouseX, mouseY, isSelected);
        }

        @Override
        public void updateValueButtonText()
        {
            this.btnValue.displayString = I18n.format(configElement.getValidValues()[currentIndex]) + " - " + I18n.format("fml.configgui.sampletext");
        }
    }

    /**
     * SelectValueEntry
     *
     * Provides a GuiButton with the current value as the displayString. Accepts a Map of selectable values with the signature <Object,
     * String> where the key is the Object to be selected and the value is the String that will show on the selection list. EG: a map of Mod
     * ID values where the key is the Mod ID and the value is the Mod Name.
     */
    public static class SelectValueEntry extends ButtonEntry
    {
        protected final String        beforeValue;
        protected Object              currentValue;
        protected Map<Object, String> selectableValues;

        public SelectValueEntry(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement configElement, Map<Object, String> selectableValues)
        {
            super(owningScreen, owningEntryList, configElement);
            beforeValue = configElement.get().toString();
            currentValue = configElement.get().toString();
            this.selectableValues = selectableValues;
            updateValueButtonText();
        }

        @Override
        public void updateValueButtonText()
        {
            this.btnValue.displayString = currentValue.toString();
        }

        @Override
        public void valueButtonPressed(int slotIndex)
        {
            mc.displayGuiScreen(new GuiSelectString(this.owningScreen, configElement, slotIndex, selectableValues, currentValue, enabled()));
        }

        public void setValueFromChildScreen(Object newValue)
        {
            if (enabled() && currentValue != null ? !currentValue.equals(newValue) : newValue != null)
            {
                currentValue = newValue;
                updateValueButtonText();
            }
        }

        @Override
        public boolean isDefault()
        {
            if (configElement.getDefault() != null)
                return configElement.getDefault().equals(currentValue);
            else
                return currentValue == null;
        }

        @Override
        public void setToDefault()
        {
            if (enabled())
            {
                this.currentValue = configElement.getDefault().toString();
                updateValueButtonText();
            }
        }

        @Override
        public boolean isChanged()
        {
            if (beforeValue != null)
                return !beforeValue.equals(currentValue);
            else
                return currentValue == null;
        }

        @Override
        public void undoChanges()
        {
            if (enabled())
            {
                currentValue = beforeValue;
                updateValueButtonText();
            }
        }

        @Override
        public boolean saveConfigElement()
        {
            if (enabled() && isChanged())
            {
                this.configElement.set(currentValue);
                return configElement.requiresMcRestart();
            }
            return false;
        }

        @Override
        public String getCurrentValue()
        {
            return this.currentValue.toString();
        }

        @Override
        public String[] getCurrentValues()
        {
            return new String[] { getCurrentValue() };
        }
    }

    /**
     * ArrayEntry
     *
     * Provides a GuiButton with the list contents as the displayString. Clicking the button navigates to a screen where the list can be
     * edited.
     */
    public static class ArrayEntry extends ButtonEntry
    {
        protected final Object[] beforeValues;
        protected Object[]       currentValues;

        public ArrayEntry(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement configElement)
        {
            super(owningScreen, owningEntryList, configElement);
            beforeValues = configElement.getList();
            currentValues = configElement.getList();
            updateValueButtonText();
        }

        @Override
        public void updateValueButtonText()
        {
            this.btnValue.displayString = "";
            for (Object o : currentValues)
                this.btnValue.displayString += ", [" + o + "]";

            this.btnValue.displayString = this.btnValue.displayString.replaceFirst(", ", "");
        }

        @Override
        public void valueButtonPressed(int slotIndex)
        {
            mc.displayGuiScreen(new GuiEditArray(this.owningScreen, configElement, slotIndex, currentValues, enabled()));
        }

        public void setListFromChildScreen(Object[] newList)
        {
            if (enabled() && !Arrays.deepEquals(currentValues, newList))
            {
                currentValues = newList;
                updateValueButtonText();
            }
        }

        @Override
        public boolean isDefault()
        {
            return Arrays.deepEquals(configElement.getDefaults(), currentValues);
        }

        @Override
        public void setToDefault()
        {
            if (enabled())
            {
                this.currentValues = configElement.getDefaults();
                updateValueButtonText();
            }
        }

        @Override
        public boolean isChanged()
        {
            return !Arrays.deepEquals(beforeValues, currentValues);
        }

        @Override
        public void undoChanges()
        {
            if (enabled())
            {
                currentValues = beforeValues;
                updateValueButtonText();
            }
        }

        @Override
        public boolean saveConfigElement()
        {
            if (enabled() && isChanged())
            {
                this.configElement.set(currentValues);
                return configElement.requiresMcRestart();
            }
            return false;
        }

        @Override
        public Object getCurrentValue()
        {
            return this.btnValue.displayString;
        }

        @Override
        public Object[] getCurrentValues()
        {
            return this.currentValues;
        }
    }

    /**
     * NumberSliderEntry
     *
     * Provides a slider for numeric properties.
     */
    public static class NumberSliderEntry extends ButtonEntry
    {
        protected final double beforeValue;

        public NumberSliderEntry(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement configElement)
        {
            super(owningScreen, owningEntryList, configElement, new GuiSlider(0, owningEntryList.controlX, 0, owningEntryList.controlWidth, 18,
                    "", "", Double.valueOf(configElement.getMinValue().toString()), Double.valueOf(configElement.getMaxValue().toString()),
                    Double.valueOf(configElement.get().toString()), configElement.getType() == ConfigGuiType.DOUBLE, true));

            if (configElement.getType() == ConfigGuiType.INTEGER)
                this.beforeValue = Integer.valueOf(configElement.get().toString());
            else
                this.beforeValue = Double.valueOf(configElement.get().toString());
        }

        @Override
        public void updateValueButtonText()
        {
            ((GuiSlider) this.btnValue).updateSlider();
        }

        @Override
        public void valueButtonPressed(int slotIndex) {}

        @Override
        public boolean isDefault()
        {
            if (configElement.getType() == ConfigGuiType.INTEGER)
                return ((GuiSlider) this.btnValue).getValueInt() == Integer.valueOf(configElement.getDefault().toString());
            else
                return ((GuiSlider) this.btnValue).getValue() == Double.valueOf(configElement.getDefault().toString());
        }

        @Override
        public void setToDefault()
        {
            if (this.enabled())
            {
                ((GuiSlider) this.btnValue).setValue(Double.valueOf(configElement.getDefault().toString()));
                ((GuiSlider) this.btnValue).updateSlider();
            }
        }

        @Override
        public boolean isChanged()
        {
            if (configElement.getType() == ConfigGuiType.INTEGER)
                return ((GuiSlider) this.btnValue).getValueInt() != (int) Math.round(beforeValue);
            else
                return ((GuiSlider) this.btnValue).getValue() != beforeValue;
        }

        @Override
        public void undoChanges()
        {
            if (this.enabled())
            {
                ((GuiSlider) this.btnValue).setValue(beforeValue);
                ((GuiSlider) this.btnValue).updateSlider();
            }
        }

        @Override
        public boolean saveConfigElement()
        {
            if (this.enabled() && this.isChanged())
            {
                if (configElement.getType() == ConfigGuiType.INTEGER)
                    configElement.set(((GuiSlider) this.btnValue).getValueInt());
                else
                    configElement.set(((GuiSlider) this.btnValue).getValue());
                return configElement.requiresMcRestart();
            }
            return false;
        }

        @Override
        public Object getCurrentValue()
        {
            if (configElement.getType() == ConfigGuiType.INTEGER)
                return ((GuiSlider) this.btnValue).getValueInt();
            else
                return ((GuiSlider) this.btnValue).getValue();
        }

        @Override
        public Object[] getCurrentValues()
        {
            return new Object[] { getCurrentValue() };
        }
    }

    /**
     * ButtonEntry
     *
     * Provides a basic GuiButton entry to be used as a base for other entries that require a button for the value.
     */
    public static abstract class ButtonEntry extends ListEntryBase
    {
        protected final GuiButtonExt btnValue;

        public ButtonEntry(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement configElement)
        {
            this(owningScreen, owningEntryList, configElement, new GuiButtonExt(0, owningEntryList.controlX, 0, owningEntryList.controlWidth, 18,
                    configElement.get() != null ? I18n.format(String.valueOf(configElement.get())) : ""));
        }

        public ButtonEntry(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement configElement, GuiButtonExt button)
        {
            super(owningScreen, owningEntryList, configElement);
            this.btnValue = button;
        }

        /**
         * Updates the displayString of the value button.
         */
        public abstract void updateValueButtonText();

        /**
         * Called when the value button has been clicked.
         */
        public abstract void valueButtonPressed(int slotIndex);

        @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.width = this.owningEntryList.controlWidth;
            this.btnValue.xPosition = this.owningScreen.entryList.controlX;
            this.btnValue.yPosition = y;
            this.btnValue.enabled = enabled();
            this.btnValue.drawButton(this.mc, mouseX, mouseY);
        }

        /**
         * Returns true if the mouse has been pressed on this control.
         */
        /**
         * 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(this.mc, x, y))
            {
                btnValue.playPressSound(mc.getSoundHandler());
                valueButtonPressed(index);
                updateValueButtonText();
                return true;
            }
            else
                return super.mousePressed(index, x, y, mouseEvent, relativeX, relativeY);
        }

        /**
         * Fired when the mouse button is released. Arguments: 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)
        {
            super.mouseReleased(index, x, y, mouseEvent, relativeX, relativeY);
            this.btnValue.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)
        {}
    }

    /**
     * IntegerEntry
     *
     * Provides a GuiTextField for user input. Input is restricted to ensure the value can be parsed using Integer.parseInteger().
     */
    public static class IntegerEntry extends StringEntry
    {
        protected final int beforeValue;

        public IntegerEntry(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement configElement)
        {
            super(owningScreen, owningEntryList, configElement);
            this.beforeValue = Integer.valueOf(configElement.get().toString());
        }

        @Override
        public void keyTyped(char eventChar, int eventKey)
        {
            if (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((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 boolean isChanged()
        {
            try
            {
                return this.beforeValue != Integer.parseInt(textFieldValue.getText().trim());
            }
            catch (Throwable e)
            {
                return true;
            }
        }

        @Override
        public void undoChanges()
        {
            if (enabled())
                this.textFieldValue.setText(String.valueOf(beforeValue));
        }

        @Override
        public boolean saveConfigElement()
        {
            if (enabled())
            {
                if (isChanged() && this.isValidValue)
                    try
                    {
                        int value = Integer.parseInt(textFieldValue.getText().trim());
                        this.configElement.set(value);
                        return configElement.requiresMcRestart();
                    }
                    catch (Throwable e)
                    {
                        this.configElement.setToDefault();
                    }
                else if (isChanged() && !this.isValidValue)
                    try
                    {
                        int value = Integer.parseInt(textFieldValue.getText().trim());
                        if (value < Integer.valueOf(configElement.getMinValue().toString()))
                            this.configElement.set(configElement.getMinValue());
                        else
                            this.configElement.set(configElement.getMaxValue());

                    }
                    catch (Throwable e)
                    {
                        this.configElement.setToDefault();
                    }

                return configElement.requiresMcRestart() && beforeValue != Integer.parseInt(configElement.get().toString());
            }
            return false;
        }
    }

    /**
     * DoubleEntry
     *
     * Provides a GuiTextField for user input. Input is restricted to ensure the value can be parsed using Double.parseDouble().
     */
    public static class DoubleEntry extends StringEntry
    {
        protected final double beforeValue;

        public DoubleEntry(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement configElement)
        {
            super(owningScreen, owningEntryList, configElement);
            this.beforeValue = Double.valueOf(configElement.get().toString());
        }

        @Override
        public void keyTyped(char eventChar, int eventKey)
        {
            if (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((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 boolean isChanged()
        {
            try
            {
                return this.beforeValue != Double.parseDouble(textFieldValue.getText().trim());
            }
            catch (Throwable e)
            {
                return true;
            }
        }

        @Override
        public void undoChanges()
        {
            if (enabled())
                this.textFieldValue.setText(String.valueOf(beforeValue));
        }

        @Override
        public boolean saveConfigElement()
        {
            if (enabled())
            {
                if (isChanged() && this.isValidValue)
                    try
                    {
                        double value = Double.parseDouble(textFieldValue.getText().trim());
                        this.configElement.set(value);
                        return configElement.requiresMcRestart();
                    }
                    catch (Throwable e)
                    {
                        this.configElement.setToDefault();
                    }
                else if (isChanged() && !this.isValidValue)
                    try
                    {
                        double value = Double.parseDouble(textFieldValue.getText().trim());
                        if (value < Double.valueOf(configElement.getMinValue().toString()))
                            this.configElement.set(configElement.getMinValue());
                        else
                            this.configElement.set(configElement.getMaxValue());
                    }
                    catch (Throwable e)
                    {
                        this.configElement.setToDefault();
                    }

                return configElement.requiresMcRestart() && beforeValue != Double.parseDouble(configElement.get().toString());
            }
            return false;
        }
    }

    /**
     * StringEntry
     *
     * Provides a GuiTextField for user input.
     */
    public static class StringEntry extends ListEntryBase
    {
        protected final GuiTextField textFieldValue;
        protected final String       beforeValue;

        public StringEntry(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement configElement)
        {
            super(owningScreen, owningEntryList, configElement);
            beforeValue = configElement.get().toString();
            this.textFieldValue = new GuiTextField(10, this.mc.fontRendererObj, this.owningEntryList.controlX + 1, 0, this.owningEntryList.controlWidth - 3, 16);
            this.textFieldValue.setMaxStringLength(10000);
            this.textFieldValue.setText(configElement.get().toString());
        }

        @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.textFieldValue.xPosition = this.owningEntryList.controlX + 2;
            this.textFieldValue.yPosition = y + 1;
            this.textFieldValue.width = this.owningEntryList.controlWidth - 4;
            this.textFieldValue.setEnabled(enabled());
            this.textFieldValue.drawTextBox();
        }

        @Override
        public void keyTyped(char eventChar, int eventKey)
        {
            if (enabled() || eventKey == Keyboard.KEY_LEFT || eventKey == Keyboard.KEY_RIGHT || eventKey == Keyboard.KEY_HOME || eventKey == Keyboard.KEY_END)
            {
                this.textFieldValue.textboxKeyTyped((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 boolean isDefault()
        {
            return configElement.getDefault() != null ? configElement.getDefault().toString().equals(this.textFieldValue.getText()) :
                this.textFieldValue.getText().trim().isEmpty();
        }

        @Override
        public void setToDefault()
        {
            if (enabled())
            {
                this.textFieldValue.setText(this.configElement.getDefault().toString());
                keyTyped((char) Keyboard.CHAR_NONE, Keyboard.KEY_HOME);
            }
        }

        @Override
        public boolean isChanged()
        {
            return beforeValue != null ? !this.beforeValue.equals(textFieldValue.getText()) : this.textFieldValue.getText().trim().isEmpty();
        }

        @Override
        public void undoChanges()
        {
            if (enabled())
                this.textFieldValue.setText(beforeValue);
        }

        @Override
        public boolean saveConfigElement()
        {
            if (enabled())
            {
                if (isChanged() && this.isValidValue)
                {
                    this.configElement.set(this.textFieldValue.getText());
                    return configElement.requiresMcRestart();
                }
                else if (isChanged() && !this.isValidValue)
                {
                    this.configElement.setToDefault();
                    return configElement.requiresMcRestart()
                            && beforeValue != null ? beforeValue.equals(configElement.getDefault()) : configElement.getDefault() == null;
                }
            }
            return false;
        }

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

        @Override
        public Object[] getCurrentValues()
        {
            return new Object[] { getCurrentValue() };
        }
    }

    /**
     * CategoryEntry
     *
     * Provides an entry that consists of a GuiButton for navigating to the child category GuiConfig screen.
     */
    public static class CategoryEntry extends ListEntryBase
    {
        protected GuiScreen childScreen;
        protected final GuiButtonExt btnSelectCategory;

        public CategoryEntry(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement configElement)
        {
            super(owningScreen, owningEntryList, configElement);

            this.childScreen = this.buildChildScreen();

            this.btnSelectCategory = new GuiButtonExt(0, 0, 0, 300, 18, I18n.format(name));
            this.tooltipHoverChecker = new HoverChecker(this.btnSelectCategory, 800);

            this.drawLabel = false;
        }

        /**
         * This method is called in the constructor and is used to set the childScreen field.
         */
        protected GuiScreen buildChildScreen()
        {
            return new GuiConfig(this.owningScreen, this.configElement.getChildElements(), this.owningScreen.modID,
                    owningScreen.allRequireWorldRestart || this.configElement.requiresWorldRestart(),
                    owningScreen.allRequireMcRestart || this.configElement.requiresMcRestart(), this.owningScreen.title,
                    ((this.owningScreen.titleLine2 == null ? "" : this.owningScreen.titleLine2) + " > " + this.name));
        }

        @Override
        public void drawEntry(int slotIndex, int x, int y, int listWidth, int slotHeight, int mouseX, int mouseY, boolean isSelected)
        {
            this.btnSelectCategory.xPosition = listWidth / 2 - 150;
            this.btnSelectCategory.yPosition = y;
            this.btnSelectCategory.enabled = enabled();
            this.btnSelectCategory.drawButton(this.mc, mouseX, mouseY);

            super.drawEntry(slotIndex, x, y, listWidth, slotHeight, mouseX, mouseY, isSelected);
        }

        @Override
        public void drawToolTip(int mouseX, int mouseY)
        {
            boolean canHover = mouseY < this.owningScreen.entryList.bottom && mouseY > this.owningScreen.entryList.top;

            if (this.tooltipHoverChecker.checkHover(mouseX, mouseY, canHover))
                this.owningScreen.drawToolTip(toolTip, mouseX, mouseY);

            super.drawToolTip(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.btnSelectCategory.mousePressed(this.mc, x, y))
            {
                btnSelectCategory.playPressSound(mc.getSoundHandler());
                Minecraft.getMinecraft().displayGuiScreen(childScreen);
                return true;
            }
            else
                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.btnSelectCategory.mouseReleased(x, y);
        }

        @Override
        public boolean isDefault()
        {
            if (childScreen instanceof GuiConfig && ((GuiConfig) childScreen).entryList != null)
                return ((GuiConfig) childScreen).entryList.areAllEntriesDefault(true);

            return true;
        }

        @Override
        public void setToDefault()
        {
            if (childScreen instanceof GuiConfig && ((GuiConfig) childScreen).entryList != null)
                ((GuiConfig) childScreen).entryList.setAllToDefault(true);
        }

        @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 saveConfigElement()
        {
            boolean requiresRestart = false;

            if (childScreen instanceof GuiConfig && ((GuiConfig) childScreen).entryList != null)
            {
                requiresRestart = configElement.requiresMcRestart() && ((GuiConfig) childScreen).entryList.hasChangedEntry(true);

                if (((GuiConfig) childScreen).entryList.saveConfigElements())
                    requiresRestart = true;
            }

            return requiresRestart;
        }

        @Override
        public boolean isChanged()
        {
            if (childScreen instanceof GuiConfig && ((GuiConfig) childScreen).entryList != null)
                return ((GuiConfig) childScreen).entryList.hasChangedEntry(true);
            else
                return false;
        }

        @Override
        public void undoChanges()
        {
            if (childScreen instanceof GuiConfig && ((GuiConfig) childScreen).entryList != null)
                ((GuiConfig) childScreen).entryList.undoAllChanges(true);
        }

        @Override
        public boolean enabled()
        {
            return true;
        }

        @Override
        public int getLabelWidth()
        {
            return 0;
        }

        @Override
        public int getEntryRightBound()
        {
            return this.owningEntryList.width / 2 + 155 + 22 + 18;
        }

        @Override
        public String getCurrentValue()
        {
            return "";
        }

        @Override
        public String[] getCurrentValues()
        {
            return new String[] { getCurrentValue() };
        }
    }

    /**
     * ListEntryBase
     *
     * Provides a base entry for others to extend. Handles drawing the prop label (if drawLabel == true) and the Undo/Default buttons.
     */
    @SuppressWarnings("rawtypes")
    public static abstract class ListEntryBase implements IConfigEntry
    {
        protected final GuiConfig owningScreen;
        protected final GuiConfigEntries owningEntryList;
        protected final IConfigElement configElement;
        protected final Minecraft mc;
        protected final String name;
        protected final GuiButtonExt btnUndoChanges;
        protected final GuiButtonExt btnDefault;
        protected List toolTip;
        protected List undoToolTip;
        protected List defaultToolTip;
        protected boolean isValidValue = true;
        protected HoverChecker tooltipHoverChecker;
        protected HoverChecker undoHoverChecker;
        protected HoverChecker defaultHoverChecker;
        protected boolean drawLabel;

        @SuppressWarnings({ "unchecked" })
        public ListEntryBase(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement configElement)
        {
            this.owningScreen = owningScreen;
            this.owningEntryList = owningEntryList;
            this.configElement = configElement;
            this.mc = Minecraft.getMinecraft();
            String trans = I18n.format(configElement.getLanguageKey());
            if (!trans.equals(configElement.getLanguageKey()))
                this.name = trans;
            else
                this.name = configElement.getName();
            this.btnUndoChanges = new GuiButtonExt(0, 0, 0, 18, 18, UNDO_CHAR);
            this.btnDefault = new GuiButtonExt(0, 0, 0, 18, 18, RESET_CHAR);

            this.undoHoverChecker = new HoverChecker(this.btnUndoChanges, 800);
            this.defaultHoverChecker = new HoverChecker(this.btnDefault, 800);
            this.undoToolTip = Arrays.asList(new String[] { I18n.format("fml.configgui.tooltip.undoChanges") });
            this.defaultToolTip = Arrays.asList(new String[] { I18n.format("fml.configgui.tooltip.resetToDefault") });

            this.drawLabel = true;

            String comment;

            comment = I18n.format(configElement.getLanguageKey() + ".tooltip").replace("\\n", "\n");

            if (!comment.equals(configElement.getLanguageKey() + ".tooltip"))
                toolTip = new ArrayList<String>(this.mc.fontRendererObj.listFormattedStringToWidth(
                        EnumChatFormatting.GREEN + name + "\n" + EnumChatFormatting.YELLOW + comment, 300));
            else if (configElement.getComment() != null && !configElement.getComment().trim().isEmpty())
                toolTip = new ArrayList<String>(this.mc.fontRendererObj.listFormattedStringToWidth(
                        EnumChatFormatting.GREEN + name + "\n" + EnumChatFormatting.YELLOW + configElement.getComment(), 300));
            else
                toolTip = new ArrayList<String>(this.mc.fontRendererObj.listFormattedStringToWidth(
                        EnumChatFormatting.GREEN + name + "\n" + EnumChatFormatting.RED + "No tooltip defined.", 300));

            if ((configElement.getType() == ConfigGuiType.INTEGER
                    && (Integer.valueOf(configElement.getMinValue().toString()) != Integer.MIN_VALUE || Integer.valueOf(configElement.getMaxValue().toString()) != Integer.MAX_VALUE))
                    || (configElement.getType() == ConfigGuiType.DOUBLE
                    && (Double.valueOf(configElement.getMinValue().toString()) != -Double.MAX_VALUE || Double.valueOf(configElement.getMaxValue().toString()) != Double.MAX_VALUE)))
                toolTip.addAll(this.mc.fontRendererObj.listFormattedStringToWidth(
                        EnumChatFormatting.AQUA + I18n.format("fml.configgui.tooltip.defaultNumeric", configElement.getMinValue(), configElement.getMaxValue(), configElement.getDefault()), 300));
            else if (configElement.getType() != ConfigGuiType.CONFIG_CATEGORY)
                toolTip.addAll(this.mc.fontRendererObj.listFormattedStringToWidth(EnumChatFormatting.AQUA + I18n.format("fml.configgui.tooltip.default", configElement.getDefault()),300));

            if (configElement.requiresMcRestart() || owningScreen.allRequireMcRestart)
                toolTip.add(EnumChatFormatting.RED + "[" + I18n.format("fml.configgui.gameRestartTitle") + "]");
        }

        @Override
        public void drawEntry(int slotIndex, int x, int y, int listWidth, int slotHeight, int mouseX, int mouseY, boolean isSelected)
        {
            boolean isChanged = isChanged();

            if (drawLabel)
            {
                String label = (!isValidValue ? EnumChatFormatting.RED.toString() :
                        (isChanged ? EnumChatFormatting.WHITE.toString() : EnumChatFormatting.GRAY.toString()))
                        + (isChanged ? EnumChatFormatting.ITALIC.toString() : "") + this.name;
                this.mc.fontRendererObj.drawString(
                        label,
                        this.owningScreen.entryList.labelX,
                        y + slotHeight / 2 - this.mc.fontRendererObj.FONT_HEIGHT / 2,
                        16777215);
            }

            this.btnUndoChanges.xPosition = this.owningEntryList.scrollBarX - 44;
            this.btnUndoChanges.yPosition = y;
            this.btnUndoChanges.enabled = enabled() && isChanged;
            this.btnUndoChanges.drawButton(this.mc, mouseX, mouseY);

            this.btnDefault.xPosition = this.owningEntryList.scrollBarX - 22;
            this.btnDefault.yPosition = y;
            this.btnDefault.enabled = enabled() && !isDefault();
            this.btnDefault.drawButton(this.mc, mouseX, mouseY);

            if (this.tooltipHoverChecker == null)
                this.tooltipHoverChecker = new HoverChecker(y, y + slotHeight, x, this.owningScreen.entryList.controlX - 8, 800);
            else
                this.tooltipHoverChecker.updateBounds(y, y + slotHeight, x, this.owningScreen.entryList.controlX - 8);
        }

        @Override
        public void drawToolTip(int mouseX, int mouseY)
        {
            boolean canHover = mouseY < this.owningScreen.entryList.bottom && mouseY > this.owningScreen.entryList.top;
            if (toolTip != null && this.tooltipHoverChecker != null)
            {
                if (this.tooltipHoverChecker.checkHover(mouseX, mouseY, canHover))
                    this.owningScreen.drawToolTip(toolTip, mouseX, mouseY);
            }

            if (this.undoHoverChecker.checkHover(mouseX, mouseY, canHover))
                this.owningScreen.drawToolTip(undoToolTip, mouseX, mouseY);

            if (this.defaultHoverChecker.checkHover(mouseX, mouseY, canHover))
                this.owningScreen.drawToolTip(defaultToolTip, 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.btnDefault.mousePressed(this.mc, x, y))
            {
                btnDefault.playPressSound(mc.getSoundHandler());
                setToDefault();
                return true;
            }
            else if (this.btnUndoChanges.mousePressed(this.mc, x, y))
            {
                btnUndoChanges.playPressSound(mc.getSoundHandler());
                undoChanges();
                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.btnDefault.mouseReleased(x, y);
        }

        @Override
        public abstract boolean isDefault();

        @Override
        public abstract void setToDefault();

        @Override
        public abstract void keyTyped(char eventChar, int eventKey);

        @Override
        public abstract void updateCursorCounter();

        @Override
        public abstract void mouseClicked(int x, int y, int mouseEvent);

        @Override
        public abstract boolean isChanged();

        @Override
        public abstract void undoChanges();

        @Override
        public abstract boolean saveConfigElement();

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

        @Override
        public boolean enabled()
        {
            return owningScreen.isWorldRunning ? !owningScreen.allRequireWorldRestart && !configElement.requiresWorldRestart() : true;
        }

        @Override
        public int getLabelWidth()
        {
            return this.mc.fontRendererObj.getStringWidth(this.name);
        }

        @Override
        public int getEntryRightBound()
        {
            return this.owningEntryList.resetX + 40;
        }

        @Override
        public IConfigElement getConfigElement()
        {
            return configElement;
        }

        @Override
        public String getName()
        {
            return configElement.getName();
        }

        @Override
        public abstract Object getCurrentValue();

        @Override
        public abstract Object[] getCurrentValues();

        @Override
        public void onGuiClosed()
        {}
    }

    /**
     * Provides an interface for defining GuiConfigEntry.listEntry objects.
     */
    public static interface IConfigEntry extends GuiListExtended.IGuiListEntry
    {
        /**
         * Gets the IConfigElement object owned by this entry.
         * @return
         */
        public IConfigElement getConfigElement();

        /**
         * Gets the name of the ConfigElement owned by this entry.
         */
        public String getName();

        /**
         * Gets the current value of this entry.
         */
        public Object getCurrentValue();

        /**
         * Gets the current values of this list entry.
         */
        public Object[] getCurrentValues();

        /**
         * Is this list entry enabled?
         *
         * @return true if this entry's controls should be enabled, false otherwise.
         */
        public boolean enabled();

        /**
         * Handles user keystrokes for any GuiTextField objects in this entry. Call {@code GuiTextField.keyTyped()} for any GuiTextField
         * objects that should receive the input provided.
         */
        public void keyTyped(char eventChar, int eventKey);

        /**
         * Call {@code GuiTextField.updateCursorCounter()} for any GuiTextField objects in this entry.
         */
        public void updateCursorCounter();

        /**
         * Call {@code GuiTextField.mouseClicked()} for and GuiTextField objects in this entry.
         */
        public void mouseClicked(int x, int y, int mouseEvent);

        /**
         * Is this entry's value equal to the default value? Generally true should be returned if this entry is not a property or category
         * entry.
         *
         * @return true if this entry's value is equal to this entry's default value.
         */
        public boolean isDefault();

        /**
         * Sets this entry's value to the default value.
         */
        public void setToDefault();

        /**
         * Handles reverting any changes that have occurred to this entry.
         */
        public void undoChanges();

        /**
         * Has the value of this entry changed?
         *
         * @return true if changes have been made to this entry's value, false otherwise.
         */
        public boolean isChanged();

        /**
         * Handles saving any changes that have been made to this entry back to the underlying object. It is a good practice to check
         * isChanged() before performing the save action. This method should return true if the element has changed AND REQUIRES A RESTART.
         */
        public boolean saveConfigElement();

        /**
         * Handles drawing any tooltips that apply to this entry. This method is called after all other GUI elements have been drawn to the
         * screen, so it could also be used to draw any GUI element that needs to be drawn after all entries have had drawEntry() called.
         */
        public void drawToolTip(int mouseX, int mouseY);

        /**
         * Gets this entry's label width.
         */
        public int getLabelWidth();

        /**
         * Gets this entry's right-hand x boundary. This value is used to control where the scroll bar is placed.
         */
        public int getEntryRightBound();

        /**
         * This method is called when the parent GUI is closed. Most handlers won't need this; it is provided for special cases.
         */
        public void onGuiClosed();
    }
}