/* UtilLib - Copyright (c) 2011-2017 Vincent Calame - Exemole
 * Logiciel libre donné sous triple licence :
 * 1) selon les termes de la CeCILL V2
 * 2) selon les termes de l’EUPL V.1.1
 * 3) selon les termes de la GNU GPLv3
 * Voir le fichier licences.txt
 */


package net.mapeadores.util.html;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import net.mapeadores.util.instruction.Argument;
import net.mapeadores.util.instruction.Instruction;
import net.mapeadores.util.instruction.InstructionResolver;
import net.mapeadores.util.instruction.InstructionUtils;
import net.mapeadores.util.logging.ErrorMessageException;


/**
 *
 * @author Vincent Calame
 */
public class GabaritHtmlProducer implements HtmlProducer {

    private final String gabaritString;
    private final InstructionResolver instructionResolver;

    public GabaritHtmlProducer(String gabaritString, InstructionResolver instructionResolver) {
        this.gabaritString = gabaritString;
        this.instructionResolver = instructionResolver;
    }

    @Override
    public void writeHtml(Appendable appendable) throws IOException {
        resolve(gabaritString, instructionResolver, appendable);
    }

    private static void resolve(String gabaritString, InstructionResolver instructionResolver, Appendable appendable) throws IOException {
        BufferedReader bufreader = new BufferedReader(new StringReader(gabaritString));
        InternalParser parser = new InternalParser();
        String ligne;
        while ((ligne = bufreader.readLine()) != null) {
            parser.testNewLine(ligne);
        }
        resolve(parser.toObjectArray(), instructionResolver, appendable);
    }

    private static void resolve(Object[] objectArray, InstructionResolver instructionResolver, Appendable appendable) throws IOException {
        for (Object element : objectArray) {
            if (element instanceof String) {
                appendable.append((String) element);
            } else {
                Instruction instruction = (Instruction) element;
                try {
                    Object obj = instructionResolver.resolve(instruction);
                    if (obj != null) {
                        appendable.append(obj.toString());
                    }
                } catch (ErrorMessageException eme) {

                }
            }
        }
    }


    private static class InternalParser {

        private final List<Object> objectList = new ArrayList<Object>();
        private StringBuilder currentStringBuilder = new StringBuilder();
        private boolean firstLine = true;

        private void testNewLine(String line) {
            if (firstLine) {
                firstLine = false;
            } else {
                currentStringBuilder.append('\n');
            }
            int openIndex = line.indexOf('$');
            if (openIndex == -1) {
                currentStringBuilder.append(line);
                return;
            }
            int closeIndex = line.indexOf('$', openIndex + 1);
            if (closeIndex == -1) {
                currentStringBuilder.append(line);
                return;
            }
            Instruction instruction = parseInclusion(line, openIndex + 1, closeIndex);
            if (instruction != null) {
                currentStringBuilder.append(line, 0, openIndex);
                flush();
                objectList.add(instruction);
                testRemainInLine(line, closeIndex + 1);
            } else {
                currentStringBuilder.append(line, 0, closeIndex);
                testRemainInLine(line, closeIndex);
            }
        }

        private void testRemainInLine(String line, int startIndex) {
            int openIndex = line.indexOf('$', startIndex);
            if (openIndex == -1) {
                currentStringBuilder.append(line, startIndex, line.length());
                return;
            }
            int closeIndex = line.indexOf('$', openIndex + 1);
            if (closeIndex == -1) {
                currentStringBuilder.append(line, startIndex, line.length());
                return;
            }
            Instruction instruction = parseInclusion(line, openIndex + 1, closeIndex);
            if (instruction != null) {
                currentStringBuilder.append(line, startIndex, openIndex);
                flush();
                objectList.add(instruction);
                testRemainInLine(line, closeIndex + 1);
            } else {
                currentStringBuilder.append(line, 0, closeIndex);
                testRemainInLine(line, closeIndex);
            }
        }

        private void flush() {
            if (currentStringBuilder.length() > 0) {
                objectList.add(currentStringBuilder.toString());
                currentStringBuilder = new StringBuilder();
            }
        }

        private Instruction parseInclusion(String s, int startIndex, int endIndex) {
            if (startIndex == endIndex) {
                return null;
            }
            char firstChar = s.charAt(startIndex);
            if (!testFirstChar(firstChar)) {
                return null;
            }
            StringBuilder nameBuilder = new StringBuilder();
            nameBuilder.append(firstChar);
            int argumentIndex = -1;
            for (int i = (startIndex + 1); i < endIndex; i++) {
                char c = s.charAt(i);
                if (c == ':') {
                    argumentIndex = i + 1;
                    break;
                }
                if (!testNameChar(c)) {
                    return null;
                }
                nameBuilder.append(c);
            }
            List<Argument> argumentList = new ArrayList<Argument>();
            argumentList.add(InstructionUtils.toArgument(nameBuilder.toString(), null));
            while (argumentIndex != -1) {
                StringBuilder argumentBuilder = new StringBuilder();
                boolean newArgument = false;
                for (int i = argumentIndex; i < endIndex; i++) {
                    char c = s.charAt(i);
                    if (c == ':') {
                        argumentIndex = i + 1;
                        newArgument = true;
                        break;
                    }
                    if (!testArgumentChar(c)) {
                        return null;
                    }
                    argumentBuilder.append(c);
                }
                argumentList.add(InstructionUtils.toArgument(argumentBuilder.toString(), null));
                if (!newArgument) {
                    break;
                }
            }
            return InstructionUtils.toInstruction(argumentList);
        }

        private Object[] toObjectArray() {
            flush();
            Object[] gabaritElementArray = new Object[objectList.size()];
            return gabaritElementArray;
        }

    }

    private static boolean testFirstChar(char c) {
        if ((c >= 'A') && (c <= 'Z')) {
            return true;
        }
        if ((c >= 'a') && (c <= 'z')) {
            return true;
        }
        return false;
    }

    private static boolean testNameChar(char c) {
        if (c == '_') {
            return true;
        }
        if ((c >= 'A') && (c <= 'Z')) {
            return true;
        }
        if ((c >= 'a') && (c <= 'z')) {
            return true;
        }
        if ((c >= '0') && (c <= '9')) {
            return true;
        }
        return false;
    }

    private static boolean testArgumentChar(char c) {
        if ((c >= 'A') && (c <= 'Z')) {
            return true;
        }
        if ((c >= 'a') && (c <= 'z')) {
            return true;
        }
        if ((c >= '0') && (c <= '9')) {
            return true;
        }
        switch (c) {
            case '_':
            case '/':
            case '-':
            case '.':
            case ',':
            case ';':
            case '|':
                return true;
            default:
                return false;
        }
    }

}
