/* FichothequeLib_Tools - Copyright (c) 2007-2025 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.exportation.table;

import java.io.IOException;
import java.io.LineNumberReader;
import java.io.Reader;
import java.io.StringReader;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import net.fichotheque.Subset;
import net.fichotheque.exportation.table.FormatColDef;
import net.fichotheque.exportation.table.FormatColDefChecker;
import net.fichotheque.exportation.table.TableDef;
import net.fichotheque.exportation.table.TableDefItem;
import net.fichotheque.exportation.table.TableExportContext;
import net.fichotheque.exportation.table.TableInclusionDef;
import net.fichotheque.exportation.table.TableInclusionResolver;
import net.fichotheque.exportation.table.TableParameterDef;
import net.fichotheque.format.FichothequeFormatDef;
import net.fichotheque.tools.format.FichothequeFormatDefParser;
import net.fichotheque.utils.TableDefUtils;
import net.mapeadores.util.exceptions.ShouldNotOccurException;
import net.mapeadores.util.format.FormatConstants;
import net.mapeadores.util.logging.LineMessageHandler;
import net.mapeadores.util.text.LabelUtils;
import net.mapeadores.util.text.Labels;


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

    private final static short START_BLOCK = 1;
    private final static short ON_BLOCK = 2;
    private final static short ON_PARAMETER = 3;
    private final static String START_PREFIX = "!!start ";
    private final static String END_PREFIX = "!!end";
    private final List<TableDefItem> defItemList = new ArrayList<TableDefItem>();
    private final LineMessageHandler lineMessageHandler;
    private final FormatColDefChecker formatColDefChecker;
    private final TableExportContext tableExportContext;
    private final Subset subset;
    private String currentColLine = null;
    private String currentFormatSource = null;
    private String currentFormatPattern = null;
    private String currentFormatInstruction = null;
    private final List<String> inclusionLineList = new ArrayList<String>();
    private String currentParameter = null;
    private StringBuilder currentParameterBuilder = null;
    private int firstLineNumber;
    private boolean ficheFormatLine = false;
    private short state = START_BLOCK;

    private TableDefParser(Subset subset, TableExportContext tableExportContext, LineMessageHandler lineMessageHandler) {
        this.subset = subset;
        this.tableExportContext = tableExportContext;
        this.lineMessageHandler = lineMessageHandler;
        if (tableExportContext != null) {
            this.formatColDefChecker = tableExportContext.getFormatColDefChecker(subset);
        } else {
            this.formatColDefChecker = null;
        }
    }

    public static TableDef parse(Reader reader, Subset subset, TableExportContext tableExportContext, LineMessageHandler lineMessageHandler, int lineDiff) throws IOException {
        LineNumberReader lineNumberReader = new LineNumberReader(reader);
        String line;
        TableDefParser tableDefParser = new TableDefParser(subset, tableExportContext, lineMessageHandler);
        while ((line = lineNumberReader.readLine()) != null) {
            int lineNumber = lineNumberReader.getLineNumber() + lineDiff;
            tableDefParser.parseLine(line, lineNumber);
        }
        tableDefParser.flush();
        tableDefParser.flushParameter();
        return TableDefUtils.toTableDef(tableDefParser.defItemList);
    }

    public static TableDef parse(String s, Subset subset, TableExportContext tableExportContext, LineMessageHandler lineMessageHandler, int lineDif) {
        try {
            StringReader reader = new StringReader(s);
            return parse(reader, subset, tableExportContext, lineMessageHandler, 0);
        } catch (IOException ioe) {
            throw new ShouldNotOccurException();
        }
    }

    public static TableDef parse(String s, LineMessageHandler lineMessageHandler, int lineDif) {
        try {
            StringReader reader = new StringReader(s);
            return parse(reader, null, null, lineMessageHandler, 0);
        } catch (IOException ioe) {
            throw new ShouldNotOccurException();
        }
    }

    private void parseLine(String line, int lineNumber) {
        line = line.trim();
        if (line.length() == 0) {
            switch (state) {
                case ON_BLOCK:
                    flush();
                    state = START_BLOCK;
                    break;
                case ON_PARAMETER:
                    currentParameterBuilder.append('\n');
                    break;
                default:
                    state = START_BLOCK;
            }
            return;
        }
        char carac = line.charAt(0);
        if (carac == '#') {
            return;
        }
        if (carac == '!') {
            switch (state) {
                case START_BLOCK:
                    if (line.startsWith(START_PREFIX)) {
                        currentParameter = line.substring(START_PREFIX.length()).trim();
                        if (currentParameter.length() == 0) {
                            lineMessageHandler.addMessage(FormatConstants.SEVERE_SYNTAX, lineNumber, "_ error.empty.tableexport.parametername");
                            currentParameter = null;
                            return;
                        } else {
                            currentParameterBuilder = new StringBuilder();
                            state = ON_PARAMETER;
                        }
                    } else {
                        addParameter(line, lineNumber);
                    }
                    break;
                case ON_BLOCK:
                    addLine(line, lineNumber);
                    break;
                case ON_PARAMETER:
                    if (line.startsWith(END_PREFIX)) {
                        state = START_BLOCK;
                        flushParameter();
                    } else {
                        currentParameterBuilder.append(line);
                        currentParameterBuilder.append('\n');
                    }
                    break;
            }
            return;
        }
        switch (state) {
            case START_BLOCK:
            case ON_BLOCK:
                addLine(line, lineNumber);
                state = ON_BLOCK;
                break;
            case ON_PARAMETER:
                currentParameterBuilder.append(line);
                currentParameterBuilder.append('\n');
                break;
        }
    }

    private void addParameter(String line, int currentLineNumber) {
        if (line.length() == 1) {
            lineMessageHandler.addMessage(FormatConstants.SEVERE_SYNTAX, currentLineNumber, "_ error.empty.tableexport.parameterline");
            return;
        }
        int idx = line.indexOf('=');
        if (idx == -1) {
            lineMessageHandler.addMessage(FormatConstants.SEVERE_SYNTAX, currentLineNumber, "_ error.empty.tableexport.parametervalue", line);
            return;
        }
        String paramName = line.substring(1, idx).trim();
        if (paramName.length() == 0) {
            lineMessageHandler.addMessage(FormatConstants.SEVERE_SYNTAX, currentLineNumber, "_ error.empty.tableexport.parametername", line);
            return;
        }
        if (paramName.charAt(0) == '!') {
            lineMessageHandler.addMessage(FormatConstants.SEVERE_SYNTAX, currentLineNumber, "_ error.wrong.tableexport.parametername", line);
            return;
        }
        String paramValue = line.substring(idx + 1).trim();
        addParameter(paramName, paramValue);
    }

    private void addLine(String line, int currentLineNumber) {
        if (currentColLine == null) {
            firstLineNumber = currentLineNumber;
            if (line.startsWith("%")) {
                if (line.length() == 1) {
                    line = "NULL";
                } else {
                    line = line.substring(1);
                }
                ficheFormatLine = false;
            } else {
                ficheFormatLine = true;
            }
            currentColLine = line;
        } else {
            if (ficheFormatLine) {
                if (currentFormatSource == null) {
                    currentFormatSource = line;
                } else if (currentFormatPattern == null) {
                    currentFormatPattern = line;
                } else if (currentFormatInstruction == null) {
                    currentFormatInstruction = line;
                } else {
                    lineMessageHandler.addMessage(FormatConstants.WARNING_SYNTAX, currentLineNumber, "_ error.wrong.tableexport.toomanylines", line);
                }
            } else {
                inclusionLineList.add(line);
            }
        }
    }

    private void flushParameter() {
        if (currentParameter != null) {
            addParameter(currentParameter, currentParameterBuilder.toString());
        }
        currentParameter = null;
        currentParameterBuilder = null;
    }

    private void flush() {
        if (currentColLine != null) {
            String colName;
            Labels customLabels;
            int idx = currentColLine.indexOf('>');
            if (idx > 0) {
                colName = currentColLine.substring(0, idx).trim();
                String instruction = currentColLine.substring(idx + 1);
                try {
                    customLabels = LabelUtils.parseInstruction(instruction);
                } catch (ParseException pe) {
                    customLabels = null;
                }
            } else {
                colName = currentColLine;
                customLabels = null;
            }
            if (ficheFormatLine) {
                if (currentFormatSource == null) {
                    lineMessageHandler.addMessage(FormatConstants.SEVERE_SYNTAX, firstLineNumber + 2, "_ error.empty.tableexport.formatsourceline");
                    clear();
                    return;
                }
                if ((currentFormatPattern != null) && (currentFormatPattern.equals("-"))) {
                    currentFormatPattern = null;
                }
                FichothequeFormatDef formatDef = FichothequeFormatDefParser.parse(currentFormatSource, currentFormatPattern, currentFormatInstruction, TableDefUtils.toFormatDefMessageHandler(lineMessageHandler, firstLineNumber));
                if (formatDef != null) {
                    if ((formatColDefChecker == null) || (formatColDefChecker.checkFormatColDef(colName, formatDef, firstLineNumber, lineMessageHandler))) {
                        FormatColDef colDef = TableDefUtils.toFormatColDef(colName, formatDef, customLabels);
                        addDefItem(firstLineNumber, colDef);
                    }
                }
            } else {
                TableInclusionDef tableInclusionDef = TableDefUtils.newTableInclusionDef(colName, customLabels, inclusionLineList.toArray(new String[inclusionLineList.size()]));
                if (checkTableInclusionDef(firstLineNumber, tableInclusionDef)) {
                    addDefItem(firstLineNumber, tableInclusionDef);
                }
            }
            clear();
        }
    }

    private boolean checkTableInclusionDef(int lineNumber, TableInclusionDef tableInclusionDef) {
        if (tableExportContext == null) {
            return true;
        }
        TableInclusionResolver tableInclusionResolver = tableExportContext.getTableInclusionResolverProvider().getTableInclusionResolver(tableInclusionDef.getNameSpace());
        if (tableInclusionResolver == null) {
            lineMessageHandler.addMessage(FormatConstants.WARNING_FICHOTHEQUE, lineNumber, "_ error.unknown.tableexport.inclusion", tableInclusionDef.getNameSpace());
            return false;
        }
        return tableInclusionResolver.test(tableInclusionDef, subset, tableExportContext, lineNumber, lineMessageHandler);
    }

    private void clear() {
        currentColLine = null;
        currentFormatSource = null;
        currentFormatPattern = null;
        currentFormatInstruction = null;
        inclusionLineList.clear();
    }

    private void addParameter(String paramName, String paramValue) {
        defItemList.add(new InternalTableParameterDef(paramName, paramValue));
    }

    private void addDefItem(int lineNumber, TableDefItem defItem) {
        defItemList.add(defItem);
    }


    private static class InternalTableParameterDef implements TableParameterDef {

        private final String parameterName;
        private final String parameterValue;

        private InternalTableParameterDef(String parameterName, String parameterValue) {
            this.parameterName = parameterName;
            this.parameterValue = parameterValue;
        }

        @Override
        public String getParameterName() {
            return parameterName;
        }

        @Override
        public String getParameterValue() {
            return parameterValue;
        }

    }


}
