/* UtilLib - Copyright (c) 2007-2022 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.text.coupleparser;

import java.util.ArrayList;
import java.util.List;
import net.mapeadores.util.exceptions.SwitchException;
import net.mapeadores.util.text.Idalpha;
import net.mapeadores.util.text.StringUtils;


/**
 *
 * @author Vincent Calame
 */
public class CoupleParser {

    public final static String ALLOWED_SEPARATOR = "\n§\u00B6$";
    private char separator = '\n';
    public final static short INTEGER_MODE = 1;
    public final static short IDALPHA_MODE = 2;
    private final short mode;
    private final static int START_STEP = 0;
    private final static int INTEGER_STEP = 1;
    private final static int IDALPHA_STEP = 11;
    private final static int IDALPHA_SPACEQUESTION_STEP = 12;
    private final static int SEPARATOR_STEP = 2;
    private final static int TRIM_STEP = 3;
    private final static int VALUE_STEP = 4;
    private final static int ERROR_STEP = -1;

    public CoupleParser(short mode) {
        this.mode = mode;
    }

    public void setSeparator(char separator) {
        if (ALLOWED_SEPARATOR.indexOf(separator) == -1) {
            throw new IllegalArgumentException("wrong separator = " + separator);
        }
        this.separator = separator;
    }

    public Couple[] parse(CharSequence charSequence) {
        int length = charSequence.length();
        LineBuffer lineBuffer = null;
        if (mode == INTEGER_MODE) {
            lineBuffer = new IntegerLineBuffer();
        } else if (mode == IDALPHA_MODE) {
            lineBuffer = new IdalphaLineBuffer();
        }
        for (int i = 0; i < length; i++) {
            char carac = charSequence.charAt(i);
            if (carac == '\\') {
                if (i < (length - 1)) {
                    char next = charSequence.charAt(i + 1);
                    if (next == '\r') {
                        lineBuffer.incrementLineNumber();
                        if (separator != '\n') {
                            lineBuffer.appendChar('\\');
                        }
                        lineBuffer.appendChar('\r');
                        if (i < (length - 2)) {
                            if (charSequence.charAt(i + 2) == '\n') {
                                lineBuffer.appendChar('\n');
                                i++;
                            }
                        }
                    } else if (next == '\n') {
                        lineBuffer.incrementLineNumber();
                        if (separator != '\n') {
                            lineBuffer.appendChar('\\');
                        }
                        lineBuffer.appendChar('\n');
                    } else if (next == separator) {
                        lineBuffer.appendChar(next);
                    } else {
                        lineBuffer.appendChar('\\');
                        lineBuffer.appendChar(next);
                    }
                    i++;
                } else {
                    lineBuffer.appendChar('\\');
                }
            } else if (carac == '\r') {
                if (separator == '\n') {
                    if (i < (length - 1)) {
                        if (charSequence.charAt(i + 1) == '\n') {
                            i++;
                        }
                    }
                    lineBuffer.flushLine();
                } else {
                    if (i < (length - 1)) {
                        lineBuffer.appendChar('\r');
                        if (charSequence.charAt(i + 1) == '\n') {
                            lineBuffer.appendChar('\n');
                            i++;
                        }
                    }
                }
                lineBuffer.incrementLineNumber();
            } else if (carac == '\n') {
                if (separator == '\n') {
                    lineBuffer.flushLine();
                } else {
                    lineBuffer.appendChar('\n');
                }
                lineBuffer.incrementLineNumber();
            } else if (carac == separator) {
                lineBuffer.flushLine();
            } else {
                lineBuffer.appendChar(carac);
            }
        }
        lineBuffer.flushLine();
        return lineBuffer.toCoupleArray();
    }


    private abstract class LineBuffer {

        int lineNumber = 1;
        List<Couple> list = new ArrayList<Couple>();
        StringBuilder valueBuffer = new StringBuilder();
        int step = START_STEP;
        int startLineNumber = 0;

        public abstract void flushLine();

        public Couple[] toCoupleArray() {
            int size = list.size();
            Couple[] result = new Couple[size];
            for (int i = 0; i < size; i++) {
                Couple couple = list.get(i);
                result[i] = couple;
            }
            return result;
        }

        public abstract void appendChar(char c);

        public void incrementLineNumber() {
            lineNumber++;
        }

        protected void flushLine(Couple couple) {
            list.add(couple);
            step = START_STEP;
            valueBuffer = new StringBuilder();
        }

        protected void reinitStartLineNumber() {
            startLineNumber = lineNumber;
        }

        protected void testSeparator(char c) {
            if (Character.isWhitespace(c)) {
                step = SEPARATOR_STEP;
            } else {
                if (!isCommonSeparator(c)) {
                    valueBuffer.append(c);
                    step = VALUE_STEP;
                } else {
                    step = TRIM_STEP;
                }
            }
        }

        protected void appendInValue(char c) {
            valueBuffer.append(c);
        }

        protected void testTrim(char c) {
            if (!Character.isWhitespace(c)) {
                valueBuffer.append(c);
                step = VALUE_STEP;
            }
        }

    }


    private class IntegerLineBuffer extends LineBuffer {

        private int integer = 0;

        private IntegerLineBuffer() {

        }

        @Override
        public void flushLine() {
            if (step == START_STEP) {
                return;
            }
            Couple couple = null;
            if (step == ERROR_STEP) {
                couple = new ErrorCouple(startLineNumber, valueBuffer.toString());
            } else {
                couple = new IntegerCouple(integer, startLineNumber, valueBuffer.toString());
            }
            flushLine(couple);
            integer = 0;
        }

        @Override
        public void appendChar(char c) {
            switch (step) {
                case START_STEP:
                    if (!Character.isWhitespace(c)) {
                        reinitStartLineNumber();
                        int firstTest = StringUtils.testNumberChar(c);
                        if (firstTest == -1) {
                            valueBuffer.append(c);
                            step = ERROR_STEP;
                        } else {
                            integer = firstTest;
                            step = INTEGER_STEP;
                        }
                    }
                    break;
                case INTEGER_STEP:
                    int test = StringUtils.testNumberChar(c);
                    if (test != - 1) {
                        integer = integer * 10 + test;
                    } else {
                        testSeparator(c);
                    }
                    break;
                case SEPARATOR_STEP:
                    testSeparator(c);
                    break;
                case TRIM_STEP:
                    testTrim(c);
                    break;
                case ERROR_STEP:
                case VALUE_STEP:
                    appendInValue(c);
                    break;
                default:
                    throw new SwitchException("step = " + step);
            }
        }

    }


    private class IdalphaLineBuffer extends LineBuffer {

        private StringBuilder idalphaBuffer = new StringBuilder();

        @Override
        public void flushLine() {
            if (step == START_STEP) {
                return;
            }
            Couple couple = null;
            if (step == ERROR_STEP) {
                couple = new ErrorCouple(startLineNumber, valueBuffer.toString());
            } else {
                couple = new IdalphaCouple(idalphaBuffer.toString(), startLineNumber, valueBuffer.toString());
            }
            flushLine(couple);
            idalphaBuffer = new StringBuilder();
        }

        @Override
        public void appendChar(char c) {
            switch (step) {
                case START_STEP:
                    if (!Character.isWhitespace(c)) {
                        reinitStartLineNumber();
                        boolean valid = Idalpha.isValidNoSpaceChar(c);
                        if (!valid) {
                            valueBuffer.append(c);
                            step = ERROR_STEP;
                        } else {
                            idalphaBuffer.append(c);
                            step = IDALPHA_STEP;
                        }
                    }
                    break;
                case IDALPHA_STEP:
                    if (c == ' ') {
                        step = IDALPHA_SPACEQUESTION_STEP;
                    } else {
                        boolean valid2 = Idalpha.isValidNoSpaceChar(c);
                        if (valid2) {
                            idalphaBuffer.append(c);
                        } else {
                            testSeparator(c);
                        }
                    }
                    break;
                case IDALPHA_SPACEQUESTION_STEP:
                    if (Idalpha.isValidNoSpaceChar(c)) {
                        idalphaBuffer.append(' ');
                        idalphaBuffer.append(c);
                        step = IDALPHA_STEP;
                    } else {
                        testSeparator(c);
                    }
                    break;
                case SEPARATOR_STEP:
                    testSeparator(c);
                    break;
                case TRIM_STEP:
                    testTrim(c);
                    break;
                case ERROR_STEP:
                case VALUE_STEP:
                    appendInValue(c);
                    break;
                default:
                    throw new SwitchException("step = " + step);
            }
        }

    }

    private static boolean isCommonSeparator(char c) {
        switch (c) {
            case ',':
            case ';':
            case '|':
            case '=':
                return true;
            default:
                return false;
        }
    }

    public static char[] getAllowedCoupleSeparateurArray() {
        return ALLOWED_SEPARATOR.toCharArray();
    }

    public static String[] getAllowedCoupleSeparateurArrayToString() {
        return StringUtils.toStringArray(ALLOWED_SEPARATOR);
    }

}
