/* FichothequeLib_Tools - Copyright (c) 2012-2026 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.inclusionresolvers;

import java.text.ParseException;
import java.util.List;
import net.fichotheque.Subset;
import net.fichotheque.SubsetKey;
import net.fichotheque.corpus.Corpus;
import net.fichotheque.corpus.fiche.FicheItem;
import net.fichotheque.corpus.fiche.FicheItems;
import net.fichotheque.corpus.fiche.LanguageItem;
import net.fichotheque.corpus.metadata.CorpusField;
import net.fichotheque.corpus.metadata.FieldKey;
import net.fichotheque.exportation.table.Col;
import net.fichotheque.exportation.table.ColDef;
import net.fichotheque.exportation.table.TableExportContext;
import net.fichotheque.exportation.table.TableInclusionDef;
import net.fichotheque.exportation.table.TableInclusionResolver;
import net.fichotheque.format.FormatSource;
import net.fichotheque.format.formatters.SourceFormatter;
import net.fichotheque.pointeurs.FichePointeur;
import net.fichotheque.pointeurs.SubsetItemPointeur;
import net.fichotheque.thesaurus.Thesaurus;
import net.fichotheque.utils.FichothequeUtils;
import net.fichotheque.utils.TableExportUtils;
import net.mapeadores.util.attr.AttributeUtils;
import net.mapeadores.util.attr.Attributes;
import net.mapeadores.util.exceptions.ShouldNotOccurException;
import net.mapeadores.util.format.FormatConstants;
import net.mapeadores.util.instruction.Argument;
import net.mapeadores.util.instruction.Instruction;
import net.mapeadores.util.instruction.InstructionParser;
import net.mapeadores.util.localisation.Lang;
import net.mapeadores.util.logging.LineMessageHandler;
import net.mapeadores.util.logging.LogUtils;
import net.mapeadores.util.text.Labels;
import net.mapeadores.util.text.alphabet.Alphabet;
import net.mapeadores.util.text.alphabet.AlphabeticEntry;


/**
 *
 * @author Vincent Calame
 */
public class AlphabeticInclusionResolver implements TableInclusionResolver {

    public final static String NAMESPACE = "alphabetic";
    public final static String MERGE_NUMBERS_OPTION = "merge_numbers";
    public final static String CORPUS_OPTION = "corpus";
    public final static String LANGFIELD_OPTION = "langfield";
    public final static String SORTLANG_OPTION = "sortlang";
    private final static ColDef ENTRY = new InternalColDef("alphabetic_entry", "");
    private final static ColDef ARTICLE = new InternalColDef("alphabetic_article", "");
    private final static ColDef WITH_SPACE = new InternalColDef("alphabetic_withspace", FormatConstants.INTEGER_CAST);
    private final static ColDef INITIAL = new InternalColDef("alphabetic_initial", "");
    private final static ColDef SORT = new InternalColDef("alphabetic_sort", "");

    public AlphabeticInclusionResolver() {
    }

    @Override
    public boolean test(TableInclusionDef tableInclusionDef, Subset subset, TableExportContext tableExportContext, int lineNumber, LineMessageHandler lineMessageHandler) {
        String nameSpace = tableInclusionDef.getNameSpace();
        if (!nameSpace.equals(NAMESPACE)) {
            return false;
        }
        int instructionLineNumber = lineNumber + 1;
        Instruction instruction = getConfigInstruction(tableInclusionDef, instructionLineNumber, lineMessageHandler);
        Corpus corpus;
        if (subset instanceof Thesaurus) {
            corpus = getSatelliteCorpus(instruction, subset, instructionLineNumber, lineMessageHandler);
            if (corpus == null) {
                return false;
            }
        } else if (subset instanceof Corpus) {
            corpus = (Corpus) subset;
        } else {
            return false;
        }
        getLangField(instruction, corpus, instructionLineNumber, lineMessageHandler);
        getSortLang(instruction, instructionLineNumber, lineMessageHandler);
        return true;
    }

    @Override
    public Col[] resolve(TableInclusionDef tableInclusionDef, Subset subset, TableExportContext tableExportContext) {
        LineMessageHandler lineMessageHandler = LogUtils.NULL_LINEMESSAGEHANDLER;
        String nameSpace = tableInclusionDef.getNameSpace();
        if (!nameSpace.equals(NAMESPACE)) {
            return TableExportUtils.EMPTY_COLARRAY;
        }
        Instruction instruction = getConfigInstruction(tableInclusionDef, 0, lineMessageHandler);
        SubsetKey satelliteSubsetKey = null;
        Corpus corpus;
        if (subset instanceof Thesaurus) {
            corpus = getSatelliteCorpus(instruction, subset, 0, lineMessageHandler);
            if (corpus == null) {
                return getErrorColArray();
            }
            satelliteSubsetKey = corpus.getSubsetKey();
        } else if (subset instanceof Corpus) {
            corpus = (Corpus) subset;
        } else {
            return getErrorColArray();
        }
        CorpusField corpusField = getLangField(instruction, corpus, 0, lineMessageHandler);
        if (corpusField == null) {
            return getErrorColArray();
        }
        boolean mergeNumbersOption = false;
        if (instruction != null) {
            for (Argument argument : instruction) {
                if (argument.getKey().equals(MERGE_NUMBERS_OPTION)) {
                    mergeNumbersOption = true;
                    break;
                }
            }
        }
        Lang sortLang = getSortLang(instruction, 0, lineMessageHandler);
        return getSuccessColArray(satelliteSubsetKey, corpusField.getFieldKey(), sortLang, mergeNumbersOption);
    }

    private Instruction getConfigInstruction(TableInclusionDef tableInclusionDef, int lineNumber, LineMessageHandler lineMessageHandler) {
        List<String> configLineList = tableInclusionDef.getConfigLineList();
        if (configLineList.isEmpty()) {
            return null;
        }
        Instruction instruction = InstructionParser.parse(configLineList.get(0), LogUtils.encapsulate(lineMessageHandler, FormatConstants.SEVERE_SYNTAX, lineNumber));
        return instruction;
    }

    private Corpus getSatelliteCorpus(Instruction instruction, Subset subset, int lineNumber, LineMessageHandler lineMessageHandler) {
        if (instruction == null) {
            lineMessageHandler.addMessage(FormatConstants.SEVERE_SYNTAX, lineNumber, "_ error.empty.tableexport.configline", lineNumber);
            return null;
        }
        boolean corpusHere = false;
        SubsetKey parentageSubsetKey = null;
        for (Argument argument : instruction) {
            if (argument.getKey().equals(CORPUS_OPTION)) {
                String value = argument.getNotNullValue();
                if (value.isEmpty()) {
                    lineMessageHandler.addMessage(FormatConstants.SEVERE_SYNTAX, lineNumber, "_ error.empty.argumentvalue", CORPUS_OPTION);
                } else {
                    corpusHere = true;
                    try {
                        parentageSubsetKey = SubsetKey.parse(SubsetKey.CATEGORY_CORPUS, value);
                    } catch (ParseException pe) {
                        lineMessageHandler.addMessage(FormatConstants.SEVERE_SYNTAX, lineNumber, "_ error.wrong.argumentvalue", CORPUS_OPTION, value);
                    }
                }
                break;
            }
        }
        if (parentageSubsetKey == null) {
            if (!corpusHere) {
                lineMessageHandler.addMessage(FormatConstants.SEVERE_SYNTAX, lineNumber, "_ error.empty.argumentkey", CORPUS_OPTION);
            }
            return null;
        }
        Corpus other = (Corpus) subset.getFichotheque().getSubset(parentageSubsetKey);
        if (other == null) {
            lineMessageHandler.addMessage(FormatConstants.WARNING_FICHOTHEQUE, lineNumber, "_ error.unknown.subset", parentageSubsetKey);
            return null;
        }
        if (!FichothequeUtils.ownToSameParentage(subset, other)) {
            lineMessageHandler.addMessage(FormatConstants.SEVERE_SYNTAX, lineNumber, "_ error.unsupported.parentage", parentageSubsetKey, subset.getSubsetKey());
            return null;
        }
        return other;
    }

    private CorpusField getLangField(Instruction instruction, Corpus corpus, int lineNumber, LineMessageHandler lineMessageHandler) {
        if (instruction == null) {
            return corpus.getCorpusMetadata().getCorpusField(FieldKey.LANG);
        }
        boolean langFieldHere = false;
        CorpusField corpusField = null;
        for (Argument argument : instruction) {
            if (argument.getKey().equals(LANGFIELD_OPTION)) {
                String value = argument.getNotNullValue();
                if (value.isEmpty()) {
                    lineMessageHandler.addMessage(FormatConstants.SEVERE_SYNTAX, lineNumber, "_ error.empty.argumentvalue", LANGFIELD_OPTION);
                } else {
                    langFieldHere = true;
                    try {
                        FieldKey fieldKey = FieldKey.parse(value);
                        corpusField = corpus.getCorpusMetadata().getCorpusField(fieldKey);
                        if (corpusField == null) {
                            lineMessageHandler.addMessage(FormatConstants.WARNING_FICHOTHEQUE, lineNumber, "_ error.unknown.fieldkey", value);
                        }
                    } catch (ParseException pe) {
                        lineMessageHandler.addMessage(FormatConstants.SEVERE_SYNTAX, lineNumber, "_ error.wrong.argumentvalue", LANGFIELD_OPTION, value);
                    }
                }
                break;
            }
        }
        if (!langFieldHere) {
            corpusField = corpus.getCorpusMetadata().getCorpusField(FieldKey.LANG);
        }
        return corpusField;
    }

    private Lang getSortLang(Instruction instruction, int lineNumber, LineMessageHandler lineMessageHandler) {
        if (instruction == null) {
            return null;
        }
        for (Argument argument : instruction) {
            if (argument.getKey().equals(SORTLANG_OPTION)) {
                String value = argument.getNotNullValue();
                if (value.isEmpty()) {
                    lineMessageHandler.addMessage(FormatConstants.SEVERE_SYNTAX, lineNumber, "_ error.empty.argumentvalue", SORTLANG_OPTION);
                } else {
                    try {
                        Lang lang = Lang.parse(value);
                        return lang;
                    } catch (ParseException pe) {
                        lineMessageHandler.addMessage(FormatConstants.SEVERE_SYNTAX, lineNumber, "_ error.wrong.argumentvalue", SORTLANG_OPTION, value);
                    }
                }
                break;
            }
        }
        return null;
    }

    private static boolean isNumber(String initialChar) {
        try {
            Integer.parseInt(initialChar);
            return true;
        } catch (NumberFormatException nfe) {
            return false;
        }
    }

    private static Col[] getErrorColArray() {
        SourceFormatter errorFormatter = new ErrorFormatter();
        Col[] colArray = new Col[5];
        colArray[0] = TableExportUtils.toCol(ENTRY, errorFormatter);
        colArray[1] = TableExportUtils.toCol(ARTICLE, errorFormatter);
        colArray[2] = TableExportUtils.toCol(WITH_SPACE, errorFormatter);
        colArray[3] = TableExportUtils.toCol(INITIAL, errorFormatter);
        colArray[4] = TableExportUtils.toCol(SORT, errorFormatter);
        return colArray;
    }

    private static Col[] getSuccessColArray(SubsetKey satelliteSubsetKey, FieldKey langFieldKey, Lang sortLang, boolean mergeNumbersOption) {
        Col[] colArray = new Col[5];
        colArray[0] = TableExportUtils.toCol(ENTRY, new AlphabeticFormatter(0, satelliteSubsetKey, langFieldKey, sortLang, mergeNumbersOption));
        colArray[1] = TableExportUtils.toCol(ARTICLE, new AlphabeticFormatter(1, satelliteSubsetKey, langFieldKey, sortLang, mergeNumbersOption));
        colArray[2] = TableExportUtils.toCol(WITH_SPACE, new AlphabeticFormatter(2, satelliteSubsetKey, langFieldKey, sortLang, mergeNumbersOption));
        colArray[3] = TableExportUtils.toCol(INITIAL, new AlphabeticFormatter(3, satelliteSubsetKey, langFieldKey, sortLang, mergeNumbersOption));
        colArray[4] = TableExportUtils.toCol(SORT, new AlphabeticFormatter(4, satelliteSubsetKey, langFieldKey, sortLang, mergeNumbersOption));
        return colArray;
    }


    private static class ErrorFormatter implements SourceFormatter {

        private ErrorFormatter() {
        }

        @Override
        public String formatSource(FormatSource formatSource) {
            return "#ERROR - check test";
        }

    }


    private static class AlphabeticFormatter implements SourceFormatter {

        private final int index;
        private final SubsetKey satelliteSubsetKey;
        private final FieldKey langFieldKey;
        private final Lang sortLang;
        private final boolean mergeNumbersOption;

        private AlphabeticFormatter(int index, SubsetKey satelliteSubsetKey, FieldKey langFieldKey, Lang sortLang, boolean mergeNumbersOption) {
            this.index = index;
            this.satelliteSubsetKey = satelliteSubsetKey;
            this.langFieldKey = langFieldKey;
            this.sortLang = sortLang;
            this.mergeNumbersOption = mergeNumbersOption;
        }

        @Override
        public String formatSource(FormatSource formatSource) {
            SubsetItemPointeur pointeur = formatSource.getSubsetItemPointeur();
            if (satelliteSubsetKey != null) {
                pointeur = pointeur.getParentagePointeur(satelliteSubsetKey);
            }
            if ((pointeur == null) || (!(pointeur instanceof FichePointeur))) {
                return "#ERROR : not fiche Pointeur";
            }
            Object object = pointeur.getCurrentObject(NAMESPACE);
            AlphabeticEntry entry;
            if (object == null) {
                FichePointeur fichePointeur = (FichePointeur) pointeur;
                String title = (String) fichePointeur.getValue(FieldKey.TITLE);
                if (title == null) {
                    pointeur.putCurrentObject(NAMESPACE, Boolean.FALSE);
                    return null;
                }
                Lang lang = getLang(fichePointeur, langFieldKey);
                if (lang == null) {
                    lang = formatSource.getDefaultLang();
                }
                if (sortLang == null) {
                    entry = Alphabet.newInstance(title, lang);
                } else {
                    entry = Alphabet.newInstance(title, lang, sortLang);
                }
                pointeur.putCurrentObject(NAMESPACE, entry);
            } else {
                if (object.equals(Boolean.FALSE)) {
                    return "";
                }
                if (!(object instanceof AlphabeticEntry)) {
                    return "#ERROR";
                }
                entry = (AlphabeticEntry) object;
            }
            switch (index) {
                case 0:
                    return entry.getEntryString();
                case 1:
                    if (entry.isWithArticle()) {
                        return entry.getArticleString();
                    } else {
                        return null;
                    }
                case 2:
                    if (entry.isWithSeparationSpace()) {
                        return "1";
                    } else {
                        return "0";
                    }
                case 3:
                    String initialChar = entry.getInitialChar();
                    if ((mergeNumbersOption) && (isNumber(initialChar))) {
                        return "0";
                    }
                    return initialChar;
                case 4:
                    return entry.getAlphabeticSort();
                default:
                    throw new ShouldNotOccurException();
            }

        }

    }


    private static class InternalColDef implements ColDef {

        private final String name;
        private final String castType;

        private InternalColDef(String name, String castType) {
            this.name = name;
            this.castType = castType;
        }

        @Override
        public String getColName() {
            return name;
        }

        /**
         * Nul s'il faut utiliser le libellé par défaut
         */
        @Override
        public Labels getCustomLabels() {
            return null;
        }

        @Override
        public String getCastType() {
            return castType;
        }

        @Override
        public Attributes getAttributes() {
            return AttributeUtils.EMPTY_ATTRIBUTES;
        }

    }

    private static Lang getLang(FichePointeur fichePointeur, FieldKey fieldKey) {
        Object object = fichePointeur.getValue(fieldKey);
        if (object == null) {
            return null;
        } else if (object instanceof LanguageItem) {
            return ((LanguageItem) object).getLang();
        } else if (object instanceof FicheItems) {
            FicheItems ficheItems = (FicheItems) object;
            int count = ficheItems.size();
            for (int i = 0; i < count; i++) {
                FicheItem ficheItem = ficheItems.get(i);
                if (ficheItem instanceof LanguageItem) {
                    return ((LanguageItem) ficheItem).getLang();
                }
            }
            return null;
        } else {
            return null;
        }
    }

}
