/* FichothequeLib_Tools - Copyright (c) 2007-2019 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.fichotheque.tools.format;

import java.text.ParseException;
import java.util.List;
import net.fichotheque.corpus.metadata.FieldKey;
import net.fichotheque.format.FichothequeFormatConstants;
import net.fichotheque.format.FichothequeFormatDef;
import net.fichotheque.format.FormatSourceKey;
import net.mapeadores.util.format.Calcul;
import net.mapeadores.util.format.FormatConstants;
import net.mapeadores.util.format.FormatUtils;
import net.mapeadores.util.instruction.Argument;
import net.mapeadores.util.instruction.Instruction;
import net.mapeadores.util.instruction.InstructionErrorHandler;
import net.mapeadores.util.instruction.InstructionParser;
import net.mapeadores.util.localisation.LocalisationUtils;
import net.mapeadores.util.logging.MessageHandler;
import net.mapeadores.util.text.StringUtils;


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

    private final MessageHandler messageHandler;
    private FichothequeFormatDefBuilder formatDefBuilder = null;

    public static FichothequeFormatDef parse(String[] formatArray, MessageHandler errorHandler) {
        int length = formatArray.length;
        if (length == 0) {
            throw new IllegalArgumentException("formatArray.length = 0");
        }
        String formatSource = formatArray[0];
        String formatPattern = (length > 1) ? formatArray[1] : null;
        String formatInstruction = (length > 2) ? formatArray[2] : null;
        return parse(formatSource, formatPattern, formatInstruction, errorHandler);
    }

    public static FichothequeFormatDef parse(List<String> formatList, MessageHandler errorHandler) {
        int length = formatList.size();
        if (length == 0) {
            throw new IllegalArgumentException("formatArray.length = 0");
        }
        String formatSource = formatList.get(0);
        String formatPattern = (length > 1) ? formatList.get(1) : null;
        String formatInstruction = (length > 2) ? formatList.get(2) : null;
        return parse(formatSource, formatPattern, formatInstruction, errorHandler);
    }

    public static FichothequeFormatDef parse(String formatSource, String formatPattern, String formatInstruction, MessageHandler errorHandler) {
        FichothequeFormatDefParser ficheFormatDefParser = new FichothequeFormatDefParser(errorHandler);
        return ficheFormatDefParser.parseFormatDef(formatSource, formatPattern, formatInstruction);
    }

    private FichothequeFormatDefParser(MessageHandler errorHandler) {
        this.messageHandler = errorHandler;
    }

    private FichothequeFormatDef parseFormatDef(String formatSource, String formatPattern, String formatInstruction) {
        boolean done = initFormatDef(formatSource);
        if (!done) {
            return null;
        }
        initFormatPattern(formatDefBuilder, formatPattern);
        Instruction instruction = getInstruction(formatInstruction);
        if (instruction != null) {
            for (Argument argument : instruction) {
                initArgument(argument);
            }
        }
        return formatDefBuilder.toFichothequeFormatDef();

    }

    private void initFormatPattern(FichothequeFormatDefBuilder formatDefBuilder, String formatPattern) {
        if (formatPattern == null) {
            return;
        }
        int idx = formatPattern.indexOf("||");
        if (idx == -1) {
            formatDefBuilder.addFormatPattern(formatPattern);
        } else {
            formatDefBuilder.addFormatPattern(formatPattern.substring(0, idx));
            initFormatPattern(formatDefBuilder, formatPattern.substring(idx + 2));
        }

    }

    private void initArgument(Argument arg) {
        String key = arg.getKey();
        String value = arg.getValue();
        if (value == null) {
            value = "";
        }
        boolean done;
        try {
            done = initCommonArgument(key, value);
        } catch (IllegalArgumentException iae) {
            addInstructionError("_ error.wrong.argumentvalue", key, value);
            return;
        }
        if (done) {
            return;
        }
        switch (key) {
            case FichothequeFormatConstants.NOITEM_PARAMKEY:
            case FichothequeFormatConstants.ONLYITEM_PARAMKEY:
            case FichothequeFormatConstants.IDSORT_PARAMKEY:
                formatDefBuilder.putBooleanValue(key, true);
                return;
            case FichothequeFormatConstants.DEFAULTPROP_PARAMKEY:
                if (value.isEmpty()) {
                    addInstructionError("_ error.empty.argumentvalue", key);
                } else {
                    try {
                        FieldKey propFieldKey = FieldKey.parse(value);
                        try {
                            formatDefBuilder.setDefaultPropKey(propFieldKey);
                        } catch (IllegalArgumentException iae) {
                            addInstructionError("_ error.wrong.argumentvalue", key, value);
                        }
                    } catch (ParseException pe) {
                        addInstructionError("_ error.wrong.argumentvalue", key, value);
                    }
                }
                return;
            default:
                addInstructionWarning("_ error.unknown.argumentkey", key);
        }
    }

    private boolean initCommonArgument(String key, String value) throws NumberFormatException, IllegalArgumentException {
        switch (key) {
            case FormatConstants.FIXEDEMPTY_PARAMKEY:
            case FormatConstants.UNIQUETEST_PARAMKEY:
            case FormatConstants.EMPTYTONULL_PARAMKEY:
            case FormatConstants.JSONARRAY_PARAMKEY:
            case FormatConstants.COLUMNSUM_PARAMKEY:
                formatDefBuilder.putBooleanValue(key, true);
                return true;
            case FormatConstants.GLOBALSELECT_PARAMKEY:
                formatDefBuilder.putBooleanValue(key, isTrue(value));
                return true;
            case FormatConstants.DEFAULTVALUE_PARAMKEY:
            case FormatConstants.SEPARATOR_PARAMKEY:
                formatDefBuilder.putStringValue(key, value);
                return true;
            case FormatConstants.PREFIX_PARAMKEY:
            case FormatConstants.SUFFIX_PARAMKEY:
            case FormatConstants.POSTTRANSFORM_PARAMKEY:
                if (testValue(key, value)) {
                    formatDefBuilder.putStringValue(key, value);
                }
                return true;
            case FormatConstants.MAXLENGTH_PARAMKEY:
            case FormatConstants.FIXEDLENGTH_PARAMKEY:
            case FormatConstants.LIMIT_PARAMKEY:
                if (testValue(key, value)) {
                    int intValue = Integer.parseInt(value);
                    formatDefBuilder.putIntValue(key, intValue);
                }
                return true;
            case FormatConstants.POSITION_PARAMKEY:
                if (testValue(key, value)) {
                    if (value.equals(FormatConstants.LAST_PARAMVALUE)) {
                        formatDefBuilder.putIntValue(key, FormatConstants.MAX_POSITION);
                    } else {
                        int position = Integer.parseInt(value);
                        formatDefBuilder.putIntValue(key, position - 1);
                    }
                }
                return true;
        }
        if (key.equals(FormatConstants.CAST_PARAMKEY)) {
            if (testValue(key, value)) {
                short castType = FormatUtils.castTypeToShort(value);
                formatDefBuilder.setCastType(castType);
            }
        } else if (key.equals(FormatConstants.FIXEDCHAR_PARAMKEY)) {
            if (testValue(key, value)) {
                if (value.length() > 0) {
                    formatDefBuilder.setFixedChar(value.charAt(0));
                }
            }
        } else if (key.equals(FormatConstants.CALCUL_PARAMKEY)) {
            if (testValue(key, value)) {
                try {
                    Calcul calcul = Calcul.parse(value);
                    formatDefBuilder.setCalcul(calcul);
                } catch (ParseException pe) {
                    addInstructionError("_ error.wrong.argumentvalue", key, value);
                }
            }
        } else if (key.equals(FormatConstants.SUM_PARAMKEY)) {
            short sumCastType;
            if (value.isEmpty()) {
                sumCastType = FormatConstants.NO_CAST;
            } else {
                sumCastType = FormatUtils.castTypeToShort(value);
            }
            formatDefBuilder.setSum(true, sumCastType);
        } else if (key.equals(FormatConstants.FORMULA_PARAMKEY)) {
            short formulaCastType;
            if (value.isEmpty()) {
                formulaCastType = FormatConstants.NO_CAST;
            } else {
                formulaCastType = FormatUtils.castTypeToShort(value);
            }
            formatDefBuilder.setFormula(true, formulaCastType);
        } else if ((key.startsWith(FormatConstants.SEPARATOR_PARAMKEY)) && (key.length() > 3)) {
            int idx = key.indexOf('_');
            if (idx == -1) {
                try {
                    int pos = Integer.parseInt(key.substring(3));
                    formatDefBuilder.setInternalSeparator(pos - 1, value);
                } catch (NumberFormatException nfe) {
                    return false;
                }
            } else {
                try {
                    int pos1 = Integer.parseInt(key.substring(3, idx));
                    int pos2 = Integer.parseInt(key.substring(idx + 1));
                    formatDefBuilder.setSourceSeparator(pos1 - 1, pos2 - 1, value);
                } catch (NumberFormatException nfe) {
                    return false;
                }
            }
        } else {
            return false;
        }
        return true;
    }

    private boolean testValue(String key, String value) {
        if (value.isEmpty()) {
            addInstructionError("_ error.empty.argumentvalue", key);
            return false;
        } else {
            return true;
        }
    }

    private Instruction getInstruction(String formatInstruction) {
        if (formatInstruction == null) {
            return null;
        }
        formatInstruction = formatInstruction.trim();
        if (formatInstruction.length() == 0) {
            return null;
        }
        return InstructionParser.parse(formatInstruction, new InternalInstructionErrorHandler());
    }

    private boolean initFormatDef(String formatSource) {
        formatSource = checkFormatSource(formatSource);
        if (formatSource == null) {
            return false;
        }
        String[] tokens = StringUtils.getTechnicalTokens(formatSource, true);
        int length = tokens.length;
        if (length == 0) {
            addSourceError("_ error.empty.formatsource");
            return false;
        }
        formatDefBuilder = new FichothequeFormatDefBuilder();
        if (length == 1) {
            try {
                FormatSourceKey formatSourceKey = FormatSourceKey.parse(formatSource);
                formatDefBuilder.addSource(formatSourceKey);
                return true;
            } catch (ParseException pe) {
                addSourceError("_ error.wrong.formatsource", formatSource);
                return false;
            }
        }
        boolean withError = false;
        for (int i = 0; i < length; i++) {
            boolean done;
            try {
                FormatSourceKey formatSourceKey = FormatSourceKey.parse(tokens[i]);
                formatDefBuilder.addSource(formatSourceKey);
                done = true;
            } catch (ParseException pe) {
                addSourceError("_ error.wrong.formatsource", tokens[i]);
                done = false;
            }
            if (!done) {
                withError = true;
            }
        }
        return !withError;
    }

    private String checkFormatSource(String formatSource) {
        if (formatSource == null) {
            addSourceError("_ error.empty.formatsource");
        } else {
            formatSource = formatSource.trim();
            if (formatSource.length() == 0) {
                addSourceError("_ error.empty.formatsource");

                formatSource = null;
            }
        }
        return formatSource;
    }

    private void addInstructionError(String messageKey, Object... messageValues) {
        messageHandler.addMessage(FormatConstants.SEVERE_INSTRUCTION, LocalisationUtils.toMessage(messageKey, messageValues));
    }

    private void addSourceError(String messageKey, Object... messageValues) {
        messageHandler.addMessage(FormatConstants.SEVERE_SOURCE, LocalisationUtils.toMessage(messageKey, messageValues));
    }

    private void addInstructionWarning(String messageKey, Object... messageValues) {
        messageHandler.addMessage(FormatConstants.WARNING_INSTRUCTION, LocalisationUtils.toMessage(messageKey, messageValues));
    }

    private static boolean isTrue(String value) {
        if (value == null) {
            return true;
        }
        if (value.isEmpty()) {
            return true;
        }
        if (value.equals("0")) {
            return false;
        }
        value = value.toLowerCase();
        if (value.equals("no")) {
            return false;
        }
        if (value.equals("false")) {
            return false;
        }
        return true;
    }


    private class InternalInstructionErrorHandler implements InstructionErrorHandler {

        private InternalInstructionErrorHandler() {
        }

        @Override
        public void invalidAsciiCharacterError(String part, int row, int col) {
            addInstructionError("_ error.wrong.character_notascii", part);
        }

        @Override
        public void invalidEndCharacterError(String part, int row, int col) {
            addInstructionError("_ error.wrong.character_end", part);
        }

        @Override
        public void invalidSeparatorCharacterError(String part, int row, int col) {
            addInstructionError("_ error.wrong.character_separator", part);
        }

    }

}
