/* BdfServer - Copyright (c) 2023-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 fr.exemole.bdfserver.tools.overview;

import fr.exemole.bdfserver.api.BdfServer;
import fr.exemole.bdfserver.api.EditSession;
import fr.exemole.bdfserver.api.ui.CommentUi;
import fr.exemole.bdfserver.api.ui.DataUi;
import fr.exemole.bdfserver.api.ui.SpecialIncludeUi;
import fr.exemole.bdfserver.api.ui.UiComponent;
import fr.exemole.bdfserver.api.ui.UiComponents;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import net.fichotheque.Subset;
import net.fichotheque.corpus.Corpus;
import net.fichotheque.corpus.metadata.CorpusField;
import net.fichotheque.corpus.metadata.FieldKey;
import net.fichotheque.include.ExtendedIncludeKey;
import net.mapeadores.util.attr.AttributeKey;
import net.mapeadores.util.attr.AttributeParser;
import net.mapeadores.util.localisation.Lang;
import net.mapeadores.util.logging.LineMessageException;
import net.mapeadores.util.logging.LineMessageHandler;
import net.mapeadores.util.text.CleanedString;


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

    /**
     * Catégorie des erreurs diverses sur la syntaxe
     */
    public final static String SEVERE_SYNTAX = "severe.overview.syntax";
    public final static String SEVERE_FICHOTHEQUE = "severe.overview.fichotheque";
    private final BdfServer bdfServer;
    private final Corpus corpus;
    private final LineMessageHandler lineMessageHandler;
    private final UiComponents uiComponents;
    private final Map<String, OverviewBlock> blockMap = new LinkedHashMap<String, OverviewBlock>();

    public OverviewEngine(BdfServer bdfServer, Corpus corpus, LineMessageHandler lineMessageHandler) {
        this.bdfServer = bdfServer;
        this.corpus = corpus;
        this.lineMessageHandler = lineMessageHandler;
        this.uiComponents = bdfServer.getUiManager().getMainUiComponents(corpus);

    }

    public boolean run(EditSession editSession) {
        for (OverviewBlock overviewBlock : blockMap.values()) {
            overviewBlock.firstPass(editSession, uiComponents);
        }
        for (OverviewBlock overviewBlock : blockMap.values()) {
            overviewBlock.secondPass(editSession, uiComponents);
        }
        return true;
    }

    public void parse(String text) {
        for (RawBlock block : firstScan(text)) {
            try {
                OverviewBlock overviewBlock = parseBlock(block);
                for (RawLine rawLine : block.lineList) {
                    addLine(overviewBlock, rawLine);
                }
            } catch (LineMessageException lme) {
                lineMessageHandler.addMessage(lme);
            }
        }
    }

    private List<RawBlock> firstScan(String text) {
        List<RawBlock> blockList = new ArrayList<RawBlock>();
        RawBlock currentBlock = null;
        BufferedReader bufReader = new BufferedReader(new StringReader(text));
        int lineNumber = 0;
        String line;
        try {
            while ((line = bufReader.readLine()) != null) {
                line = line.trim();
                lineNumber++;
                if (line.length() == 0) {
                    continue;
                }
                switch (line.charAt(0)) {
                    case ';':
                    case '#':
                    case '!':
                    case '=':
                        continue;
                    case '[': {
                        int closeIdx = line.indexOf("]");
                        String header;
                        if (closeIdx > 0) {
                            header = line.substring(1, closeIdx).trim();
                        } else {
                            header = line.substring(1).trim();
                        }
                        if (header.isEmpty()) {
                            lineMessageHandler.addMessage(OverviewEngine.SEVERE_SYNTAX, lineNumber, "_ error.wrong.conf.header", line);
                            currentBlock = null;
                        } else {
                            currentBlock = new RawBlock(lineNumber, header);
                            blockList.add(currentBlock);
                        }
                        break;
                    }
                    default: {
                        int idx = line.indexOf('=');
                        if (idx < 1) {
                            lineMessageHandler.addMessage(OverviewEngine.SEVERE_SYNTAX, lineNumber, "_ error.wrong.conf.missingseparator");
                            continue;
                        }
                        if (currentBlock != null) {
                            String key = line.substring(0, idx).trim();
                            String value = unscape(line.substring(idx + 1).trim());
                            currentBlock.addLine(lineNumber, key, value);
                        }
                        break;
                    }
                }

            }
        } catch (IOException ioe) {

        }
        return blockList;
    }

    private OverviewBlock parseBlock(RawBlock rawBlock) throws LineMessageException {
        String name = rawBlock.header;
        OverviewBlock existing = blockMap.get(name);
        if (existing != null) {
            return existing;
        }
        UiComponent uiComponent = uiComponents.getUiComponent(name);
        OverviewBlock newBlock;
        if (uiComponent != null) {
            newBlock = OverviewBlock.fromUiComponent(rawBlock.lineNumber, bdfServer, uiComponent, corpus.getCorpusMetadata());
        } else {
            newBlock = parseName(rawBlock);
        }
        if (newBlock != null) {
            blockMap.put(name, newBlock);
            return newBlock;
        } else {
            return null;
        }
    }


    private void addLine(OverviewBlock currentBlock, RawLine rawLine) throws LineMessageException {
        String key = rawLine.key;
        int lineNumber = rawLine.lineNumber;
        String value = rawLine.value;
        int idx = key.indexOf(':');
        if (idx != -1) {
            try {
                AttributeKey attributeKey = AttributeKey.parse(key);
                List<CleanedString> list = AttributeParser.parseValues(value);
                currentBlock.addAttribute(attributeKey, list);
            } catch (ParseException pe) {
                throw new LineMessageException(lineNumber, SEVERE_SYNTAX, "_ error.wrong.conf.attributekey", key);
            }
            return;
        }
        int idx2 = key.indexOf('|');
        if (idx2 != -1) {
            String labelKey = key.substring(0, idx2);
            String langCode = key.substring(idx2 + 1);
            try {
                Lang lang = Lang.parse(langCode);
                boolean done = currentBlock.addText(labelKey, lang, value);
                if (!done) {
                    throw new LineMessageException(lineNumber, SEVERE_SYNTAX, "_ error.unknown.conf.key", labelKey);
                }
            } catch (ParseException pe) {
                throw new LineMessageException(lineNumber, SEVERE_SYNTAX, "_ error.wrong.lang", langCode);
            }
            return;
        }
        int idx3 = key.indexOf('_');
        if (idx3 == -1) {
            idx3 = key.indexOf('.');
        }
        if (idx3 != -1) {
            currentBlock.put(lineNumber, key.substring(0, idx3), key.substring(idx3 + 1), value);
        } else {
            currentBlock.put(lineNumber, key, value);
        }
    }

    private OverviewBlock parseName(RawBlock rawBlock) throws LineMessageException {
        int lineNumber = rawBlock.lineNumber;
        String name = rawBlock.header;
        if (name.equals("metadata")) {
            return OverviewBlock.metadata(lineNumber, corpus.getCorpusMetadata());
        } else {
            try {
                FieldKey fieldKey = FieldKey.parse(name);
                String ficheItemType = "";
                if ((fieldKey.isInfo()) || (fieldKey.isProp())) {
                    if (rawBlock.typeLine == null) {
                        throw new LineMessageException(lineNumber, SEVERE_FICHOTHEQUE, "_ error.wrong.conf.missingtype");
                    } else {
                        try {
                            ficheItemType = CorpusField.checkFicheItemType(rawBlock.typeLine.value);
                        } catch (IllegalArgumentException iae) {
                            throw new LineMessageException(rawBlock.typeLine.lineNumber, SEVERE_FICHOTHEQUE, "_ error.unknown.parametervalue", rawBlock.typeLine.key, rawBlock.typeLine.value);
                        }
                    }
                }
                return OverviewBlock.fieldKey(lineNumber, fieldKey, ficheItemType, corpus.getCorpusMetadata());
            } catch (ParseException pe) {
                try {
                    ExtendedIncludeKey includeKey = ExtendedIncludeKey.parse(name);
                    testIncludeKey(lineNumber, includeKey);
                    return OverviewBlock.includeKey(lineNumber, includeKey);
                } catch (ParseException pe2) {
                    try {
                        String specialName = SpecialIncludeUi.checkSpecialIncludeName(name);
                        return OverviewBlock.specialInclude(lineNumber, specialName);
                    } catch (IllegalArgumentException iae) {
                        if (name.startsWith("data_")) {
                            String dataName = name.substring("data_".length());
                            try {
                                DataUi.checkDataName(dataName);
                                if (rawBlock.typeLine == null) {
                                    throw new LineMessageException(lineNumber, SEVERE_SYNTAX, "_ error.wrong.conf.missingtype");
                                }
                                return OverviewBlock.externalData(lineNumber, dataName, rawBlock.typeLine.value);
                            } catch (ParseException pe3) {
                                throw new LineMessageException(lineNumber, SEVERE_SYNTAX, "_ error.wrong.dataname", dataName);
                            }

                        } else if (name.startsWith("comment_")) {
                            String commentName = name.substring("comment_".length());
                            try {
                                CommentUi.checkCommentName(commentName);
                                return OverviewBlock.comment(lineNumber, bdfServer, commentName);
                            } catch (ParseException pe3) {
                                throw new LineMessageException(lineNumber, SEVERE_SYNTAX, "_ error.wrong.conf.header", commentName);
                            }
                        }
                    }
                }
            }
        }
        throw new LineMessageException(lineNumber, SEVERE_SYNTAX, "_ error.wrong.conf.header", name);
    }

    private void testIncludeKey(int lineNumber, ExtendedIncludeKey includeKey) throws LineMessageException {
        Subset subset = bdfServer.getFichotheque().getSubset(includeKey.getSubsetKey());
        if (subset == null) {
            throw new LineMessageException(lineNumber, SEVERE_FICHOTHEQUE, "_ error.unknown.subset", includeKey.getSubsetKey().toString());
        }
        if ((includeKey.isMaster()) && (corpus.getMasterSubset() == null)) {
            throw new LineMessageException(lineNumber, SEVERE_FICHOTHEQUE, "_ error.unsupported.notsatellitecorpus", includeKey.toString());
        }

    }


    private static String unscape(String value) {
        StringBuilder buf = new StringBuilder();
        int length = value.length();
        for (int i = 0; i < length; i++) {
            char carac = value.charAt(i);
            switch (carac) {
                case '\\': {
                    if (i == (length - 1)) {
                        buf.append(carac);
                    } else {
                        char nextChar = value.charAt(i + 1);
                        i++;
                        switch (nextChar) {
                            case '\\':
                                buf.append('\\');
                                break;
                            case 'n':
                                buf.append('\n');
                                break;
                            case 'r':
                                buf.append('\r');
                                break;
                            case 't':
                                buf.append('\t');
                                break;
                            case '¶':
                                buf.append('¶');
                                break;
                            default:
                                buf.append('\\');
                                buf.append(nextChar);
                        }
                    }
                    break;
                }
                case '¶': {
                    buf.append('\n');
                    break;
                }
                default:
                    buf.append(carac);
            }
        }
        return buf.toString();
    }


    private static class RawBlock {

        private final int lineNumber;
        private final String header;
        private final List<RawLine> lineList = new ArrayList<RawLine>();
        private RawLine typeLine;

        private RawBlock(int lineNumber, String header) {
            this.lineNumber = lineNumber;
            this.header = header;
        }

        private void addLine(int lineNumber, String key, String value) {
            RawLine rawLine = new RawLine(lineNumber, key, value);
            lineList.add(rawLine);
            if ((key.equals("type")) && (typeLine == null)) {
                typeLine = rawLine;
            }
        }

    }


    private static class RawLine {

        private final int lineNumber;
        private final String key;
        private final String value;

        private RawLine(int lineNumber, String key, String value) {
            this.lineNumber = lineNumber;
            this.key = key;
            this.value = value;
        }

    }

}
