public abstract class Info {
    public final List<Info> children = new ArrayList<Info>();
    public int x;
    public int y;
    public int offsetX;
    public int offsetY;

    protected Info(final int x, final int y) {
        this.x = x;
        this.y = y;
        this.offsetX = 0;
        this.offsetY = 0;
    }

    public void draw() {
        drawInfo();

        for (final Info child : this.children) {
            child.offsetX = this.x;
            child.offsetY = this.y;

            child.draw();
        }
    }

    public abstract void drawInfo();

    public int getX() {
        return this.x + this.offsetX;
    }

    public int getY() {
        return this.y + this.offsetY;
    }

    public int getWidth() {
        return 0;
    }

    public int getHeight() {
        return 0;
    }

    @Override
    public String toString() {
        return "Info";
    }
} public class InfoText extends Info {
    private final FontRenderer fontRenderer;
    private final String text;

    public InfoText(final FontRenderer fontRenderer, final String text) {
        this(fontRenderer, text, 0, 0);
    }

    public InfoText(final FontRenderer fontRenderer, final String text, final int x, final int y) {
        super(x, y);
        this.fontRenderer = fontRenderer;
        this.text = text;
    }

    @Override
    public void drawInfo() {
        FontRendererHelper.drawLeftAlignedString(this.fontRenderer, this.text, getX(), getY(), 0x00FFFFFF);
    }

    @Override
    public int getWidth() {
        return this.fontRenderer.getStringWidth(this.text);
    }

    @Override
    public int getHeight() {
        return this.fontRenderer.FONT_HEIGHT;
    }

    @Override
    public String toString() {
        return String.format("InfoText{text: %s, x: %d, y: %d, offsetX: %d, offsetY: %d, children: %s}", this.text, this.x, this.y, this.offsetX, this.offsetY, this.children);
    }
} public class ConfigurationHandler {
    public static final ConfigurationHandler INSTANCE = new ConfigurationHandler();

    public static Configuration configuration;

    public static final String CONFIG_NAME_DEFAULT = Names.Files.FILE_XML;
    public static final boolean REPLACE_DEBUG_DEFAULT = false;
    public static final boolean SHOW_IN_CHAT_DEFAULT = true;
    public static final boolean SHOW_ON_PLAYER_LIST_DEFAULT = true;
    public static final double SCALE_DEFAULT = 1.0;
    public static final int FILE_INTERVAL_DEFAULT = 5;
    public static final boolean SHOW_OVERLAY_POTIONS_DEFAULT = true;
    public static final boolean SHOW_OVERLAY_ITEM_ICONS_DEFAULT = true;

    public static String configName = CONFIG_NAME_DEFAULT;
    public static boolean replaceDebug = REPLACE_DEBUG_DEFAULT;
    public static boolean showInChat = SHOW_IN_CHAT_DEFAULT;
    public static boolean showOnPlayerList = SHOW_ON_PLAYER_LIST_DEFAULT;
    public static float scale = (float) SCALE_DEFAULT;
    public static int fileInterval = FILE_INTERVAL_DEFAULT;
    public static boolean showOverlayPotions = SHOW_OVERLAY_POTIONS_DEFAULT;
    public static boolean showOverlayItemIcons = SHOW_OVERLAY_ITEM_ICONS_DEFAULT;

    public static Property propConfigName = null;
    public static Property propReplaceDebug = null;
    public static Property propShowInChat = null;
    public static Property propShowOnPlayerList = null;
    public static Property propScale = null;
    public static Property propFileInterval = null;
    public static Property propShowOverlayPotions = null;
    public static Property propShowOverlayItemIcons= null;
    public static final Map<Alignment, Property> propAlignments = new HashMap<Alignment, Property>();

    private ConfigurationHandler() {}

    public static void init(final File configFile) {
        if (configuration == null) {
            configuration = new Configuration(configFile);
            loadConfiguration();
        }
    }

    private static void loadConfiguration() {
        propConfigName = configuration.get(Names.Config.Category.GENERAL, Names.Config.FILENAME, CONFIG_NAME_DEFAULT, Names.Config.FILENAME_DESC);
        propConfigName.setLanguageKey(Names.Config.LANG_PREFIX + "." + Names.Config.FILENAME);
        propConfigName.setRequiresMcRestart(true);
        configName = propConfigName.getString();

        propReplaceDebug = configuration.get(Names.Config.Category.GENERAL, Names.Config.REPLACE_DEBUG, REPLACE_DEBUG_DEFAULT, Names.Config.REPLACE_DEBUG_DESC);
        propReplaceDebug.setLanguageKey(Names.Config.LANG_PREFIX + "." + Names.Config.REPLACE_DEBUG);
        replaceDebug = propReplaceDebug.getBoolean(REPLACE_DEBUG_DEFAULT);

        propShowInChat = configuration.get(Names.Config.Category.GENERAL, Names.Config.SHOW_IN_CHAT, SHOW_IN_CHAT_DEFAULT, Names.Config.SHOW_IN_CHAT_DESC);
        propShowInChat.setLanguageKey(Names.Config.LANG_PREFIX + "." + Names.Config.SHOW_IN_CHAT);
        showInChat = propShowInChat.getBoolean(SHOW_IN_CHAT_DEFAULT);

        propShowOnPlayerList = configuration.get(Names.Config.Category.GENERAL, Names.Config.SHOW_ON_PLAYER_LIST, SHOW_ON_PLAYER_LIST_DEFAULT, Names.Config.SHOW_ON_PLAYER_LIST_DESC);
        propShowOnPlayerList.setLanguageKey(Names.Config.LANG_PREFIX + "." + Names.Config.SHOW_ON_PLAYER_LIST);
        showOnPlayerList = propShowOnPlayerList.getBoolean(SHOW_ON_PLAYER_LIST_DEFAULT);

        propScale = configuration.get(Names.Config.Category.GENERAL, Names.Config.SCALE, String.valueOf(SCALE_DEFAULT), Names.Config.SCALE_DESC);
        propScale.setLanguageKey(Names.Config.LANG_PREFIX + "." + Names.Config.SCALE);
        propScale.setValidValues(new String[] { "0.5", "1.0", "1.5", "2.0" });
        scale = (float) propScale.getDouble(SCALE_DEFAULT);

        propFileInterval = configuration.get(Names.Config.Category.GENERAL, Names.Config.FILE_INTERVAL, FILE_INTERVAL_DEFAULT, Names.Config.FILE_INTERVAL_DESC, 1, 60);
        propFileInterval.setLanguageKey(Names.Config.LANG_PREFIX + "." + Names.Config.FILE_INTERVAL);
        fileInterval = propFileInterval.getInt(FILE_INTERVAL_DEFAULT);

        propShowOverlayPotions = configuration.get(Names.Config.Category.GENERAL, Names.Config.SHOW_OVERLAY_POTIONS, SHOW_OVERLAY_POTIONS_DEFAULT, Names.Config.SHOW_OVERLAY_POTIONS_DESC);
        propShowOverlayPotions.setLanguageKey(Names.Config.LANG_PREFIX + "." + Names.Config.SHOW_OVERLAY_POTIONS);
        showOverlayPotions = propShowOverlayPotions.getBoolean(SHOW_OVERLAY_POTIONS_DEFAULT);

        propShowOverlayItemIcons = configuration.get(Names.Config.Category.GENERAL, Names.Config.SHOW_OVERLAY_ITEM_ICONS, SHOW_OVERLAY_ITEM_ICONS_DEFAULT, Names.Config.SHOW_OVERLAY_ITEM_ICONS_DESC);
        propShowOverlayItemIcons.setLanguageKey(Names.Config.LANG_PREFIX + "." + Names.Config.SHOW_OVERLAY_ITEM_ICONS);
        showOverlayItemIcons = propShowOverlayItemIcons.getBoolean(SHOW_OVERLAY_ITEM_ICONS_DEFAULT);

        for (final Alignment alignment : Alignment.values()) {
            final String alignmentName = alignment.toString().toLowerCase(Locale.ENGLISH);
            final Property property = configuration.get(Names.Config.Category.ALIGNMENT, alignmentName, alignment.getDefaultXY(), String.format(Names.Config.ALIGNMENT_DESC, alignment.toString()));
            property.setLanguageKey(Names.Config.LANG_PREFIX + "." + alignmentName);
            property.setValidationPattern(Pattern.compile("-?\\d+ -?\\d+"));
            propAlignments.put(alignment, property);
            alignment.setXY(property.getString());
        }

        save();
    }

    public static void reload() {
        loadConfiguration();
        save();
    }

    public static void save() {
        if (configuration.hasChanged()) {
            configuration.save();
        }
    }

    public static void setConfigName(final String name) {
        propConfigName.set(name);
    }

    @SubscribeEvent
    public void onConfigurationChangedEvent(final ConfigChangedEvent.OnConfigChangedEvent event) {
        if (event.getModID().equalsIgnoreCase(Reference.MODID)) {
            loadConfiguration();
        }
    }
} public interface IParser {
    public boolean load(InputStream inputStream);

    public boolean parse(Map<Alignment, List<List<Value>>> format);
} public class JsonParser implements IParser {
    private JsonElement element;

    @Override
    public boolean load(final InputStream inputStream) {
        try {
            final InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "UTF-8");
            final com.google.gson.JsonParser parser = new com.google.gson.JsonParser();

            this.element = parser.parse(inputStreamReader);

            inputStreamReader.close();
        } catch (final Exception e) {
            Reference.logger.fatal("Could not read json configuration file!", e);
            return false;
        }

        return true;
    }

    @Override
    public boolean parse(final Map<Alignment, List<List<Value>>> format) {
        if (!this.element.isJsonObject()) {
            return false;
        }

        final JsonObject config = this.element.getAsJsonObject();
        final Set<Map.Entry<String, JsonElement>> entries = config.entrySet();

        for (final Map.Entry<String, JsonElement> entry : entries) {
            final Alignment alignment = Alignment.parse(entry.getKey());
            if (alignment != null) {
                format.put(alignment, getLines(entry.getValue()));
            }
        }

        return true;
    }

    private List<List<Value>> getLines(final JsonElement elementLines) {
        final List<List<Value>> listLines = new ArrayList<List<Value>>();

        final JsonArray arrayLines = elementLines.getAsJsonArray();
        for (final JsonElement elementLine : arrayLines) {
            if (elementLine != null && elementLine.isJsonArray()) {
                listLines.add(getValues(elementLine.getAsJsonArray()));
            }
        }

        return listLines;
    }

    private List<Value> getValues(final JsonArray arrayValues) {
        final List<Value> values = new ArrayList<Value>();

        for (final JsonElement elementValue : arrayValues) {
            if (elementValue != null && elementValue.isJsonObject()) {
                final JsonObject object = elementValue.getAsJsonObject();
                final Set<Map.Entry<String, JsonElement>> entries = object.entrySet();
                for (final Map.Entry<String, JsonElement> entry : entries) {
                    final String type = entry.getKey();
                    final Value value = Value.fromString(type);

                    if (!value.isValid()) {
                        continue;
                    }

                    if (value.isSimple()) {
                        value.setRawValue(entry.getValue().getAsString(), false);
                    } else if (entry.getValue().isJsonArray()) {
                        value.values.addAll(getValues(entry.getValue().getAsJsonArray()));
                    }
                    values.add(value);
                }
            }
        }

        return values;
    }
} public class TextParser implements IParser {
    private final Tokenizer tokenizer;
    private Token token;
    private int level = 0;
    private Alignment alignment = Alignment.TOPLEFT;

    public TextParser() {
        this.tokenizer = new Tokenizer();
    }

    private Token nextToken() {
        this.token = this.tokenizer.nextToken();
        return this.token;
    }

    @Override
    public boolean load(final InputStream inputStream) {
        try {
            final InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
            final BufferedReader reader = new BufferedReader(inputStreamReader);
            String line, content = "";

            while ((line = reader.readLine()) != null) {
                content += line + "\n";
            }

            reader.close();
            inputStreamReader.close();

            this.tokenizer.tokenize(content);
        } catch (final Exception e) {
            Reference.logger.fatal("Could not read text configuration file!", e);
            return false;
        }

        return true;
    }

    @Override
    public boolean parse(final Map<Alignment, List<List<Value>>> format) {
        boolean expr;

        try {
            nextToken();
            expr = alignments(format) && this.token.isEof();
        } catch (final Exception e) {
            expr = false;
            Reference.logger.error("Parsing failed at {}!", this.token, e);
        }

        return expr;
    }

    private boolean alignments(final Map<Alignment, List<List<Value>>> format) {
        return alignment(format) && alignmentsTail(format);
    }

    private boolean alignmentsTail(final Map<Alignment, List<List<Value>>> format) {
        if (alignment(format)) {
            alignmentsTail(format);
        }

        return true;
    }

    private boolean alignment(final Map<Alignment, List<List<Value>>> format) {
        boolean expr;
        List<List<Value>> lines = format.get(this.alignment);

        if (lines == null) {
            lines = new ArrayList<List<Value>>();
        }

        try {
            expr = lines(lines);

            if (expr) {
                format.put(this.alignment, lines);
            }
        } catch (final AlignmentException e) {
            format.put(this.alignment, lines);

            this.alignment = e.getAlignment();
            expr = e.isValid();
        }

        return expr;
    }

    private boolean lines(final List<List<Value>> lines) throws AlignmentException {
        return line(lines) && linesTail(lines);
    }

    private boolean linesTail(final List<List<Value>> lines) throws AlignmentException {
        if (line(lines)) {
            linesTail(lines);
        }

        return true;
    }

    private boolean line(final List<List<Value>> lines) throws AlignmentException {
        final boolean expr;
        final List<Value> values = new ArrayList<Value>();

        expr = values(values);

        if (this.token.getType().equals(TokenType.NEWLINE)) {
            nextToken();
        }

        if (expr) {
            lines.add(values);
        }

        return expr;
    }

    private boolean values(final List<Value> values) throws AlignmentException {
        return value(values) && valuesTail(values);
    }

    private boolean valuesTail(final List<Value> values) throws AlignmentException {
        if (value(values)) {
            valuesTail(values);
        }

        return true;
    }

    private boolean value(final List<Value> values) throws AlignmentException {
        final boolean expr;

        if (this.token.getType().equals(TokenType.STRING)) {
            expr = string(values, this.token.getLexem());
            nextToken();
        } else if (this.token.getType().equals(TokenType.FUNC_HEAD)) {
            nextToken();
            expr = function(values, this.token.getLexem());
        } else if (this.level == 0 && TokenType.EXCEPTIONS.contains(this.token.getType())) {
            expr = string(values, this.token.getLexem());
            nextToken();
        } else {
            expr = false;
        }

        return expr;
    }

    private boolean string(final List<Value> values, final String lexem) {
        final Value value = Value.fromString("str").setRawValue(lexem, true);
        values.add(value);
        return true;
    }

    private boolean function(final List<Value> values, final String lexem) throws AlignmentException {
        boolean expr;

        this.level++;

        Value value = Value.fromString(lexem);
        if (!value.isValid()) {
            value = Value.fromString("var");
            value.setRawValue(lexem, true);
        } else {
            value.setRawValue("", true);
        }

        if (this.token.getType().equals(TokenType.STRING)) {
            nextToken();

            expr = argumentGroupA(value);

            if (this.token.getType().equals(TokenType.FUNC_TAIL)) {
                nextToken();
            } else {
                expr = false;
            }
        } else {
            expr = false;
        }

        this.level--;

        final Alignment alignment = Alignment.parse(lexem);
        if (alignment != null) {
            throw new AlignmentException(alignment, expr);
        } else if (expr) {
            values.add(value);
        }

        return expr;
    }

    private boolean argumentGroupA(final Value value) throws AlignmentException {
        boolean expr;

        if (this.token.getType().equals(TokenType.ARGS_HEAD)) {
            nextToken();

            expr = argumentsA(value);

            expr &= argumentGroupB(value);

            if (this.token.getType().equals(TokenType.ARGS_TAIL)) {
                nextToken();
            } else {
                expr = false;
            }
        } else {
            expr = true;
        }

        return expr;
    }

    private boolean argumentsA(final Value value) throws AlignmentException {
        return argument(value) && argumentsATail(value);
    }

    private boolean argumentsATail(final Value value) throws AlignmentException {
        if (this.token.getType().equals(TokenType.ARGS_SEPARATOR)) {
            nextToken();

            if (argument(value)) {
                argumentsATail(value);
            }
        }

        return true;
    }

    private boolean argument(final Value value) throws AlignmentException {
        final boolean expr;

        if (this.token.getType().equals(TokenType.STRING)) {
            expr = string(value.values, this.token.getLexem());
            nextToken();
        } else if (this.token.getType().equals(TokenType.FUNC_HEAD)) {
            nextToken();
            expr = function(value.values, this.token.getLexem());
        } else {
            expr = string(value.values, "");
        }

        return expr;
    }

    private boolean argumentGroupB(final Value value) throws AlignmentException {
        boolean expr;

        if (this.token.getType().equals(TokenType.ARGS_HEAD)) {
            nextToken();

            expr = argumentsB(value);

            if (this.token.getType().equals(TokenType.ARGS_TAIL)) {
                nextToken();
            } else {
                expr = false;
            }
        } else {
            expr = true;
        }

        return expr;
    }

    private boolean argumentsB(final Value value) throws AlignmentException {
        return argument(value) && argumentsBTail(value);
    }

    private boolean argumentsBTail(final Value value) throws AlignmentException {
        if (this.token.getType().equals(TokenType.ARGS_SEPARATOR)) {
            nextToken();

            if (argument(value)) {
                argumentsBTail(value);
            }
        }

        return true;
    }
} public class XmlParser implements IParser {
    private Document document;

    @Override
    public boolean load(final InputStream inputStream) {
        try {
            final DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
            final DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
            this.document = dBuilder.parse(inputStream);
            this.document.getDocumentElement().normalize();
        } catch (final Exception e) {
            Reference.logger.fatal("Could not read xml configuration file!", e);
            return false;
        }

        return true;
    }

    @Override
    public boolean parse(final Map<Alignment, List<List<Value>>> format) {
        if (this.document == null) {
            return false;
        }

        final Element documentElement = this.document.getDocumentElement();
        final NodeList nodeListLines = documentElement.getChildNodes();
        for (int i = 0; i < nodeListLines.getLength(); i++) {
            final Element elementLines = getElement(nodeListLines.item(i), "lines");
            if (elementLines != null) {
                final Alignment alignment = Alignment.parse(elementLines.getAttribute("at"));
                if (alignment != null) {
                    format.put(alignment, getLines(elementLines));
                }
            }
        }

        return true;
    }

    private List<List<Value>> getLines(final Element element) {
        final List<List<Value>> listLines = new ArrayList<List<Value>>();

        final NodeList nodeListLine = element.getChildNodes();
        for (int i = 0; i < nodeListLine.getLength(); i++) {
            final Element elementLine = getElement(nodeListLine.item(i), "line");
            if (elementLine != null) {
                listLines.add(getValues(elementLine));
            }
        }

        return listLines;
    }

    private List<Value> getValues(final Element element) {
        final List<Value> values = new ArrayList<Value>();

        final NodeList nodeListValues = element.getChildNodes();
        for (int i = 0; i < nodeListValues.getLength(); i++) {
            final Element elementValue = getElement(nodeListValues.item(i));
            if (elementValue != null) {
                final String type = elementValue.getNodeName().equalsIgnoreCase("value") ? elementValue.getAttribute("type") : elementValue.getNodeName();
                final Value value = Value.fromString(type);

                if (!value.isValid()) {
                    continue;
                }

                if (value.isSimple()) {
                    value.setRawValue(elementValue.getTextContent(), false);
                } else {
                    value.values.addAll(getValues(elementValue));
                }
                values.add(value);
            }
        }

        return values;
    }

    private Element getElement(final Node node) {
        if (node.getNodeType() == Node.ELEMENT_NODE) {
            return (Element) node;
        }

        return null;
    }

    private Element getElement(final Node node, final String name) {
        final Element element = getElement(node);
        if (element != null && element.getNodeName().equalsIgnoreCase(name)) {
            return element;
        }

        return null;
    }
} public interface IPrinter {
    public boolean print(File file, Map<Alignment, List<List<Value>>> format);
} public class JsonPrinter implements IPrinter {
    @Override
    public boolean print(final File file, final Map<Alignment, List<List<Value>>> format) {
        try {
            final FileWriter fileWriter = new FileWriter(file);
            final BufferedWriter writer = new BufferedWriter(fileWriter);

            final JsonObject jsonConfig = new JsonObject();

            appendLines(jsonConfig, format);

            final Gson gson = new GsonBuilder().setPrettyPrinting().create();
            writer.write(gson.toJson(jsonConfig));

            writer.close();
            fileWriter.close();
            return true;
        } catch (final Exception e) {
            Reference.logger.fatal("Could not save json configuration file!", e);
        }

        return false;
    }

    private void appendLines(final JsonObject jsonConfig, final Map<Alignment, List<List<Value>>> format) {
        for (final Alignment alignment : Alignment.values()) {
            final List<List<Value>> lists = format.get(alignment);
            if (lists != null) {
                final JsonArray arrayLines = new JsonArray();

                appendLine(arrayLines, lists);

                if (arrayLines.size() > 0) {
                    jsonConfig.add(alignment.toString().toLowerCase(Locale.ENGLISH), arrayLines);
                }
            }
        }
    }

    private void appendLine(final JsonArray jsonLines, final List<List<Value>> lines) {
        for (final List<Value> line : lines) {
            final JsonArray arrayLine = new JsonArray();

            appendValues(arrayLine, line);

            if (arrayLine.size() > 0) {
                jsonLines.add(arrayLine);
            }
        }
    }

    private void appendValues(final JsonArray jsonValues, final List<Value> values) {
        for (final Value value : values) {
            final JsonObject obj = new JsonObject();

            final String type = value.getType();
            if (value.values.size() > 0) {
                final JsonArray array = new JsonArray();
                appendValues(array, value.values);
                obj.add(type, array);
            } else {
                final String val = value.getRawValue(false);
                if (val.matches("^-?\\d+$")) {
                    obj.addProperty(type, Integer.valueOf(val));
                } else if (val.matches("^-?\\d+(\\.\\d+)?$")) {
                    obj.addProperty(type, Double.valueOf(val));
                } else {
                    obj.addProperty(type, val);
                }
            }

            jsonValues.add(obj);
        }
    }
} public class TextPrinter implements IPrinter {
    @Override
    public boolean print(final File file, final Map<Alignment, List<List<Value>>> format) {
        try {
            final FileWriter fileWriter = new FileWriter(file);
            final BufferedWriter writer = new BufferedWriter(fileWriter);

            writeLines(writer, format);

            writer.close();
            fileWriter.close();
            return true;
        } catch (final Exception e) {
            Reference.logger.fatal("Could not save text configuration file!", e);
        }

        return false;
    }

    private void writeLines(final BufferedWriter writer, final Map<Alignment, List<List<Value>>> format) throws IOException {
        for (final Alignment alignment : Alignment.values()) {
            final List<List<Value>> lists = format.get(alignment);
            if (lists != null) {
                writer.write(String.format("<%s>", alignment.toString().toLowerCase(Locale.ENGLISH)));

                writeLine(writer, lists);
            }
        }
    }

    private void writeLine(final BufferedWriter writer, final List<List<Value>> lines) throws IOException {
        for (final List<Value> line : lines) {
            writeValues(writer, line);
            writer.write("\n");
        }
    }

    private void writeValues(final BufferedWriter writer, final List<Value> values) throws IOException {
        for (final Value value : values) {
            writeValue(writer, value);
        }
    }

    private void writeValue(final BufferedWriter writer, final Value value) throws IOException {
        final List<Value> values = value.values;
        final int size = values.size();
        final String type = value.getType();

        if (value.isValidSize()) {
            if (value.isSimple()) {
                final String str = value.getRawValue(true);
                if (value.getClass() == ValueSimple.ValueVariable.class) {
                    writer.write(String.format("<%s>", str));
                } else {
                    writer.write(String.format("%s", str));
                }
            } else {
                writer.write(String.format("<%s[", type));
                writeValue(writer, values.get(0));
                if (value.getClass() == ValueLogic.ValueIf.class) {
                    writer.write("[");
                    writeValue(writer, values.get(1));
                    if (size == 3) {
                        writer.write("/");
                        writeValue(writer, values.get(2));
                    }
                    writer.write("]");
                } else if (value.getClass() == ValueComplex.ValueMin.class || value.getClass() == ValueComplex.ValueMax.class) {
                    writer.write("/");
                    writeValue(writer, values.get(1));
                    if (size == 4) {
                        writer.write("[");
                        writeValue(writer, values.get(2));
                        writer.write("/");
                        writeValue(writer, values.get(3));
                        writer.write("]");
                    }
                } else {
                    for (int i = 1; i < size; i++) {
                        writer.write("/");
                        writeValue(writer, values.get(i));
                    }
                }
                writer.write("]>");
            }
        } else {
            Reference.logger.fatal(String.format("Invalid size %d for value type %s!", size, type));
        }
    }
} public class XmlPrinter implements IPrinter {
    @Override
    public boolean print(final File file, final Map<Alignment, List<List<Value>>> format) {
        final DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
        final DocumentBuilder dBuilder;
        try {
            dBuilder = dbFactory.newDocumentBuilder();
            final Document doc = dBuilder.newDocument();

            final Element config = doc.createElement("config");
            appendLines(doc, config, format);
            doc.appendChild(config);

            final TransformerFactory transformerFactory = TransformerFactory.newInstance();
            final Transformer transformer = transformerFactory.newTransformer();
            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
            transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");

            final DOMSource source = new DOMSource(doc);
            final StreamResult streamResult = new StreamResult(file);
            transformer.transform(source, streamResult);

            return true;
        } catch (final Exception e) {
            Reference.logger.fatal("Could not save xml configuration file!", e);
        }

        return false;
    }

    private void appendLines(final Document doc, final Element config, final Map<Alignment, List<List<Value>>> format) {
        for (final Alignment alignment : Alignment.values()) {
            final List<List<Value>> lists = format.get(alignment);
            if (lists != null) {
                final Element elementLines = doc.createElement("lines");
                elementLines.setAttribute("at", alignment.toString().toLowerCase(Locale.ENGLISH));

                appendLine(doc, elementLines, lists);

                if (elementLines.hasChildNodes()) {
                    config.appendChild(elementLines);
                }
            }
        }
    }

    private void appendLine(final Document doc, final Element elementLines, final List<List<Value>> lines) {
        for (final List<Value> line : lines) {
            final Element elementLine = doc.createElement("line");

            appendValues(doc, elementLine, line);

            if (elementLine.hasChildNodes()) {
                elementLines.appendChild(elementLine);
            }
        }
    }

    private void appendValues(final Document doc, final Element elementValues, final List<Value> values) {
        for (final Value value : values) {
            final Element elementValue = doc.createElement(value.getType());

            elementValue.setTextContent(value.getRawValue(false));
            if (value.values.size() > 0) {
                appendValues(doc, elementValue, value.values);
            }

            elementValues.appendChild(elementValue);
        }
    }
} @SuppressWarnings("HardCodedStringLiteral")
public final class Names {
    public static final class Mods {
        public static final String BLOODMAGIC_MODID = "AWWayofTime";
        public static final String BLOODMAGIC_NAME = "Blood Magic";

        public static final String SIMPLYJETPACKS_MODID = "simplyjetpacks";
        public static final String SIMPLYJETPACKS_NAME = "Simply Jetpacks";

        public static final String TERRAFIRMACRAFT_MODID = "terrafirmacraft";
        public static final String TERRAFIRMACRAFT_NAME = "TerraFirmaCraft";

        public static final String THAUMCRAFT_MODID = "Thaumcraft";
        public static final String THAUMCRAFT_NAME = "Thaumcraft";
    }

    public static final class Command {
        public static final class Message {
            public static final String USAGE = "commands.ingameinfoxml.usage";
            public static final String RELOAD = "commands.ingameinfoxml.reload";
            public static final String LOAD = "commands.ingameinfoxml.load";
            public static final String SAVE = "commands.ingameinfoxml.save";
            public static final String SUCCESS = "commands.ingameinfoxml.success";
            public static final String FAILURE = "commands.ingameinfoxml.failure";
            public static final String ENABLE = "commands.ingameinfoxml.enable";
            public static final String DISABLE = "commands.ingameinfoxml.disable";
        }

        public static final String NAME = "igi";
        public static final String RELOAD = "reload";
        public static final String LOAD = "load";
        public static final String SAVE = "save";
        public static final String ENABLE = "enable";
        public static final String DISABLE = "disable";
        public static final String TAGLIST = "taglist";
    }

    public static final class Config {
        public static final class Category {
            public static final String GENERAL = "general";
            public static final String ALIGNMENT = "alignment";
        }

        public static final String FILENAME = "filename";
        public static final String FILENAME_DESC = "The configuration that should be loaded on startup.";
        public static final String REPLACE_DEBUG = "replaceDebug";
        public static final String REPLACE_DEBUG_DESC = "Replace the debug overlay (F3) with the InGameInfoXML overlay.";
        public static final String SHOW_IN_CHAT = "showInChat";
        public static final String SHOW_IN_CHAT_DESC = "Display the overlay in chat.";
        public static final String SHOW_ON_PLAYER_LIST = "showOnPlayerList";
        public static final String SHOW_ON_PLAYER_LIST_DESC = "Display the overlay on the player list.";
        public static final String SCALE = "scale";
        public static final String SCALE_DESC = "The overlay will be scaled by this amount.";
        public static final String FILE_INTERVAL = "fileInterval";
        public static final String FILE_INTERVAL_DESC = "The interval between file reads for the 'file' tag (in seconds).";

        public static final String SHOW_OVERLAY_POTIONS = "showOverlayPotions";
        public static final String SHOW_OVERLAY_POTIONS_DESC = "Display the vanilla potion overlay.";

        public static final String SHOW_OVERLAY_ITEM_ICONS = "showOverlayItemIcons";
        public static final String SHOW_OVERLAY_ITEM_ICONS_DESC = "Display the item overlay on icon (durability, stack size).";

        public static final String ALIGNMENT_DESC = "Offsets for %s (X<space>Y).";

        public static final String LANG_PREFIX = Reference.MODID + ".config";
    }

    public static final class Files {
        public static final String NAME = "InGameInfo";

        public static final String FILE_XML = "InGameInfo.xml";
        public static final String FILE_JSON = "InGameInfo.json";
        public static final String FILE_TXT = "InGameInfo.txt";

        public static final String EXT_XML = ".xml";
        public static final String EXT_JSON = ".json";
        public static final String EXT_TXT = ".txt";
    }

    public static final class Keys {
        public static final String CATEGORY = "ingameinfoxml.key.category";
        public static final String TOGGLE = "ingameinfoxml.key.toggle";
    }
} public class Reference {
    public static final String MODID = "ingameinfoxml";
    public static final String NAME = "InGame Info XML";
    public static final String VERSION = "${version}";
    public static final String FORGE = "${forgeversion}";
    public static final String MINECRAFT = "${mcversion}";
    public static final String PROXY_SERVER = "com.github.lunatrius.ingameinfo.proxy.ServerProxy";
    public static final String PROXY_CLIENT = "com.github.lunatrius.ingameinfo.proxy.ClientProxy";
    public static final String GUI_FACTORY = "com.github.lunatrius.ingameinfo.client.gui.config.GuiFactory";

    public static Logger logger = LogManager.getLogger(Reference.MODID);
} public abstract class Tag {
    protected static final Minecraft minecraft = Minecraft.getMinecraft();
    protected static final MBlockPos playerPosition = new MBlockPos();
    protected static final Vector3f playerMotion = new Vector3f();
    protected static MinecraftServer server;
    protected static World world;
    protected static EntityPlayerSP player;
    protected static List<Info> info;
    protected static boolean hasSeed = false;
    protected static long seed = 0;

    private String name = null;
    private String[] aliases = new String[0];

    public Tag setName(final String name) {
        this.name = name;
        return this;
    }

    public String getName() {
        return this.name;
    }

    public Tag setAliases(final String... aliases) {
        this.aliases = aliases;
        return this;
    }

    public String[] getAliases() {
        return this.aliases;
    }

    public boolean isIndexed() {
        return false;
    }

    public int getMaximumIndex() {
        return -1;
    }

    public String getRawName() {
        return this.name;
    }

    public String getFormattedName() {
        return this.name + (isIndexed() ? String.format("[0..%d]", getMaximumIndex()) : "");
    }

    public String getLocalizedCategory() {
        return I18n.format(Reference.MODID + ".tag.category." + getCategory() + ".name");
    }

    public String getLocalizedDescription() {
        return I18n.format(Reference.MODID + ".tag." + getRawName() + ".desc");
    }

    public abstract String getCategory();

    public abstract String getValue();

    public static void setServer(final MinecraftServer server) {
        Tag.server = server;

        try {
            setSeed(Tag.server.getWorld(0).getSeed());
        } catch (final Exception e) {
            unsetSeed();
        }
    }

    public static void setSeed(final long seed) {
        Tag.hasSeed = true;
        Tag.seed = seed;
    }

    public static void unsetSeed() {
        Tag.hasSeed = false;
        Tag.seed = 0;
    }

    public static void setWorld(final World world) {
        Tag.world = world;
    }

    public static void setPlayer(final EntityPlayerSP player) {
        Tag.player = player;

        if (player != null) {
            playerPosition.set(player);
            playerMotion.set((float) (player.posX - player.prevPosX), (float) (player.posY - player.prevPosY), (float) (player.posZ - player.prevPosZ));
        }
    }

    public static void setInfo(final List<Info> info) {
        Tag.info = info;
    }

    public static void releaseResources() {
        setWorld(null);
        setPlayer(null);
        TagNearbyPlayer.releaseResources();
        TagPlayerPotion.releaseResources();
    }

    public static String getIconTag(final Info info) {
        String str = "";
        for (int i = 0; i < info.getWidth() && minecraft.fontRenderer.getStringWidth(str) < info.getWidth(); i++) {
            str += " ";
        }
        return String.format("{ICON|%s}", str);
    }
} public abstract class Value {
    private static final Pattern PATTERN = Pattern.compile("\\{([a-z0-9]+)\\}", Pattern.CASE_INSENSITIVE);
    private static final Matcher MATCHER = PATTERN.matcher("");
    protected static List<Info> info;

    private String name = null;
    private String[] aliases = new String[0];
    protected String value = "";
    public final List<Value> values = new ArrayList<Value>();

    public Value setName(final String name) {
        this.name = name;
        return this;
    }

    public String getName() {
        return this.name;
    }

    public Value setAliases(final String... aliases) {
        this.aliases = aliases;
        return this;
    }

    public String[] getAliases() {
        return this.aliases;
    }

    public String getType() {
        return ValueRegistry.INSTANCE.forClass(getClass());
    }

    public Value setRawValue(final String value, final boolean isText) {
        this.value = "";
        return this;
    }

    public String getRawValue(final boolean isText) {
        return this.value;
    }

    protected String replaceVariables(String str) {
        MATCHER.reset(str);

        while (MATCHER.find()) {
            str = str.replace(MATCHER.group(0), getVariableValue(MATCHER.group(1)));
        }

        return str;
    }

    public abstract boolean isSimple();

    public abstract boolean isValidSize();

    public abstract String getValue();

    public boolean isValid() {
        return true;
    }

    protected String getValue(final int index) {
        return this.values.get(index).getReplacedValue();
    }

    protected int getIntValue() {
        return Integer.parseInt(getReplacedValue());
    }

    protected int getIntValue(final int index) {
        return Integer.parseInt(getValue(index));
    }

    protected double getDoubleValue() {
        return Double.parseDouble(getReplacedValue());
    }

    protected double getDoubleValue(final int index) {
        return Double.parseDouble(getValue(index));
    }

    protected boolean getBooleanValue() {
        return Boolean.parseBoolean(getReplacedValue());
    }

    protected boolean getBooleanValue(final int index) {
        return Boolean.parseBoolean(getValue(index));
    }

    protected String getVariableValue(final String var) {
        try {
            final String value = TagRegistry.INSTANCE.getValue(var);
            if (value != null) {
                return value;
            }
        } catch (final Exception e) {
            Reference.logger.debug("Failed to get value!", e);
            return "null";
        }

        return "{" + var + "}";
    }

    public String getReplacedValue() {
        return replaceVariables(getValue());
    }

    @Override
    public String toString() {
        return String.format(Locale.ENGLISH, "[%s] '%s'", getClass(), this.value);
    }

    public static void setInfo(final List<Info> info) {
        Value.info = info;
    }

    public static Value fromString(final String str) {
        return ValueRegistry.INSTANCE.forName(str);
    }

    public static String toString(final Value value) {
        return ValueRegistry.INSTANCE.forClass(value.getClass());
    }
} public abstract class ValueComplex extends Value {
    @Override
    public boolean isSimple() {
        return false;
    }

    public static class ValueOperation extends ValueComplex {
        @Override
        public boolean isValidSize() {
            return this.values.size() > 1;
        }

        @Override
        public String getValue() {
            try {
                final Operation operation = Operation.fromString(getValue(0));
                return operation.getValue(this);
            } catch (final Exception e) {
                return "";
            }
        }
    }

    public static class ValueConcat extends ValueComplex {
        @Override
        public boolean isValidSize() {
            return this.values.size() > 1;
        }

        @Override
        public String getValue() {
            String str = "";
            for (final Value val : this.values) {
                str += replaceVariables(val.getValue());
            }
            return str;
        }
    }

    public static class ValueMin extends ValueComplex {
        @Override
        public boolean isValidSize() {
            return this.values.size() == 2 || this.values.size() == 4;
        }

        @Override
        public String getValue() {
            try {
                final double arg0 = getDoubleValue(0);
                final double arg1 = getDoubleValue(1);
                final int shift = this.values.size() - 2;
                return arg0 <= arg1 ? getValue(0 + shift) : getValue(1 + shift);
            } catch (final Exception e) {
                return "0";
            }
        }
    }

    public static class ValueMax extends ValueComplex {
        @Override
        public boolean isValidSize() {
            return this.values.size() == 2 || this.values.size() == 4;
        }

        @Override
        public String getValue() {
            try {
                final double arg0 = getDoubleValue(0);
                final double arg1 = getDoubleValue(1);
                final int shift = this.values.size() - 2;
                return arg0 >= arg1 ? getValue(0 + shift) : getValue(1 + shift);
            } catch (final Exception e) {
                return "0";
            }
        }
    }

    public static class ValueItemQuantity extends ValueComplex {
        @Override
        public boolean isValidSize() {
            return this.values.size() == 1 || this.values.size() == 2;
        }

        @Override
        public String getValue() {
            try {
                Item item;
                int itemDamage = -1;
                try {
                    item = Item.REGISTRY.getObject(new ResourceLocation(getValue(0)));
                } catch (final Exception e3) {
                    item = Item.REGISTRY.getObjectById(getIntValue(0));
                }
                if (this.values.size() == 2) {
                    itemDamage = getIntValue(1);
                }
                return String.valueOf(EntityHelper.getItemCountInInventory(Minecraft.getMinecraft().player.inventory, item, itemDamage));
            } catch (final Exception e2) {
                return "0";
            }
        }
    }

    public static class ValueTranslate extends ValueComplex {
        @Override
        public boolean isValidSize() {
            return this.values.size() > 0;
        }

        @Override
        public String getValue() {
            try {
                final String format = getValue(0);
                final String[] args = new String[this.values.size() - 1];
                for (int i = 0; i < args.length; i++) {
                    args[i] = getValue(i + 1);
                }
                return I18n.format(format, args);
            } catch (final Exception e) {
                return "?";
            }
        }
    }

    public static class ValueFormattedTime extends ValueComplex {
        @Override
        public boolean isValidSize() {
            return this.values.size() == 1;
        }

        @Override
        public String getValue() {
            try {
                final String format = getValue(0);
                return new SimpleDateFormat(format).format(new Date());
            } catch (final Exception e) {
                return "?";
            }
        }
    }

    public static class ValueFormattedNumber extends ValueComplex {
        @Override
        public boolean isValidSize() {
            return this.values.size() == 2;
        }

        @Override
        public String getValue() {
            try {
                final String format = getValue(0);
                return new DecimalFormat(format).format(getDoubleValue(1));
            } catch (final Exception e) {
                return "?";
            }
        }
    }

    public static class ValueFile extends ValueComplex {
        private static int ticks = 0;

        private Map<String, String> cache = new HashMap<String, String>();

        @Override
        public boolean isValidSize() {
            return this.values.size() == 1;
        }

        @Override
        public String getValue() {
            final String filename = getValue(0);

            if (ticks == 0) {
                if (this.cache.size() > 16) {
                    Reference.logger.trace("Clearing file cache...");
                    this.cache.clear();
                }

                final File file = new File(InGameInfoCore.INSTANCE.getConfigDirectory(), filename);
                if (file.exists()) {
                    this.cache.put(filename, getLine(file));
                }
            }

            final String line = this.cache.get(filename);
            if (line != null) {
                return line;
            }

            return "";
        }

        private String getLine(final File file) {
            try {
                final FileReader fileReader = new FileReader(file);
                final BufferedReader reader = new BufferedReader(fileReader);

                final String line = reader.readLine();

                reader.close();

                if (line.startsWith("\uFEFF")) {
                    return line.substring(1);
                }

                return line;
            } catch (final Exception e) {
                Reference.logger.error("", e);
            }

            return "";
        }

        public static void tick() {
            ticks = (ticks + 1) % (ConfigurationHandler.fileInterval * 20);
        }
    }

    public static class ValueIcon extends ValueComplex {
        @Override
        public boolean isValidSize() {
            return this.values.size() == 1 || this.values.size() == 2 || this.values.size() == 5 || this.values.size() == 7 || this.values.size() == 11;
        }

        @Override
        public String getValue() {
            try {
                final int size = this.values.size();
                final ResourceLocation what = new ResourceLocation(getValue(0));

                if (size == 1 || size == 2) {
                    final InfoItem item;
                    ItemStack itemStack;

                    int metadata = 0;
                    if (size == 2) {
                        metadata = getIntValue(1);
                        // TODO: this needs a better workaround
                        final Block block = Block.REGISTRY.getObject(what);
                        if (block == Blocks.DOUBLE_PLANT) {
                            metadata &= 7;
                        }
                    }

                    itemStack = new ItemStack(Item.REGISTRY.getObject(what), 1, metadata);
                    if (itemStack.getItem() != null) {
                        item = new InfoItem(itemStack);
                        info.add(item);
                        return Tag.getIconTag(item);
                    }

                    itemStack = new ItemStack(Block.REGISTRY.getObject(what), 1, metadata);
                    if (itemStack.getItem() != null) {
                        item = new InfoItem(itemStack);
                        info.add(item);
                        return Tag.getIconTag(item);
                    }
                }

                final InfoIcon icon = new InfoIcon(what);
                int index = 0;

                if (size == 5 || size == 11) {
                    final int displayX = getIntValue(++index);
                    final int displayY = getIntValue(++index);
                    final int displayWidth = getIntValue(++index);
                    final int displayHeight = getIntValue(++index);
                    icon.setDisplayDimensions(displayX, displayY, displayWidth, displayHeight);
                }

                if (size == 7 || size == 11) {
                    final int iconX = getIntValue(++index);
                    final int iconY = getIntValue(++index);
                    final int iconWidth = getIntValue(++index);
                    final int iconHeight = getIntValue(++index);
                    final int textureWidth = getIntValue(++index);
                    final int textureHeight = getIntValue(++index);
                    icon.setTextureData(iconX, iconY, iconWidth, iconHeight, textureWidth, textureHeight);
                }

                info.add(icon);
                return Tag.getIconTag(icon);
            } catch (final Exception e) {
                return "?";
            }
        }
    }

    public static void register() {
        ValueRegistry.INSTANCE.register(new ValueOperation().setName("op").setAliases("operation"));
        ValueRegistry.INSTANCE.register(new ValueConcat().setName("concat"));
        ValueRegistry.INSTANCE.register(new ValueMax().setName("max").setAliases("maximum"));
        ValueRegistry.INSTANCE.register(new ValueMin().setName("min").setAliases("minimum"));
        ValueRegistry.INSTANCE.register(new ValueItemQuantity().setName("itemquantity"));
        ValueRegistry.INSTANCE.register(new ValueTranslate().setName("trans").setAliases("translate"));
        ValueRegistry.INSTANCE.register(new ValueFormattedTime().setName("formattedtime").setAliases("rltimef"));
        ValueRegistry.INSTANCE.register(new ValueFormattedNumber().setName("formattednumber"));
        ValueRegistry.INSTANCE.register(new ValueIcon().setName("icon").setAliases("img", "image"));
        ValueRegistry.INSTANCE.register(new ValueFile().setName("file"));
    }
}
import com.github.lunatrius.ingameinfo.client.gui.overlay.Info;
import com.github.lunatrius.ingameinfo.client.gui.overlay.InfoText;
import com.github.lunatrius.ingameinfo.handler.ConfigurationHandler;
import com.github.lunatrius.ingameinfo.parser.IParser;
import com.github.lunatrius.ingameinfo.parser.json.JsonParser;
import com.github.lunatrius.ingameinfo.parser.text.TextParser;
import com.github.lunatrius.ingameinfo.parser.xml.XmlParser;
import com.github.lunatrius.ingameinfo.printer.IPrinter;
import com.github.lunatrius.ingameinfo.printer.json.JsonPrinter;
import com.github.lunatrius.ingameinfo.printer.text.TextPrinter;
import com.github.lunatrius.ingameinfo.printer.xml.XmlPrinter;
import com.github.lunatrius.ingameinfo.reference.Names;
import com.github.lunatrius.ingameinfo.reference.Reference;
import com.github.lunatrius.ingameinfo.tag.Tag;
import com.github.lunatrius.ingameinfo.value.Value;
import com.github.lunatrius.ingameinfo.value.ValueComplex;
import net.minecraft.client.Minecraft;
import net.minecraft.client.entity.EntityPlayerSP;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.gui.ScaledResolution;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.resources.IResource;
import net.minecraft.profiler.Profiler;
import net.minecraft.util.ResourceLocation;
import net.minecraft.world.World;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
            this.profiler.endStartSection(alignment.toString().toLowerCase(Locale.ENGLISH));
            final List<List<Value>> lines = this.format.get(alignment);

            if (lines == null) {
                continue;
            }

            final FontRenderer fontRenderer = this.minecraft.fontRenderer;
            final List<Info> queue = new ArrayList<Info>();

            for (final List<Value> line : lines) {
                String str = "";

                this.infoItemQueue.clear();
                this.profiler.startSection("taggathering");
                for (final Value value : line) {
                    str += getValue(value);
                }
                this.profiler.endSection();

                if (!str.isEmpty()) {
                    final String processed = str.replaceAll("\\{ICON\\|( *)\\}", "$1");

                    x = alignment.getX(scaledWidth, fontRenderer.getStringWidth(processed));
                    final InfoText text = new InfoText(fontRenderer, processed, x, 0);

                    if (this.infoItemQueue.size() > 0) {
                        MATCHER.reset(str);

                        for (int i = 0; i < this.infoItemQueue.size() && MATCHER.find(); i++) {
                            final Info item = this.infoItemQueue.get(i);
                            item.x = fontRenderer.getStringWidth(str.substring(0, MATCHER.start()));
                            text.children.add(item);

                            str = str.replaceFirst(Pattern.quote(MATCHER.group(0)), MATCHER.group(1));
                            MATCHER.reset(str);
                        }
                    }
                    queue.add(text);
                }
            }

            y = alignment.getY(scaledHeight, queue.size() * (fontRenderer.FONT_HEIGHT + 1));
            for (final Info item : queue) {
                item.y = y;
                this.info.add(item);
                y += fontRenderer.FONT_HEIGHT + 1;
            }

            this.info.addAll(queue);
        }
        this.profiler.endSection();
        this.profiler.endSection();

        Tag.releaseResources();
        ValueComplex.ValueFile.tick();
    }

    public void onTickRender() {
        GlStateManager.color(1.0f, 1.0f, 1.0f, 1.0f);
        GlStateManager.scale(ConfigurationHandler.scale, ConfigurationHandler.scale, ConfigurationHandler.scale);

        for (final Info info : this.info) {
            info.draw();
        }

        GlStateManager.scale(1.0f / ConfigurationHandler.scale, 1.0f / ConfigurationHandler.scale, 1.0f / ConfigurationHandler.scale);
        GlStateManager.color(1.0f, 1.0f, 1.0f, 1.0f);
    }

    public boolean loadConfig(final String filename) {
        return setConfigFile(filename) && reloadConfig();
    }

    public boolean reloadConfig() {
        this.info.clear();
        this.infoItemQueue.clear();
        this.format.clear();

        if (this.parser == null) {
            return false;
        }

        final InputStream inputStream = getInputStream();
        if (inputStream == null) {
            return false;
        }

        if (this.parser.load(inputStream) && this.parser.parse(this.format)) {
            return true;
        }

        this.format.clear();
        return false;
    }

    private InputStream getInputStream() {
        InputStream inputStream = null;

        try {
            if (this.configFile != null && this.configFile.exists()) {
                Reference.logger.debug("Loading file config...");
                inputStream = new FileInputStream(this.configFile);
            } else {
                Reference.logger.debug("Loading default config...");
                final ResourceLocation resourceLocation = new ResourceLocation("ingameinfo", Names.Files.FILE_XML.toLowerCase(Locale.ENGLISH));
                final IResource resource = this.minecraft.getResourceManager().getResource(resourceLocation);
                inputStream = resource.getInputStream();
            }
        } catch (final Exception e) {
            Reference.logger.error("", e);
        }

        return inputStream;
    }

    public boolean saveConfig(final String filename) {
        IPrinter printer = null;
        final File file = new File(this.configDirectory, filename);
        if (filename.endsWith(Names.Files.EXT_XML)) {
            printer = new XmlPrinter();