/* UtilLib - Copyright (c) 2015-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.instruction;

import java.util.AbstractList;
import java.util.ArrayList;
import java.util.List;


/**
 *
 * @author Vincent Calame
 */
public final class InstructionsParser {

    private InstructionsParser() {

    }

    public static Instructions parse(String s) {
        PartParser partParser = new PartParser();
        List<Instructions.Part> partList = new ArrayList<Instructions.Part>();
        int start = 0;
        while (true) {
            int result = partParser.parse(s, start);
            partParser.flush(partList);
            if (result == -1) {
                break;
            } else {
                start = result;
            }
        }
        Instructions.Part[] array = partList.toArray(new Instructions.Part[partList.size()]);
        return new InternalInstructions(array);
    }


    private static class PartParser {

        private StringBuilder buf = new StringBuilder();
        private boolean onLiteral = true;

        private int parse(String text, int start) {
            if (onLiteral) {
                return parseLiteral(text, start);
            } else {
                return parseInstruction(text, start);
            }
        }

        private int parseLiteral(String text, int start) {
            int length = text.length();
            for (int i = start; i < length; i++) {
                char carac = text.charAt(i);
                if (carac == '{') {
                    return i + 1;
                } else if (carac == '\\') {
                    if (i == (length - 1)) {
                        buf.append('\\');
                    } else {
                        char next = text.charAt(i + 1);
                        switch (next) {
                            case 'n':
                                buf.append("\n");
                                break;
                            case 't':
                                buf.append("\t");
                                break;
                            case 'r':
                                buf.append("\r");
                                break;
                            default:
                                buf.append(next);
                        }
                        i++;
                    }
                } else {
                    buf.append(carac);
                }
            }
            return -1;
        }

        private int parseInstruction(String text, int start) {
            boolean onQuote = false;
            int length = text.length();
            for (int i = start; i < length; i++) {
                char carac = text.charAt(i);
                if (onQuote) {
                    buf.append(carac);
                    if (carac == '"') {
                        onQuote = false;
                    } else if (carac == '\\') {
                        if (i < (length - 1)) {
                            buf.append(text.charAt(i + 1));
                            i++;
                        }
                    }
                } else {
                    if (carac == '}') {
                        return i + 1;
                    } else {
                        buf.append(carac);
                        if (carac == '"') {
                            onQuote = true;
                        }
                    }
                }

            }
            return -1;
        }

        private void flush(List<Instructions.Part> partList) {
            String text = buf.toString();
            boolean currentOnLiteral = onLiteral;
            onLiteral = !onLiteral;
            buf = new StringBuilder();
            if (text.length() == 0) {
                return;
            }
            if (currentOnLiteral) {
                partList.add(new InternalLiteralPart(text));
            } else {
                InternalErrorHandler errorHandler = new InternalErrorHandler();
                Instruction instruction = InstructionParser.parse(text, errorHandler);
                if (instruction != null) {
                    partList.add(new InternalInstructionPart(instruction));
                }
                if (errorHandler.hasError()) {
                    partList.add(new InternalLiteralPart(errorHandler.getErrorText()));
                }
            }
        }

    }


    private static class InternalErrorHandler implements InstructionErrorHandler {

        private final StringBuilder buf = new StringBuilder();

        @Override
        public void invalidAsciiCharacterError(String part, int row, int col) {
            buf.append(" (");
            buf.append("invalidAscii: ");
            buf.append(part);
            buf.append(")");
        }

        @Override
        public void invalidEndCharacterError(String part, int row, int col) {
            buf.append(" (");
            buf.append("invalidEnd: ");
            buf.append(part);
            buf.append(")");
        }

        @Override
        public void invalidSeparatorCharacterError(String part, int row, int col) {
            buf.append(" (");
            buf.append("invalidSeparator: ");
            buf.append(part);
            buf.append(")");
        }

        private boolean hasError() {
            return (buf.length() > 0);
        }

        private String getErrorText() {
            buf.append(" ");
            return buf.toString();
        }

    }


    private static class InternalLiteralPart implements Instructions.LiteralPart {

        private final String text;

        private InternalLiteralPart(String text) {
            this.text = text;
        }

        @Override
        public String getText() {
            return text;
        }

    }


    private static class InternalInstructionPart implements Instructions.InstructionPart {

        private final Instruction instruction;

        private InternalInstructionPart(Instruction instruction) {
            this.instruction = instruction;
        }

        @Override
        public Instruction getInstruction() {
            return instruction;
        }

    }


    private static class InternalInstructions extends AbstractList<Instructions.Part> implements Instructions {

        private final Instructions.Part[] array;

        private InternalInstructions(Instructions.Part[] array) {
            this.array = array;
        }

        @Override
        public int size() {
            return array.length;
        }

        @Override
        public Part get(int i) {
            return array[i];
        }

    }

}
