/* FichothequeLib_Tools - Copyright (c) 2006-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.scrutari;

import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
import net.fichotheque.Fichotheque;
import net.fichotheque.FichothequeConstants;
import net.fichotheque.Subset;
import net.fichotheque.SubsetItem;
import net.fichotheque.SubsetKey;
import net.fichotheque.corpus.Corpus;
import net.fichotheque.corpus.FicheMeta;
import net.fichotheque.corpus.fiche.FicheItem;
import net.fichotheque.corpus.fiche.FicheItems;
import net.fichotheque.corpus.fiche.GeopointItem;
import net.fichotheque.corpus.fiche.LanguageItem;
import net.fichotheque.corpus.metadata.CorpusField;
import net.fichotheque.corpus.metadata.CorpusMetadata;
import net.fichotheque.corpus.metadata.FieldKey;
import net.fichotheque.junction.Liaison;
import net.fichotheque.exportation.scrutari.CorpusScrutariDef;
import net.fichotheque.exportation.scrutari.ScrutariExportDef;
import net.fichotheque.exportation.scrutari.ThesaurusScrutariDef;
import net.fichotheque.exportation.table.CellConverterProvider;
import net.fichotheque.exportation.table.Col;
import net.fichotheque.exportation.table.ColDef;
import net.fichotheque.exportation.table.FormatColDef;
import net.fichotheque.exportation.table.SubsetTable;
import net.fichotheque.exportation.table.TableDef;
import net.fichotheque.exportation.table.TableExportContext;
import net.fichotheque.extraction.ExtractionContext;
import net.fichotheque.format.FormatContext;
import net.fichotheque.format.FormatSource;
import net.fichotheque.format.FormatSourceKey;
import net.fichotheque.format.formatters.SourceFormatter;
import net.fichotheque.metadata.FichothequeMetadata;
import net.fichotheque.pointeurs.FichePointeur;
import net.fichotheque.pointeurs.SubsetItemPointeur;
import net.fichotheque.selection.FicheQuery;
import net.fichotheque.selection.FichothequeQueries;
import net.fichotheque.selection.SelectionContext;
import net.fichotheque.thesaurus.Motcle;
import net.fichotheque.thesaurus.Thesaurus;
import net.fichotheque.thesaurus.metadata.ThesaurusLangChecker;
import net.fichotheque.thesaurus.metadata.ThesaurusMetadata;
import net.fichotheque.tools.exportation.table.SubsetTableBuilder;
import net.fichotheque.tools.exportation.table.TableDefParser;
import net.fichotheque.utils.EligibilityUtils;
import net.fichotheque.utils.ExtractionUtils;
import net.fichotheque.utils.FichothequeUtils;
import net.fichotheque.utils.FormatterUtils;
import net.fichotheque.utils.SelectionUtils;
import net.fichotheque.utils.TableExportUtils;
import net.fichotheque.utils.pointeurs.PointeurFactory;
import net.fichotheque.utils.selection.FicheSelectorBuilder;
import net.fichotheque.utils.selection.SelectionContextBuilder;
import net.mapeadores.util.annotation.Nullable;
import net.mapeadores.util.attr.Attribute;
import net.mapeadores.util.attr.AttributeKey;
import net.mapeadores.util.hook.HookHandler;
import net.mapeadores.util.hook.HookUtils;
import net.mapeadores.util.localisation.Lang;
import net.mapeadores.util.localisation.LangContext;
import net.mapeadores.util.localisation.Langs;
import net.mapeadores.util.localisation.LangsUtils;
import net.mapeadores.util.localisation.LocalisationUtils;
import net.mapeadores.util.localisation.UserLangContext;
import net.mapeadores.util.logging.LogUtils;
import net.mapeadores.util.text.AccoladeArgument;
import net.mapeadores.util.text.AccoladePattern;
import net.mapeadores.util.text.Label;
import net.mapeadores.util.text.Labels;
import net.mapeadores.util.text.StringUtils;
import net.mapeadores.util.text.ValueResolver;
import net.scrutari.dataexport.ScrutariDataExportFactory;
import net.scrutari.dataexport.api.BaseMetadataExport;
import net.scrutari.dataexport.api.CorpusMetadataExport;
import net.scrutari.dataexport.api.FicheExport;
import net.scrutari.dataexport.api.MotcleExport;
import net.scrutari.dataexport.api.ScrutariDataExport;
import net.scrutari.dataexport.api.ThesaurusMetadataExport;


/**
 *
 * @author Vincent Calame
 */
public final class ScrutariExportEngine {

    private final TableExportContext tableExportContext;
    private final Fichotheque fichotheque;
    private final FormatContext formatContext;
    private final ScrutariExportDef scrutariExportDef;
    private final Lang[] langArray;
    private final ExtractionContext extractionContext;
    private final Predicate<SubsetItem> predicate;
    private final ThesaurusLangChecker thesaurusLangChecker;
    private final HookHandler hookHandler;
    private ScrutariDataExport scrutariDataExport;
    private ThesaurusEngine[] thesaurusEngineArray;

    private ScrutariExportEngine(ScrutariExportDef scrutariExportDef, Lang[] langArray, ExtractionContext extractionContext, TableExportContext tableExportContext, Predicate<SubsetItem> predicate, ThesaurusLangChecker thesaurusLangChecker, HookHandler hookHandler) {
        this.scrutariExportDef = scrutariExportDef;
        this.langArray = langArray;
        this.extractionContext = extractionContext;
        this.tableExportContext = tableExportContext;
        this.formatContext = tableExportContext.getFormatContext();
        this.fichotheque = formatContext.getFichotheque();
        this.predicate = predicate;
        this.thesaurusLangChecker = thesaurusLangChecker;
        this.hookHandler = hookHandler;
    }

    public static ScrutariExportEngine build(ScrutariExportDef scrutariExportDef, Lang[] langArray, ExtractionContext extractionContext, TableExportContext tableExportContext, ThesaurusLangChecker thesaurusLangChecker, @Nullable HookHandler hookHandler, FichothequeQueries resolvedFichothequeQueries) {
        if (langArray.length == 0) {
            throw new IllegalArgumentException("langArray.length == 0");
        }
        FormatContext formatContext = tableExportContext.getFormatContext();
        SelectionContext selectionContext = SelectionContextBuilder.init(formatContext.getFichotheque(), extractionContext.getFichothequeQuestioner(), formatContext.getMessageLocalisationProvider(), langArray[0])
                .setSubsetAccessPredicate(EligibilityUtils.ALL_SUBSET_PREDICATE)
                .toSelectionContext();
        FicheSelectorBuilder ficheSelectorBuilder = FicheSelectorBuilder.init(selectionContext);
        for (FicheQuery ficheQuery : resolvedFichothequeQueries.getFicheQueryList()) {
            ficheSelectorBuilder.add(ficheQuery);
        }
        Predicate<SubsetItem> globalPredicate = EligibilityUtils.merge(SelectionUtils.toPredicate(ficheSelectorBuilder.toFicheSelector()), null);
        tableExportContext = ScrutariExportUtils.toScrutariTableExportContext(tableExportContext);
        if (hookHandler == null) {
            hookHandler = HookUtils.NONE_HANDLER;
        }
        return new ScrutariExportEngine(scrutariExportDef, langArray, extractionContext, tableExportContext, globalPredicate, thesaurusLangChecker, hookHandler);
    }

    public void run(Appendable appendable, int indentLength, boolean includeXMLDeclaration) throws IOException {
        this.scrutariDataExport = ScrutariDataExportFactory.newInstance(appendable, indentLength, includeXMLDeclaration, true);
        this.thesaurusEngineArray = initThesaurusEngineArray();
        BaseMetadataExport basemetaDataExport = scrutariDataExport.startExport();
        addBaseMetadata(basemetaDataExport);
        CorpusEngine[] corpusEngineArray = initCorpusEngineArray();
        for (CorpusEngine corpusEngine : corpusEngineArray) {
            corpusEngine.run();
        }
        if (thesaurusEngineArray != null) {
            for (ThesaurusEngine thesaurusEngine : thesaurusEngineArray) {
                thesaurusEngine.addThesaurus();
            }
        }
        hookHandler.handle("End", scrutariDataExport);
        scrutariDataExport.endExport();
    }

    private CorpusEngine[] initCorpusEngineArray() {
        Map<SubsetKey, CorpusScrutariDef> corpusDefMap = ScrutariExportUtils.toCorpusScrutariDefMap(scrutariExportDef);
        List<CorpusEngine> selectedList = new ArrayList<CorpusEngine>();
        for (Corpus corpus : fichotheque.getCorpusList()) {
            CorpusScrutariDef corpusScrutariDef = corpusDefMap.get(corpus.getSubsetKey());
            if (corpusScrutariDef != null) {
                selectedList.add(initCorpusEngine(corpus, corpusScrutariDef));
            }
        }
        return selectedList.toArray(new CorpusEngine[selectedList.size()]);
    }

    private ThesaurusEngine[] initThesaurusEngineArray() {
        Map<SubsetKey, ThesaurusScrutariDef> thesaurusDefMap = ScrutariExportUtils.toThesaurusScrutariDefMap(scrutariExportDef);
        List<ThesaurusEngine> selectedList = new ArrayList<ThesaurusEngine>();
        for (Thesaurus thesaurus : fichotheque.getThesaurusList()) {
            ThesaurusScrutariDef thesaurusScrutariDef = thesaurusDefMap.get(thesaurus.getSubsetKey());
            if (thesaurusScrutariDef != null) {
                selectedList.add(initThesaurusEngine(thesaurus, thesaurusScrutariDef));
            }
        }
        return selectedList.toArray(new ThesaurusEngine[selectedList.size()]);
    }

    private void addBaseMetadata(BaseMetadataExport basemetaDataExport) throws IOException {
        basemetaDataExport.setAuthority(scrutariExportDef.getAuthority());
        basemetaDataExport.setBaseName(scrutariExportDef.getBaseName());
        basemetaDataExport.setBaseIcon(scrutariExportDef.getBaseIcon());
        FichothequeMetadata fichothequeMetadata = fichotheque.getFichothequeMetadata();
        addIntitule(basemetaDataExport, fichothequeMetadata, BaseMetadataExport.INTITULE_SHORT);
        addIntitule(basemetaDataExport, fichothequeMetadata, BaseMetadataExport.INTITULE_LONG);
        for (Lang lang : scrutariExportDef.getUiLangs()) {
            basemetaDataExport.addLangUI(lang.toString());
        }
        ExportInclude exportInclude = ExportInclude.parse(scrutariExportDef.getIncludeTokenList());
        if (exportInclude.isWithAttributeFilter()) {
            Predicate<AttributeKey> attributeFilter = exportInclude.getAttributeFilter();
            for (Attribute attribute : fichothequeMetadata.getAttributes()) {
                AttributeKey attributeKey = attribute.getAttributeKey();
                if (attributeFilter.test(attributeKey)) {
                    for (String value : attribute) {
                        basemetaDataExport.addAttributeValue(attributeKey.getNameSpace(), attributeKey.getLocalKey(), value);
                    }
                }
            }
        }
        hookHandler.handle("BaseMetadata", basemetaDataExport, fichothequeMetadata, exportInclude.getHookList());
    }

    private void addIntitule(BaseMetadataExport basemetaDataExport, FichothequeMetadata fichothequeMetadata, int intituleType) {
        Labels labels = scrutariExportDef.getCustomBaseIntitule(intituleType);
        if (labels == null) {
            if (intituleType == BaseMetadataExport.INTITULE_SHORT) {
                labels = fichothequeMetadata.getTitleLabels();
            } else {
                labels = fichothequeMetadata.getPhrases().getPhrase(FichothequeConstants.LONG_PHRASE);
            }
        }
        boolean done = false;
        if (labels != null) {
            int length = langArray.length;
            for (int i = 0; i < length; i++) {
                Label label = labels.getLabel(langArray[i]);
                if (label != null) {
                    basemetaDataExport.setIntitule(intituleType, label.getLang().toString(), label.getLabelString());
                    done = true;
                }
            }
        }
        if (!done) {
            basemetaDataExport.setIntitule(intituleType, "und", scrutariExportDef.getBaseName());
        }
    }

    private CorpusEngine initCorpusEngine(Corpus corpus, CorpusScrutariDef corpusScrutariDef) {
        SourceFormatter titreFormatter = null;
        SourceFormatter soustitreFormatter = null;
        SourceFormatter dateFormatter = null;
        SourceFormatter hrefFormatter = null;
        SourceFormatter ficheIconFormatter = null;
        TableDef tableDef = TableDefParser.parse(corpusScrutariDef.getFieldGenerationSource(), corpus, tableExportContext, LogUtils.NULL_LINEMESSAGEHANDLER, 0);
        SubsetTable subsetTable = SubsetTableBuilder.init(corpus).populate(tableDef, tableExportContext, false, "\n").toSubsetTable();
        List<ComplementEngine> complementEngineList = new ArrayList<ComplementEngine>();
        List<AttributeEngine> attributeEngineList = new ArrayList<AttributeEngine>();
        List<Col> colList = subsetTable.getColList();
        for (Col col : colList) {
            ColDef colDef = col.getColDef();
            String colName = colDef.getColName();
            SourceFormatter formatter = col.getSourceFormatter();
            if (colName.equals("titre")) {
                if (titreFormatter == null) {
                    titreFormatter = formatter;
                }
            } else if (colName.equals("soustitre")) {
                if (soustitreFormatter == null) {
                    soustitreFormatter = formatter;
                }
            } else if (colName.equals("date")) {
                if (dateFormatter == null) {
                    dateFormatter = formatter;
                }
            } else if (colName.equals("href")) {
                if (hrefFormatter == null) {
                    hrefFormatter = formatter;
                }
            } else if (colName.equals("ficheicon")) {
                if (ficheIconFormatter == null) {
                    ficheIconFormatter = formatter;
                }
            } else if (colName.startsWith("comp")) {
                ComplementEngine complementEngine = new ComplementEngine(formatter);
                Labels customLabels = colDef.getCustomLabels();
                if (customLabels != null) {
                    complementEngine.setCustomLabels(customLabels);
                } else if (colDef instanceof FormatColDef) {
                    complementEngine.setDefaultLabels(tableExportContext.getSourceLabelProvider().getLabels(corpus, ((FormatColDef) colDef).getFichothequeFormatDef().getFormatSourceKeyList().get(0)), langArray);
                }
                complementEngineList.add(complementEngine);
            } else {
                try {
                    AttributeKey attributeKey = AttributeKey.parse(colName);
                    attributeEngineList.add(new AttributeEngine(attributeKey, formatter));
                } catch (ParseException pe) {

                }
            }
        }
        ComplementEngine[] complementEngineArray = complementEngineList.toArray(new ComplementEngine[complementEngineList.size()]);
        AttributeEngine[] attributeEngineArray = attributeEngineList.toArray(new AttributeEngine[attributeEngineList.size()]);
        if (titreFormatter == null) {
            titreFormatter = ScrutariExportUtils.TITRE;
        }
        if (soustitreFormatter == null) {
            soustitreFormatter = ScrutariExportUtils.SOUSTITRE;
        }
        if (dateFormatter == null) {
            dateFormatter = getDefaultDateFormatter(corpus, corpusScrutariDef);
        }
        if (hrefFormatter == null) {
            hrefFormatter = getDefaultHrefFormatter(corpusScrutariDef);
        }
        if (ficheIconFormatter == null) {
            ficheIconFormatter = ScrutariExportUtils.NULL;
        }
        return new CorpusEngine(corpus, corpusScrutariDef, titreFormatter, soustitreFormatter, dateFormatter, hrefFormatter, ficheIconFormatter, complementEngineArray, attributeEngineArray);
    }

    private SourceFormatter getDefaultHrefFormatter(CorpusScrutariDef corpusScrutariDef) {
        AccoladePattern hrefPattern = corpusScrutariDef.getHrefPattern();
        if (hrefPattern == null) {
            hrefPattern = scrutariExportDef.getFicheHrefPattern();
        }
        if (hrefPattern == null) {
            return ScrutariExportUtils.NULL;
        }
        return new HrefSourceFormatter(hrefPattern);
    }

    private SourceFormatter getDefaultDateFormatter(Corpus corpus, CorpusScrutariDef corpusScrutariDef) {
        FormatSourceKey dateFormatSourceKey = corpusScrutariDef.getDateFormatSourceKey();
        if (dateFormatSourceKey == null) {
            return ScrutariExportUtils.NULL;
        }
        switch (dateFormatSourceKey.getSourceType()) {
            case FormatSourceKey.FIELDKEY_TYPE:
                CorpusField corpusField = corpus.getCorpusMetadata().getCorpusField((FieldKey) dateFormatSourceKey.getKeyObject());
                if (corpusField != null) {
                    if (corpusField.isType(CorpusField.DATE_FIELD)) {
                        return ScrutariExportUtils.getDateFieldFormatter(corpusField.getFieldKey());
                    }
                }
                break;
            case FormatSourceKey.SPECIALINCLUDENAME_TYPE:
                String specialIncludeName = (String) dateFormatSourceKey.getKeyObject();
                if (specialIncludeName.equals(FichothequeConstants.DATECREATION_NAME)) {
                    return ScrutariExportUtils.DATE_CREATION;
                } else if (specialIncludeName.equals(FichothequeConstants.DATEMODIFICATION_NAME)) {
                    return ScrutariExportUtils.DATE_MODIFICATION;
                }
                break;
        }
        return ScrutariExportUtils.NULL;
    }


    private class CorpusEngine {

        private final Corpus corpus;
        private final Collection<SubsetItem> subsetItems;
        private final ComplementEngine[] complementEngineArray;
        private final AttributeEngine[] attributeEngineArray;
        private final ExportInclude exportInclude;
        private FieldKey geolocalisationFieldKey;
        private final SourceFormatter titreFormatter;
        private final SourceFormatter soustitreFormatter;
        private final SourceFormatter dateFormatter;
        private final SourceFormatter hrefFormatter;
        private final SourceFormatter ficheIconFormatter;
        private String multilangMode = null;
        private Set<Lang> multilangSet = null;
        private FieldKey multilangFieldKey = null;

        private CorpusEngine(Corpus corpus, CorpusScrutariDef corpusScrutariDef, SourceFormatter titreFormatter, SourceFormatter soustitreFormatter, SourceFormatter dateFormatter, SourceFormatter hrefFormatter, SourceFormatter ficheIconFormatter, ComplementEngine[] complementEngineArray, AttributeEngine[] attributeEngineArray) {
            this.corpus = corpus;
            CorpusField geolocalisationField = corpus.getCorpusMetadata().getGeolocalisationField();
            if (geolocalisationField != null) {
                geolocalisationFieldKey = geolocalisationField.getFieldKey();
            }
            this.subsetItems = FichothequeUtils.filterAndSort(corpus, predicate);
            this.titreFormatter = titreFormatter;
            this.soustitreFormatter = soustitreFormatter;
            this.dateFormatter = dateFormatter;
            this.hrefFormatter = hrefFormatter;
            this.ficheIconFormatter = ficheIconFormatter;
            this.complementEngineArray = complementEngineArray;
            this.attributeEngineArray = attributeEngineArray;
            multilangMode = corpusScrutariDef.getMultilangMode();
            if (multilangMode != null) {
                String multilangParam = corpusScrutariDef.getMultilangParam();
                boolean done = false;
                switch (multilangMode) {
                    case CorpusScrutariDef.LANGUI_MODE:
                        done = initLanguiMultilangMode();
                        break;
                    case CorpusScrutariDef.THESAURUS_MODE:
                        done = initThesaurusMultilangMode(multilangParam);
                        break;
                    case CorpusScrutariDef.FIELD_MODE:
                        done = initFieldMultilangMode(multilangParam);
                        break;
                    case CorpusScrutariDef.CUSTOM_MODE:
                        done = initCustomMultilangMode(multilangParam);
                        break;
                }
                if (!done) {
                    multilangMode = null;
                }
            }
            this.exportInclude = ExportInclude.parse(corpusScrutariDef.getIncludeTokenList());
        }

        private boolean initLanguiMultilangMode() {
            Langs uiLangs = scrutariExportDef.getUiLangs();
            if (uiLangs.isEmpty()) {
                return false;
            } else {
                multilangSet = new LinkedHashSet<Lang>(uiLangs);
                return true;
            }
        }

        private boolean initThesaurusMultilangMode(String multiLangParam) {
            try {
                SubsetKey subsetKey = SubsetKey.parse(SubsetKey.CATEGORY_THESAURUS, multiLangParam);
                Thesaurus thesaurus = (Thesaurus) fichotheque.getSubset(subsetKey);
                if (thesaurus != null) {
                    if (!thesaurus.isBabelienType()) {
                        Set<Lang> result = new LinkedHashSet<Lang>();
                        Langs langs = thesaurusLangChecker.getAuthorizedLangs(thesaurus);
                        for (Lang lang : langs) {
                            result.add(lang);
                        }
                        multilangSet = result;
                        return true;
                    }
                }
            } catch (ParseException pe) {
            }
            return false;
        }

        private boolean initFieldMultilangMode(String multiLangParam) {
            try {
                FieldKey fieldKey = FieldKey.parse(multiLangParam);
                multilangFieldKey = fieldKey;
                return true;
            } catch (ParseException pe) {
            }
            return false;
        }

        private boolean initCustomMultilangMode(String multiLangParam) {
            Lang[] customLangArray = LangsUtils.toCleanLangArray(multiLangParam);
            if (customLangArray.length == 0) {
                customLangArray = langArray;
            }
            Set<Lang> result = new LinkedHashSet<Lang>();
            int length = customLangArray.length;
            for (int i = 0; i < length; i++) {
                result.add(customLangArray[i]);
            }
            multilangSet = result;
            return true;
        }

        private void run() {
            CorpusMetadataExport corpusMetadataExport = scrutariDataExport.newCorpus(corpus.getSubsetName());
            addCorpusMetadata(corpusMetadataExport, corpus.getCorpusMetadata());
            FichePointeur fichePointeur = PointeurFactory.newFichePointeur(corpus);
            for (SubsetItem subsetItem : subsetItems) {
                fichePointeur.setCurrentSubsetItem(subsetItem);
                if (multilangMode != null) {
                    addMultilangFiche(fichePointeur);
                } else {
                    addFiche(fichePointeur);
                }
            }
        }

        private void addCorpusMetadata(CorpusMetadataExport corpusMetadataExport, CorpusMetadata corpusMetadata) {
            addIntitule(corpusMetadataExport, corpusMetadata, null, CorpusMetadataExport.INTITULE_CORPUS);
            addIntitule(corpusMetadataExport, corpusMetadata, "fiche", CorpusMetadataExport.INTITULE_FICHE);
            for (ComplementEngine complementEngine : complementEngineArray) {
                complementEngine.addIntitule(corpusMetadataExport);
            }
            if (exportInclude.isWithAttributeFilter()) {
                Predicate<AttributeKey> attributeFilter = exportInclude.getAttributeFilter();
                for (Attribute attribute : corpusMetadata.getAttributes()) {
                    AttributeKey attributeKey = attribute.getAttributeKey();
                    if (attributeFilter.test(attributeKey)) {
                        for (String value : attribute) {
                            corpusMetadataExport.addAttributeValue(attributeKey.getNameSpace(), attributeKey.getLocalKey(), value);
                        }
                    }
                }
            }
            hookHandler.handle("CorpusMetadata", corpusMetadataExport, corpusMetadata, exportInclude.getHookList());
        }

        private void addIntitule(CorpusMetadataExport corpusMetadataExport, CorpusMetadata corpusMetadata, String name, int intituleType) {
            Labels labels;
            if (name == null) {
                labels = corpusMetadata.getTitleLabels();
            } else {
                labels = corpusMetadata.getPhrases().getPhrase(name);
            }
            if (labels != null) {
                boolean done = false;
                for (Lang lang : langArray) {
                    Label label = labels.getLabel(lang);
                    if (label != null) {
                        corpusMetadataExport.setIntitule(intituleType, label.getLang().toString(), label.getLabelString());
                        done = true;
                    }
                }
                if (!done) {
                    Label label = labels.getFirstLabel();
                    if (label != null) {
                        corpusMetadataExport.setIntitule(intituleType, label.getLang().toString(), label.getLabelString());
                    }
                }
            }
        }

        private void addMultilangFiche(FichePointeur fichePointeur) {
            FicheMeta ficheMeta = (FicheMeta) fichePointeur.getCurrentSubsetItem();
            Set<Lang> langSet = getLangSet(fichePointeur);
            int size = langSet.size();
            if (size == 0) {
                return;
            }
            int ficheid = ficheMeta.getId();
            String ficheidString = String.valueOf(ficheid);
            GeopointItem geopoint = getGeoloc(fichePointeur);
            String[] idStringArray = new String[size];
            int p = 0;
            for (Lang lang : langSet) {
                ExtractionContext customExtractionContext = ExtractionUtils.derive(extractionContext, LocalisationUtils.toUserLangContext(lang));
                FormatSource formatSource = FormatterUtils.toFormatSource(fichePointeur, customExtractionContext, predicate, formatContext);
                String titre = titreFormatter.formatSource(formatSource);
                if (titre == null) {
                    titre = "";
                } else {
                    titre = titre.trim();
                }
                if (titre.length() == 0) {
                    idStringArray[p] = null;
                    p++;
                    continue;
                }
                String langString = lang.toString();
                String id = ficheidString + "_" + langString;
                idStringArray[p] = id;
                p++;
                FicheExport ficheExport = scrutariDataExport.newFiche(id);
                ficheExport.setTitre(titre);
                ficheExport.setSoustitre(soustitreFormatter.formatSource(formatSource));
                ficheExport.setLang(langString);
                ficheExport.setDate(dateFormatter.formatSource(formatSource));
                ficheExport.setHref(hrefFormatter.formatSource(formatSource));
                ficheExport.setFicheIcon(ficheIconFormatter.formatSource(formatSource));
                int complementLength = complementEngineArray.length;
                for (int i = 0; i < complementLength; i++) {
                    complementEngineArray[i].addComplement(i + 1, formatSource, ficheExport);
                }
                for (AttributeEngine attributeEngine : attributeEngineArray) {
                    attributeEngine.addAttribute(formatSource, ficheExport);
                }
                if (geopoint != null) {
                    ficheExport.setGeoloc(geopoint.getLatitude().toString(), geopoint.getLongitude().toString());
                }
                if (langSet.size() > 1) {
                    for (Lang otherLang : langSet) {
                        if (!otherLang.equals(lang)) {
                            ficheExport.addAttributeValue("rel", "alternate", ficheidString + "_" + otherLang.toString());
                        }
                    }
                }
            }
            if (thesaurusEngineArray != null) {
                String corpusName = corpus.getSubsetName();
                for (ThesaurusEngine thesaurusEngine : thesaurusEngineArray) {
                    Thesaurus thesaurus = thesaurusEngine.getThesaurus();
                    String thesaurusName = thesaurus.getSubsetName();
                    for (Liaison liaison : fichePointeur.getStandardLiaisons(thesaurus)) {
                        int weight = liaison.getTie().getWeight();
                        int motcleid = liaison.getSubsetItem().getId();
                        thesaurusEngine.addId(motcleid);
                        String idString = String.valueOf(motcleid);
                        for (String ficheId : idStringArray) {
                            if (ficheId != null) {
                                scrutariDataExport.addIndexation(corpusName, ficheId, thesaurusName, idString, weight);
                            }
                        }
                    }
                }
            }
        }

        private Set<Lang> getLangSet(FichePointeur fichePointeur) {
            if (multilangSet != null) {
                return multilangSet;
            }
            Set<Lang> result = new LinkedHashSet<Lang>();
            Object value = fichePointeur.getValue(multilangFieldKey);
            if (value instanceof LanguageItem) {
                result.add(((LanguageItem) value).getLang());
            } else if (value instanceof FicheItems) {
                for (FicheItem ficheItem : (FicheItems) value) {
                    if (ficheItem instanceof LanguageItem) {
                        result.add(((LanguageItem) ficheItem).getLang());
                    }
                }
            }
            return result;
        }

        private void addFiche(FichePointeur fichePointeur) {
            FicheMeta ficheMeta = (FicheMeta) fichePointeur.getCurrentSubsetItem();
            Lang ficheLang = ficheMeta.getLang();
            LangContext langContext;
            if (ficheLang != null) {
                langContext = LocalisationUtils.toUserLangContext(ficheLang);
            } else {
                langContext = LocalisationUtils.toUserLangContext(langArray[0]);
            }
            ExtractionContext customExtractionContext = ExtractionUtils.derive(extractionContext, langContext);
            FormatSource formatSource = FormatterUtils.toFormatSource(fichePointeur, customExtractionContext, predicate, formatContext);
            int ficheid = ficheMeta.getId();
            String idString = String.valueOf(ficheid);
            FicheExport ficheExport = scrutariDataExport.newFiche(idString);
            ficheExport.setTitre(titreFormatter.formatSource(formatSource));
            ficheExport.setSoustitre(soustitreFormatter.formatSource(formatSource));
            if (ficheLang != null) {
                ficheExport.setLang(ficheLang.toString());
            }
            ficheExport.setDate(dateFormatter.formatSource(formatSource));
            ficheExport.setHref(hrefFormatter.formatSource(formatSource));
            ficheExport.setFicheIcon(ficheIconFormatter.formatSource(formatSource));
            int complementLength = complementEngineArray.length;
            for (int i = 0; i < complementLength; i++) {
                complementEngineArray[i].addComplement(i + 1, formatSource, ficheExport);
            }
            for (AttributeEngine attributeEngine : attributeEngineArray) {
                attributeEngine.addAttribute(formatSource, ficheExport);
            }
            GeopointItem geopoint = getGeoloc(fichePointeur);
            if (geopoint != null) {
                ficheExport.setGeoloc(geopoint.getLatitude().toString(), geopoint.getLongitude().toString());
            }
            if (thesaurusEngineArray != null) {
                String corpusName = corpus.getSubsetName();
                for (ThesaurusEngine thesaurusEngine : thesaurusEngineArray) {
                    Thesaurus thesaurus = thesaurusEngine.getThesaurus();
                    String thesaurusName = thesaurus.getSubsetName();
                    for (Liaison liaison : fichePointeur.getStandardLiaisons(thesaurus)) {
                        int weight = liaison.getTie().getWeight();
                        int motcleid = liaison.getSubsetItem().getId();
                        thesaurusEngine.addId(motcleid);
                        scrutariDataExport.addIndexation(corpusName, idString, thesaurusName, String.valueOf(motcleid), weight);
                    }
                }
            }
        }

        private GeopointItem getGeoloc(FichePointeur fichePointeur) {
            if (geolocalisationFieldKey != null) {
                FicheItem ficheItem = (FicheItem) fichePointeur.getValue(geolocalisationFieldKey);
                if ((ficheItem != null) && (ficheItem instanceof GeopointItem)) {
                    return (GeopointItem) ficheItem;
                }
            }
            return null;
        }

    }

    private ThesaurusEngine initThesaurusEngine(Thesaurus thesaurus, ThesaurusScrutariDef thesaurusScrutariDef) {
        TableDef tableDef = TableDefParser.parse(thesaurusScrutariDef.getFieldGenerationSource(), thesaurus, tableExportContext, LogUtils.NULL_LINEMESSAGEHANDLER, 0);
        SubsetTable subsetTable = SubsetTableBuilder.init(thesaurus).populate(tableDef, tableExportContext, false, "\n").toSubsetTable();
        List<AttributeEngine> attributeEngineList = new ArrayList<AttributeEngine>();
        List<Col> colList = subsetTable.getColList();
        SourceFormatter labelsFormatter = null;
        for (Col col : colList) {
            ColDef colDef = col.getColDef();
            String colName = colDef.getColName();
            SourceFormatter formatter = col.getSourceFormatter();
            if (colName.equals("labels")) {
                if (labelsFormatter == null) {
                    labelsFormatter = formatter;
                }
            } else {
                try {
                    AttributeKey attributeKey = AttributeKey.parse(colName);
                    attributeEngineList.add(new AttributeEngine(attributeKey, formatter));
                } catch (ParseException pe) {

                }
            }
        }
        AttributeEngine[] attributeEngineArray = attributeEngineList.toArray(new AttributeEngine[attributeEngineList.size()]);
        return new ThesaurusEngine(thesaurus, thesaurusScrutariDef, labelsFormatter, attributeEngineArray);
    }


    private class ThesaurusEngine {

        private final Thesaurus thesaurus;
        private final Set<Integer> idSet = new HashSet<Integer>();
        private final UserLangContext[] langContextArray;
        private final SourceFormatter labelsFormatter;
        private final AttributeEngine[] attributeEngineArray;
        private final MutableFormatSource mutableFormatSource;
        private final boolean wholeThesaurus;
        private final ExportInclude exportInclude;

        private ThesaurusEngine(Thesaurus thesaurus, ThesaurusScrutariDef thesaurusScrutariDef, SourceFormatter labelsFormatter, AttributeEngine[] attributeEngineArray) {
            this.thesaurus = thesaurus;
            this.mutableFormatSource = new MutableFormatSource(PointeurFactory.newMotclePointeur(thesaurus), extractionContext, formatContext, predicate);
            this.wholeThesaurus = thesaurusScrutariDef.isWholeThesaurus();
            Langs authorizedLangs = thesaurusLangChecker.getAuthorizedLangs(thesaurus);
            if (authorizedLangs != null) {
                int length = authorizedLangs.size();
                this.langContextArray = new UserLangContext[length];
                for (int i = 0; i < length; i++) {
                    langContextArray[i] = LocalisationUtils.toUserLangContext(authorizedLangs.get(i));
                }
            } else {
                this.langContextArray = null;
            }
            this.attributeEngineArray = attributeEngineArray;
            if (labelsFormatter != null) {
                this.labelsFormatter = labelsFormatter;
            } else {
                this.labelsFormatter = ScrutariExportUtils.getDefaultLabelsFormatter(thesaurus, tableExportContext);
            }
            this.exportInclude = ExportInclude.parse(thesaurusScrutariDef.getIncludeTokenList());
        }

        private void addId(int id) {
            idSet.add(id);
        }

        private Thesaurus getThesaurus() {
            return thesaurus;
        }

        private void addThesaurus() {
            if ((!wholeThesaurus) && (idSet.isEmpty())) {
                return;
            }
            ThesaurusMetadataExport thesaurusMetadataExport = scrutariDataExport.newThesaurus(thesaurus.getSubsetName());
            addThesaurusMetadata(thesaurusMetadataExport, thesaurus.getThesaurusMetadata());
            for (Motcle motcle : thesaurus.getMotcleList()) {
                if ((wholeThesaurus) || (idSet.contains(motcle.getId()))) {
                    addMotcle(motcle);
                }
            }
        }

        private void addMotcle(Motcle motcle) {
            MotcleExport motcleExport = scrutariDataExport.newMotcle(String.valueOf(motcle.getId()));
            mutableFormatSource.getSubsetItemPointeur().setCurrentSubsetItem(motcle);
            if (langContextArray != null) {
                for (UserLangContext langContext : langContextArray) {
                    mutableFormatSource.setLangContext(langContext);
                    motcleExport.setLibelle(langContext.getWorkingLang().toString(), labelsFormatter.formatSource(mutableFormatSource));
                }
            } else {
                Lang lang = motcle.getBabelienLabel().getLang();
                mutableFormatSource.setLangContext(LocalisationUtils.toUserLangContext(lang));
                motcleExport.setLibelle(lang.toString(), labelsFormatter.formatSource(mutableFormatSource));
            }
            for (AttributeEngine attributeEngine : attributeEngineArray) {
                attributeEngine.addAttribute(mutableFormatSource, motcleExport);
            }
        }

        private void addThesaurusMetadata(ThesaurusMetadataExport thesaurusMetadataExport, ThesaurusMetadata thesaurusMetadata) {
            addIntitule(thesaurusMetadataExport, thesaurusMetadata, null, ThesaurusMetadataExport.INTITULE_THESAURUS);
            if (exportInclude.isWithAttributeFilter()) {
                Predicate<AttributeKey> attributeFilter = exportInclude.getAttributeFilter();
                for (Attribute attribute : thesaurusMetadata.getAttributes()) {
                    AttributeKey attributeKey = attribute.getAttributeKey();
                    if (attributeFilter.test(attributeKey)) {
                        for (String value : attribute) {
                            thesaurusMetadataExport.addAttributeValue(attributeKey.getNameSpace(), attributeKey.getLocalKey(), value);
                        }
                    }
                }
            }
            hookHandler.handle("ThesaurusMetadata", thesaurusMetadataExport, thesaurusMetadata, exportInclude.getHookList());
        }

        private void addIntitule(ThesaurusMetadataExport thesaurusMetadataExport, ThesaurusMetadata thesaurusMetadata, @Nullable String name, int intituleType) {
            Labels labels;
            if (name == null) {
                labels = thesaurusMetadata.getTitleLabels();
            } else {
                labels = thesaurusMetadata.getPhrases().getPhrase(name);
            }
            if (labels != null) {
                boolean done = false;
                for (Lang lang : langArray) {
                    Label label = labels.getLabel(lang);
                    if (label != null) {
                        thesaurusMetadataExport.setIntitule(intituleType, label.getLang().toString(), label.getLabelString());
                        done = true;
                    }
                }
                if (!done) {
                    Label label = labels.getFirstLabel();
                    if (label != null) {
                        thesaurusMetadataExport.setIntitule(intituleType, label.getLang().toString(), label.getLabelString());
                    }
                }
            }
        }

    }


    private static class ComplementEngine {

        private Labels labels;
        private final SourceFormatter formatter;
        private boolean isCustomLabels = false;
        private Lang[] langArray;

        private ComplementEngine(SourceFormatter formatter) {
            this.formatter = formatter;
        }

        private void setCustomLabels(Labels customLabels) {
            this.labels = customLabels;
            this.isCustomLabels = true;
        }

        private void setDefaultLabels(Labels labels, Lang[] langArray) {
            this.labels = labels;
            this.isCustomLabels = false;
            this.langArray = langArray;
        }

        private void addIntitule(CorpusMetadataExport corpusMetadataExport) {
            if (labels == null) {
                return;
            }
            int number = corpusMetadataExport.addComplement();
            boolean done = false;
            if (isCustomLabels) {
                for (Label label : labels) {
                    corpusMetadataExport.setComplementIntitule(number, label.getLang().toString(), label.getLabelString());
                    done = true;
                }
            } else {
                for (Lang lang : langArray) {
                    Label label = labels.getLabel(lang);
                    if (label != null) {
                        corpusMetadataExport.setComplementIntitule(number, lang.toString(), label.getLabelString());
                        done = true;
                    }
                }
            }
            if (!done) {
                Label label = labels.getFirstLabel();
                if (label != null) {
                    corpusMetadataExport.setComplementIntitule(number, label.getLang().toString(), label.getLabelString());
                }
            }
        }

        private void addComplement(int complementNumber, FormatSource formatSource, FicheExport ficheExport) {
            String s = formatter.formatSource(formatSource);
            ficheExport.addComplement(complementNumber, s);
        }

    }


    private static class AttributeEngine {

        private final String nameSpace;
        private final String localKey;
        private final SourceFormatter formatter;

        private AttributeEngine(AttributeKey attributeKey, SourceFormatter formatter) {
            this.formatter = formatter;
            this.nameSpace = attributeKey.getNameSpace();
            this.localKey = attributeKey.getLocalKey();
        }

        private void addAttribute(FormatSource formatSource, FicheExport ficheExport) {
            for (String token : getTokens(formatSource)) {
                ficheExport.addAttributeValue(nameSpace, localKey, token);
            }
        }

        private void addAttribute(FormatSource formatSource, MotcleExport motcleExport) {
            for (String token : getTokens(formatSource)) {
                motcleExport.addAttributeValue(nameSpace, localKey, token);
            }
        }

        private String[] getTokens(FormatSource formatSource) {
            String s = formatter.formatSource(formatSource);
            return StringUtils.getTokens(s, '\n', StringUtils.EMPTY_EXCLUDE);
        }

    }


    private static class HrefSourceFormatter implements SourceFormatter {

        private final AccoladePattern hrefPattern;
        private final FicheValueResolver ficheValueResolver = new FicheValueResolver();

        public HrefSourceFormatter(AccoladePattern hrefPattern) {
            this.hrefPattern = hrefPattern;
        }

        @Override
        public String formatSource(FormatSource formatSource) {
            ficheValueResolver.setCurrentFicheMeta((FicheMeta) formatSource.getSubsetItemPointeur().getCurrentSubsetItem());
            ficheValueResolver.setCurrentLang(formatSource.getDefaultLang());
            return hrefPattern.format(ficheValueResolver);
        }


        private static class FicheValueResolver implements ValueResolver {

            private FicheMeta ficheMeta;
            private Lang workingLang;

            private FicheValueResolver() {

            }

            private void setCurrentFicheMeta(FicheMeta ficheMeta) {
                this.ficheMeta = ficheMeta;
            }

            private void setCurrentLang(Lang workingLang) {
                this.workingLang = workingLang;
            }

            @Override
            public String getValue(AccoladeArgument accoladeArgument) {
                switch (accoladeArgument.getName()) {
                    case "corpus":
                        return ficheMeta.getSubsetName();
                    case "id":
                    case "idcorpus":
                        return String.valueOf(ficheMeta.getId());
                    case "lang":
                        return workingLang.toString();
                    case "idalpha":
                        return getMasterIdalpha();
                    default:
                        return null;
                }
            }

            private String getMasterIdalpha() {
                Subset master = ficheMeta.getCorpus().getMasterSubset();
                if ((master != null) && (master instanceof Thesaurus)) {
                    return ((Motcle) master.getSubsetItemById(ficheMeta.getId())).getIdalpha();
                } else {
                    return null;
                }
            }

        }

    }


    private static class MutableFormatSource implements FormatSource {

        private final SubsetItemPointeur subsetItemPointeur;
        private final FormatContext formatContext;
        private final Predicate<SubsetItem> predicate;
        private final ExtractionContext orignalExtractionContext;
        private ExtractionContext customExtractionContext;
        private LangContext langContext;


        private MutableFormatSource(SubsetItemPointeur subsetItemPointeur, ExtractionContext orignalExtractionContext, FormatContext formatContext, Predicate<SubsetItem> predicate) {
            this.subsetItemPointeur = subsetItemPointeur;
            this.orignalExtractionContext = orignalExtractionContext;
            this.formatContext = formatContext;
            this.predicate = predicate;
        }

        @Override
        public SubsetItemPointeur getSubsetItemPointeur() {
            return subsetItemPointeur;
        }

        @Override
        public ExtractionContext getExtractionContext() {
            return customExtractionContext;
        }

        private void setLangContext(LangContext langContext) {
            this.customExtractionContext = ExtractionUtils.derive(orignalExtractionContext, langContext);
        }

        @Override
        public Predicate<SubsetItem> getGlobalPredicate() {
            return predicate;
        }

        @Override
        public FormatContext getFormatContext() {
            return formatContext;
        }

        @Override
        public CellConverterProvider getCellConverterProvider() {
            return TableExportUtils.EMPTY_CELLCONVERTERPROVIDER;
        }

        @Override
        public FormatSource.History getHistory() {
            return FormatterUtils.EMPTY_FORMATSOURCEHISTORY;
        }

        @Override
        public FormatSource.ExtractionInfo getExtractionInfo() {
            return null;
        }

    }

}
