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

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import net.fichotheque.Fichotheque;
import net.fichotheque.FichothequeConstants;
import net.fichotheque.Subset;
import net.fichotheque.SubsetKey;
import net.fichotheque.addenda.Addenda;
import net.fichotheque.album.Album;
import net.fichotheque.corpus.Corpus;
import net.fichotheque.corpus.metadata.CorpusField;
import net.fichotheque.corpus.metadata.CorpusMetadata;
import net.fichotheque.corpus.metadata.FieldKey;
import net.fichotheque.corpus.metadata.SubfieldKey;
import net.fichotheque.format.FichothequeFormatConstants;
import net.fichotheque.format.FichothequeFormatDef;
import net.fichotheque.format.FormatContext;
import net.fichotheque.format.FormatSourceKey;
import net.fichotheque.format.SubsetPathKey;
import net.fichotheque.format.Tokenizer;
import net.fichotheque.format.formatters.AttributeFormatter;
import net.fichotheque.format.formatters.DocumentFormatter;
import net.fichotheque.format.formatters.ExtractionFormatter;
import net.fichotheque.format.formatters.FicheBlockFormatter;
import net.fichotheque.format.formatters.FicheItemFormatter;
import net.fichotheque.format.formatters.FicheMetaFormatter;
import net.fichotheque.format.formatters.IllustrationFormatter;
import net.fichotheque.format.formatters.MotcleFormatter;
import net.fichotheque.format.formatters.SourceFormatter;
import net.fichotheque.format.formatters.SubfieldFormatter;
import net.fichotheque.format.formatters.ValueFormatter;
import net.fichotheque.include.IncludeKey;
import net.fichotheque.thesaurus.Thesaurus;
import net.fichotheque.thesaurus.metadata.ThesaurusFieldKey;
import net.fichotheque.tools.format.parsers.AttributeFormatterParser;
import net.fichotheque.tools.format.parsers.DocumentFormatterParser;
import net.fichotheque.tools.format.parsers.ExtractionFormatterParser;
import net.fichotheque.tools.format.parsers.FicheBlockFormatterParser;
import net.fichotheque.tools.format.parsers.FicheItemFormatterParser;
import net.fichotheque.tools.format.parsers.FicheMetaFormatterParser;
import net.fichotheque.tools.format.parsers.IdalphaFormatterParser;
import net.fichotheque.tools.format.parsers.IllustrationFormatterParser;
import net.fichotheque.tools.format.parsers.MotcleFormatterParser;
import net.fichotheque.tools.format.parsers.SubfieldFormatterParser;
import net.fichotheque.tools.format.parsers.ValueFormatterParser;
import net.fichotheque.tools.format.tokenizers.AttributeTokenizer;
import net.fichotheque.tools.format.tokenizers.ChronoTokenizer;
import net.fichotheque.tools.format.tokenizers.DocumentTokenizer;
import net.fichotheque.tools.format.tokenizers.ExtractionTokenizer;
import net.fichotheque.tools.format.tokenizers.FicheBlockTokenizer;
import net.fichotheque.tools.format.tokenizers.FicheItemTokenizer;
import net.fichotheque.tools.format.tokenizers.FicheMetaTokenizer;
import net.fichotheque.tools.format.tokenizers.FicheParentageTokenizer;
import net.fichotheque.tools.format.tokenizers.IllustrationTokenizer;
import net.fichotheque.tools.format.tokenizers.LiageTokenizer;
import net.fichotheque.tools.format.tokenizers.MasterMotcleTokenizer;
import net.fichotheque.tools.format.tokenizers.MotcleTokenizer;
import net.fichotheque.tools.format.tokenizers.ParentageTokenizer;
import net.fichotheque.tools.format.tokenizers.SimpleValueTokenizer;
import net.fichotheque.tools.format.tokenizers.ThesaurusFieldTokenizer;
import net.fichotheque.tools.format.tokenizers.ThesaurusThisTokenizer;
import net.fichotheque.tools.format.tokenizers.TokenizerProvider;
import net.fichotheque.tools.format.tokenizers.subfield.SubfieldTokenizerFactory;
import net.fichotheque.utils.FichothequeFormatUtils;
import net.fichotheque.utils.FichothequeUtils;
import net.fichotheque.utils.FormatterUtils;
import net.mapeadores.util.attr.AttributeKey;
import net.mapeadores.util.exceptions.SwitchException;
import net.mapeadores.util.format.FormatConstants;
import net.mapeadores.util.instruction.InstructionResolverProvider;
import net.mapeadores.util.localisation.LocalisationUtils;
import net.mapeadores.util.logging.MessageHandler;


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

    private final MessageHandler messageHandler;
    private final Subset mainSubset;
    private final Fichotheque fichotheque;
    private final FormatContext formatContext;
    private final InstructionResolverProvider provider;
    private final List<TokenizerProvider> sourceTokenizerProviderList = new ArrayList<TokenizerProvider>();
    private final String defaultSeparator;
    private final boolean defaultGlobalSelect;
    private final boolean ignoreMissingCorpusField;
    private SourceFormatter sourceFormatter;
    private short itemFilter;
    private boolean globalSelect;
    private boolean idSort;


    private FichothequeFormatDefEngine(Parameters parameters, Subset mainSubset, MessageHandler messageHandler) {
        this.mainSubset = mainSubset;
        this.fichotheque = mainSubset.getFichotheque();
        this.messageHandler = messageHandler;
        this.formatContext = parameters.formatContext;
        this.defaultSeparator = parameters.defaultSeparator;
        this.defaultGlobalSelect = parameters.defaultGlobalSelect;
        this.ignoreMissingCorpusField = parameters.ignoreMissingCorpusField;
        this.provider = formatContext.getInstructionResolverProvider();
    }

    public static SourceFormatter compute(Subset subset, FichothequeFormatDef formatDef, MessageHandler messageHandler, Parameters parameters) {
        if ((!(subset instanceof Corpus) && (!(subset instanceof Thesaurus)))) {
            throw new IllegalArgumentException("wrong subset type");
        }
        FichothequeFormatDefEngine engine = new FichothequeFormatDefEngine(parameters, subset, messageHandler);
        boolean done = engine.init(formatDef);
        if (done) {
            return engine.sourceFormatter;
        } else {
            return null;
        }
    }

    private boolean init(FichothequeFormatDef formatDef) {
        if (formatDef.getBoolean(FichothequeFormatConstants.NOITEM_PARAMKEY)) {
            itemFilter = FicheItemTokenizer.NOITEM_FILTER;
        } else if (formatDef.getBoolean(FichothequeFormatConstants.ONLYITEM_PARAMKEY)) {
            itemFilter = FicheItemTokenizer.ONLYITEM_FILTER;
        } else {
            itemFilter = FicheItemTokenizer.NONE_FILTER;
        }
        this.globalSelect = formatDef.getBoolean(FormatConstants.GLOBALSELECT_PARAMKEY, defaultGlobalSelect);
        this.idSort = formatDef.getBoolean(FichothequeFormatConstants.IDSORT_PARAMKEY);
        List<FormatSourceKey> formatSourceKeyList = formatDef.getFormatSourceKeyList();
        int size = formatSourceKeyList.size();
        for (int i = 0; i < size; i++) {
            FormatSourceKey formatSourceKey = formatSourceKeyList.get(i);
            String pattern = FichothequeFormatUtils.getFormatPatternBySourceIndex(formatDef, i);
            if (!init(formatSourceKey, pattern)) {
                return false;
            }
        }
        this.sourceFormatter = FormatDefSourceFormatter.build(mainSubset, formatDef, TokenizerProvider.toByLangProvider(sourceTokenizerProviderList), defaultSeparator, formatContext);
        return true;
    }

    private boolean init(FormatSourceKey formatSourceKey, String pattern) {
        SubsetPathKey subsetPathKey = formatSourceKey.getSubsetPathKey();
        if (subsetPathKey == null) {
            TokenizerProvider sourceTokenizerProvider;
            if (mainSubset instanceof Thesaurus) {
                sourceTokenizerProvider = toSourceTokenizerProvider((Thesaurus) mainSubset, formatSourceKey, pattern);
            } else {
                sourceTokenizerProvider = toSourceTokenizerProvider((Corpus) mainSubset, formatSourceKey, pattern);
            }
            if (sourceTokenizerProvider == null) {
                return false;
            }
            sourceTokenizerProviderList.add(sourceTokenizerProvider);
            return true;
        } else {
            Subset lastSubset = checkSubsetPathKey(subsetPathKey);
            if (lastSubset == null) {
                return false;
            }
            TokenizerProvider sourceTokenizerProvider;
            if (lastSubset instanceof Thesaurus) {
                sourceTokenizerProvider = toSourceTokenizerProvider((Thesaurus) lastSubset, formatSourceKey, pattern);
            } else {
                sourceTokenizerProvider = toSourceTokenizerProvider((Corpus) lastSubset, formatSourceKey, pattern);
            }
            if (sourceTokenizerProvider == null) {
                return false;
            }
            sourceTokenizerProviderList.add(TokenizerProvider.subsetPath(fichotheque, subsetPathKey, sourceTokenizerProvider, globalSelect));
            return true;
        }
    }

    private Subset checkSubsetPathKey(SubsetPathKey subsetPathKey) {
        int length = subsetPathKey.getLength();
        Subset currentSubset = mainSubset;
        for (int i = 0; i < length; i++) {
            short type = subsetPathKey.getKeyType(i);
            Object keyObject = subsetPathKey.getKeyObject(i);
            switch (type) {
                case SubsetPathKey.THESAURUS_TREE:
                    if (!(currentSubset instanceof Thesaurus)) {
                        addSourceError("_ error.unsupported.subset_thesaurustree", currentSubset.getSubsetKey(), keyObject.toString());
                        return null;
                    }
                    break;
                case SubsetPathKey.PARENTAGE_SUBSETKEY:
                    SubsetKey parentageSubsetKey = (SubsetKey) keyObject;
                    Subset parentageSubset = fichotheque.getSubset(parentageSubsetKey);
                    if (parentageSubset == null) {
                        addSourceError("_ error.unknown.subset", parentageSubsetKey);
                        return null;
                    }
                    boolean test = FichothequeUtils.ownToSameParentage(parentageSubset, currentSubset);
                    if (!test) {
                        addSourceError("_ error.unsupported.parentage", parentageSubsetKey, currentSubset.getSubsetKey());
                        return null;
                    }
                    currentSubset = parentageSubset;
                    break;

                default:
                    SubsetKey childSubsetKey = ((IncludeKey) keyObject).getSubsetKey();
                    Subset childSubset = fichotheque.getSubset(childSubsetKey);
                    if (childSubset == null) {
                        addSourceError("_ error.unknown.subset", childSubsetKey);
                        return null;
                    }
                    currentSubset = childSubset;
                    break;
            }
        }
        return currentSubset;
    }

    private TokenizerProvider toSourceTokenizerProvider(Corpus currentCorpus, FormatSourceKey formatSourceKey, String pattern) {
        Tokenizer tokenizer = null;
        switch (formatSourceKey.getSourceType()) {
            case FormatSourceKey.SPECIAL_TYPE:
                tokenizer = initSpecialTokenizer(formatSourceKey, pattern);
                break;
            case FormatSourceKey.FIELDKEY_TYPE:
                tokenizer = initFieldKey(currentCorpus, (FieldKey) formatSourceKey.getKeyObject(), pattern);
                break;
            case FormatSourceKey.SUBFIELDKEY_TYPE:
                tokenizer = initSubfieldKey(currentCorpus, formatSourceKey, pattern);
                break;
            case FormatSourceKey.NAMEPREFIXFIELDKEY_TYPE:
                return initNamePrefixFieldKey(currentCorpus, formatSourceKey, pattern);
            case FormatSourceKey.INCLUDEKEY_TYPE:
                tokenizer = initIncludeKey(formatSourceKey, pattern);
                break;
            case FormatSourceKey.THESAURUSFIELDKEY_TYPE:
                addSourceError("_ error.unsupported.formatsource", formatSourceKey.getKeyString());
                return null;
            case FormatSourceKey.ATTRIBUTEKEY_TYPE:
                tokenizer = initAttributeKey(formatSourceKey, pattern);
                break;
            case FormatSourceKey.ID_TYPE:
                tokenizer = initFieldKey(currentCorpus, FieldKey.ID, pattern);
                break;
            case FormatSourceKey.LANG_TYPE:
                tokenizer = initFieldKey(currentCorpus, FieldKey.LANG, pattern);
                break;
            case FormatSourceKey.PARENTAGESUBSETKEY_TYPE:
                tokenizer = initParentageSubsetKey(currentCorpus, formatSourceKey, pattern);
                break;
            case FormatSourceKey.SPECIALINCLUDENAME_TYPE:
                tokenizer = initSpecialInclude(currentCorpus, formatSourceKey, pattern);
                break;
            default:
                throw new SwitchException("Unknown sourceType: " + formatSourceKey.getSourceType());
        }
        if (tokenizer == null) {
            return null;
        }
        return TokenizerProvider.simple(tokenizer);
    }

    private TokenizerProvider toSourceTokenizerProvider(Thesaurus currentThesaurus, FormatSourceKey formatSourceKey, String pattern) {
        Tokenizer sourceTokenizer = null;
        switch (formatSourceKey.getSourceType()) {
            case FormatSourceKey.SPECIAL_TYPE:
                sourceTokenizer = initSpecialTokenizer(formatSourceKey, pattern);
                break;
            case FormatSourceKey.FIELDKEY_TYPE:
            case FormatSourceKey.SUBFIELDKEY_TYPE:
            case FormatSourceKey.NAMEPREFIXFIELDKEY_TYPE:
                addSourceError("_ error.unsupported.formatsource", formatSourceKey.getKeyString());
                return null;
            case FormatSourceKey.INCLUDEKEY_TYPE:
                sourceTokenizer = initIncludeKey(formatSourceKey, pattern);
                break;
            case FormatSourceKey.THESAURUSFIELDKEY_TYPE:
                sourceTokenizer = initThesaurusFieldKey(currentThesaurus, (ThesaurusFieldKey) formatSourceKey.getKeyObject(), pattern);
                break;
            case FormatSourceKey.ATTRIBUTEKEY_TYPE:
                sourceTokenizer = initAttributeKey(formatSourceKey, pattern);
                break;
            case FormatSourceKey.ID_TYPE:
                sourceTokenizer = initThesaurusFieldKey(currentThesaurus, ThesaurusFieldKey.ID, pattern);
                break;
            case FormatSourceKey.LANG_TYPE:
                sourceTokenizer = initThesaurusFieldKey(currentThesaurus, ThesaurusFieldKey.BABELIENLANG, pattern);
                break;
            case FormatSourceKey.PARENTAGESUBSETKEY_TYPE:
                sourceTokenizer = initParentageSubsetKey(currentThesaurus, formatSourceKey, pattern);
                break;
            case FormatSourceKey.SPECIALINCLUDENAME_TYPE:
                sourceTokenizer = initSpecialInclude(currentThesaurus, formatSourceKey, pattern);
                break;
            default:
                throw new SwitchException("Unknown sourceType: " + formatSourceKey.getSourceType());
        }
        if (sourceTokenizer == null) {
            return null;
        }
        return TokenizerProvider.simple(sourceTokenizer);
    }

    private Tokenizer initSpecialTokenizer(FormatSourceKey formatSourceKey, String pattern) {
        if (formatSourceKey.equals(FormatSourceKey.EXTRACTION)) {
            ExtractionFormatter extractionFormatter = ExtractionFormatterParser.parse(pattern, formatContext, messageHandler);
            if (extractionFormatter == null) {
                return null;
            }
            return new ExtractionTokenizer(extractionFormatter);
        }
        if (formatSourceKey.equals(FormatSourceKey.CONSTANT)) {
            return FormatterUtils.initConstantTokenizer(pattern);
        }
        return null;
    }

    /*
    * Les champs de thésaurus n'ont que {value} comme formatage disponible,
    * sauf pour this qui a le même formatage qu'un mot-clé croisé avec une fiche
     */
    private Tokenizer initThesaurusFieldKey(Thesaurus currentThesaurus, ThesaurusFieldKey thesaurusFieldKey, String pattern) {
        if (thesaurusFieldKey.equals(ThesaurusFieldKey.THIS)) {
            MotcleFormatter motcleFormatter = MotcleFormatterParser.parse(currentThesaurus.isIdalphaType(), pattern, provider, false, messageHandler);
            if (motcleFormatter == null) {
                return null;
            }
            return new ThesaurusThisTokenizer(currentThesaurus, motcleFormatter);
        } else {
            ValueFormatter valueFormatter;
            if ((thesaurusFieldKey.equals(ThesaurusFieldKey.IDALPHA)) || (thesaurusFieldKey.equals(ThesaurusFieldKey.PARENT_IDALPHA))) {
                valueFormatter = IdalphaFormatterParser.parse(pattern, provider, messageHandler);
            } else {
                valueFormatter = ValueFormatterParser.parse(pattern, provider, messageHandler);
            }
            if (valueFormatter == null) {
                return null;
            }
            return new ThesaurusFieldTokenizer(currentThesaurus, thesaurusFieldKey, valueFormatter);
        }
    }

    private Tokenizer initFieldKey(Corpus currentCorpus, FieldKey fieldKey, String pattern) {
        CorpusField corpusField = currentCorpus.getCorpusMetadata().getCorpusField(fieldKey);
        if (corpusField == null) {
            if (ignoreMissingCorpusField) {
                return FormatterUtils.EMPTY_TOKENIZER;
            } else {
                addSourceError("_ error.unknown.fieldkey", fieldKey);
                return null;
            }
        }
        Tokenizer tokenizer;
        switch (getTokenizerType(corpusField)) {
            case 1:
                tokenizer = createFicheBlockTokenizer(corpusField, pattern);
                break;
            case 2:
                ValueFormatter valueFormatter = ValueFormatterParser.parse(pattern, provider, messageHandler);
                if (valueFormatter == null) {
                    tokenizer = null;
                } else {
                    tokenizer = new SimpleValueTokenizer(corpusField, valueFormatter);
                }
                break;
            default:
                tokenizer = createFicheItemTokenizer(corpusField, pattern);
        }
        return tokenizer;
    }

    private TokenizerProvider initNamePrefixFieldKey(Corpus currentCorpus, FormatSourceKey formatSourceKey, String pattern) {
        FieldKey namePrefixFieldKey = (FieldKey) formatSourceKey.getKeyObject();
        Map<String, Tokenizer> tokenizerMap = new HashMap<String, Tokenizer>();
        String prefix = namePrefixFieldKey.getFieldName();
        int prefixLength = prefix.length();
        CorpusMetadata corpusMetadata = currentCorpus.getCorpusMetadata();
        switch (namePrefixFieldKey.getCategory()) {
            case FieldKey.PROP_CATEGORY: {
                for (CorpusField corpusField : corpusMetadata.getPropList()) {
                    String fieldName = corpusField.getFieldName();
                    if (fieldName.startsWith(prefix)) {
                        String part2 = fieldName.substring(prefixLength);
                        Tokenizer sourceTokenizer = createFicheItemTokenizer(corpusField, pattern);
                        if (sourceTokenizer == null) {
                            return null;
                        }
                        tokenizerMap.put(part2, sourceTokenizer);
                    }
                }
                break;
            }
            case FieldKey.INFO_CATEGORY: {
                for (CorpusField corpusField : corpusMetadata.getInfoList()) {
                    String fieldName = corpusField.getFieldName();
                    if (fieldName.startsWith(prefix)) {
                        String part2 = fieldName.substring(prefixLength);
                        Tokenizer sourceTokenizer = createFicheItemTokenizer(corpusField, pattern);
                        if (sourceTokenizer == null) {
                            return null;
                        }
                        tokenizerMap.put(part2, sourceTokenizer);
                    }
                }
                break;
            }
            case FieldKey.SECTION_CATEGORY: {
                for (CorpusField corpusField : corpusMetadata.getSectionList()) {
                    String fieldName = corpusField.getFieldName();
                    if (fieldName.startsWith(prefix)) {
                        String part2 = fieldName.substring(prefixLength);
                        Tokenizer tokenizer = createFicheBlockTokenizer(corpusField, pattern);
                        if (tokenizer == null) {
                            return null;
                        }
                        tokenizerMap.put(part2, tokenizer);
                    }
                }
                break;
            }
        }
        if (tokenizerMap.isEmpty()) {
            if (ignoreMissingCorpusField) {
                return TokenizerProvider.NULL;
            } else {
                addSourceError("_ error.unknown.fieldkeytypeprefix", namePrefixFieldKey);
                return null;
            }
        }
        return TokenizerProvider.prefix(prefix, tokenizerMap);
    }

    private Tokenizer createFicheBlockTokenizer(CorpusField corpusField, String pattern) {
        FicheBlockFormatter ficheBlockFormatter = FicheBlockFormatterParser.parse(pattern, formatContext, messageHandler);
        if (ficheBlockFormatter == null) {
            return null;
        }
        return new FicheBlockTokenizer(corpusField, ficheBlockFormatter);
    }

    private Tokenizer createFicheItemTokenizer(CorpusField corpusField, String pattern) {
        FicheItemFormatter ficheItemFormatter = FicheItemFormatterParser.parse(corpusField, pattern, formatContext, messageHandler);
        if (ficheItemFormatter == null) {
            return null;
        }
        return FicheItemTokenizer.newInstance(corpusField, ficheItemFormatter, itemFilter);
    }

    private Tokenizer initSubfieldKey(Corpus currentCorpus, FormatSourceKey formatSourceKey, String pattern) {
        SubfieldKey subfieldKey = (SubfieldKey) formatSourceKey.getKeyObject();
        FieldKey fieldKey = subfieldKey.getFieldKey();
        CorpusField corpusField = currentCorpus.getCorpusMetadata().getCorpusField(fieldKey);
        if (corpusField == null) {
            if (ignoreMissingCorpusField) {
                return FormatterUtils.EMPTY_TOKENIZER;
            } else {
                addSourceError("_ error.unknown.subfieldkey", subfieldKey);
                return null;
            }
        }
        if (!SubfieldKey.isLegalSubfield(corpusField, subfieldKey.getSubtype())) {
            addSourceError("_ error.unsupported.subfieldkey", subfieldKey);
            return null;
        }
        SubfieldFormatter subfieldFormatter = SubfieldFormatterParser.parse(subfieldKey.getSubtype(), pattern, formatContext, messageHandler);
        if (subfieldFormatter == null) {
            return null;
        }
        return SubfieldTokenizerFactory.newInstance(corpusField, subfieldKey, subfieldFormatter);
    }

    private Tokenizer initAttributeKey(FormatSourceKey formatSourceKey, String pattern) {
        AttributeKey attributeKey = (AttributeKey) formatSourceKey.getKeyObject();
        AttributeFormatter attributeFormatter = AttributeFormatterParser.parse(pattern, formatContext, messageHandler);
        if (attributeFormatter == null) {
            return null;
        }
        return new AttributeTokenizer(attributeKey, attributeFormatter);
    }

    private Tokenizer initSpecialInclude(Subset currentSubset, FormatSourceKey formatSourceKey, String pattern) {
        String specialIncludeName = (String) formatSourceKey.getKeyObject();
        if (specialIncludeName.equals(FichothequeConstants.PARENTAGE_NAME)) {
            FicheMetaFormatter ficheMetaFormatter = FicheMetaFormatterParser.parse(pattern, formatContext, false, true, messageHandler);
            if (ficheMetaFormatter == null) {
                return null;
            }
            return new ParentageTokenizer(ficheMetaFormatter, globalSelect);
        }
        if (!(currentSubset instanceof Corpus)) {
            addSourceError("_ error.unsupported.formatsource", formatSourceKey.getKeyString());
            return null;
        }
        switch (specialIncludeName) {
            case FichothequeConstants.DATECREATION_NAME:
            case FichothequeConstants.DATEMODIFICATION_NAME:
                FicheItemFormatter ficheItemFormatter = FicheItemFormatterParser.parse(CorpusField.DATE_FIELD, pattern, formatContext, messageHandler);
                if (ficheItemFormatter == null) {
                    return null;
                }
                return new ChronoTokenizer(specialIncludeName, ficheItemFormatter);
            case FichothequeConstants.LIAGE_NAME:
                FicheMetaFormatter ficheMetaFormatter = FicheMetaFormatterParser.parse(pattern, formatContext, false, true, messageHandler);
                if (ficheMetaFormatter == null) {
                    return null;
                }
                return new LiageTokenizer(ficheMetaFormatter, globalSelect, formatContext.getLiageTest((Corpus) currentSubset));
            default:
                throw new SwitchException("Unknown special include name: " + specialIncludeName);
        }
    }

    private Tokenizer initIncludeKey(FormatSourceKey formatSourceKey, String pattern) {
        IncludeKey includeKey = (IncludeKey) formatSourceKey.getKeyObject();
        SubsetKey subsetKey = includeKey.getSubsetKey();
        if (subsetKey.isCorpusSubset()) {
            Corpus corpus = (Corpus) fichotheque.getSubset(subsetKey);
            if (corpus == null) {
                addSourceError("_ error.unknown.subset", includeKey.getSubsetKey());
                return null;
            }
            FicheMetaFormatter ficheMetaFormatter = FicheMetaFormatterParser.parse(pattern, formatContext, includeKey.hasWeightFilter(), false, messageHandler);
            if (ficheMetaFormatter == null) {
                return null;
            }
            return new FicheMetaTokenizer(corpus, ficheMetaFormatter, includeKey.getMode(), includeKey.getWeightFilter(), globalSelect, idSort);
        } else if (subsetKey.isThesaurusSubset()) {
            Thesaurus thesaurus = (Thesaurus) fichotheque.getSubset(subsetKey);
            if (thesaurus == null) {
                addSourceError("_ error.unknown.subset", includeKey.getSubsetKey());
                return null;
            }
            MotcleFormatter motcleFormatter = MotcleFormatterParser.parse(thesaurus.isIdalphaType(), pattern, provider, includeKey.hasWeightFilter(), messageHandler);
            if (motcleFormatter == null) {
                return null;
            }
            return new MotcleTokenizer(thesaurus, motcleFormatter, includeKey.getMode(), includeKey.getWeightFilter(), globalSelect);
        } else if (subsetKey.isAlbumSubset()) {
            Album album = (Album) fichotheque.getSubset(subsetKey);
            if (album == null) {
                addSourceError("_ error.unknown.subset", includeKey.getSubsetKey());
                return null;
            }
            IllustrationFormatter illustrationFormatter = IllustrationFormatterParser.parse(pattern, formatContext, includeKey.hasWeightFilter(), messageHandler);
            if (illustrationFormatter == null) {
                return null;
            }
            return new IllustrationTokenizer(album, illustrationFormatter, includeKey.getMode(), includeKey.getWeightFilter(), globalSelect);
        } else if (subsetKey.isAddendaSubset()) {
            Addenda addenda = (Addenda) fichotheque.getSubset(subsetKey);
            if (addenda == null) {
                addSourceError("_ error.unknown.subset", includeKey.getSubsetKey());
                return null;
            }
            DocumentFormatter documentFormatter = DocumentFormatterParser.parse(pattern, formatContext, includeKey.hasWeightFilter(), messageHandler);
            if (documentFormatter == null) {
                return null;
            }
            return new DocumentTokenizer(addenda, documentFormatter, includeKey.getMode(), includeKey.getWeightFilter(), globalSelect);
        }
        throw new SwitchException("Unknown include key: " + includeKey);
    }

    private Tokenizer initParentageSubsetKey(Subset currentSubset, FormatSourceKey formatSourceKey, String pattern) {
        SubsetKey parentageSubsetKey = (SubsetKey) formatSourceKey.getKeyObject();
        Subset parentageSubset = fichotheque.getSubset(parentageSubsetKey);
        if (parentageSubset == null) {
            addSourceError("_ error.unknown.subset", SubsetKey.toParentageString(parentageSubsetKey));
            return null;
        }
        boolean test = FichothequeUtils.ownToSameParentage(parentageSubset, currentSubset);
        if (!test) {
            addSourceError("_ error.unsupported.parentage", parentageSubsetKey, currentSubset.getSubsetKey());
            return null;
        }
        if (parentageSubset instanceof Thesaurus) {
            MotcleFormatter motcleFormatter = MotcleFormatterParser.parse(((Thesaurus) parentageSubset).isIdalphaType(), pattern, provider, false, messageHandler);
            if (motcleFormatter == null) {
                return null;
            }
            return new MasterMotcleTokenizer((Thesaurus) parentageSubset, motcleFormatter, globalSelect);
        } else {
            FicheMetaFormatter ficheMetaFormatter = FicheMetaFormatterParser.parse(pattern, formatContext, false, true, messageHandler);
            if (ficheMetaFormatter == null) {
                return null;
            }
            return new FicheParentageTokenizer((Corpus) parentageSubset, ficheMetaFormatter, globalSelect);
        }
    }

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

    public static Parameters parameters(FormatContext formatContext) {
        return new Parameters(formatContext);
    }

    private static int getTokenizerType(CorpusField corpusField) {
        switch (corpusField.getCategory()) {
            case FieldKey.SECTION_CATEGORY:
                return 1;
            case FieldKey.SPECIAL_CATEGORY:
                switch (corpusField.getFieldString()) {
                    case FieldKey.SPECIAL_TITLE:
                    case FieldKey.SPECIAL_ID:
                        return 2;
                }
            default:
                return 0;
        }
    }


    public static class Parameters {

        private final FormatContext formatContext;
        private String defaultSeparator = ";";
        private boolean defaultGlobalSelect = false;
        private boolean ignoreMissingCorpusField;

        private Parameters(FormatContext formatContext) {
            this.formatContext = formatContext;
        }

        public FormatContext formatContext() {
            return formatContext;
        }

        public String defaultSeparator() {
            return defaultSeparator;
        }

        public Parameters defaultSeparator(String defaultSeparator) {
            this.defaultSeparator = defaultSeparator;
            return this;
        }

        public boolean defaultGlobalSelect() {
            return defaultGlobalSelect;
        }

        public Parameters defaultGlobalSelect(boolean defaultGlobalSelect) {
            this.defaultGlobalSelect = defaultGlobalSelect;
            return this;
        }

        public boolean ignoreMissingCorpusField() {
            return ignoreMissingCorpusField;
        }

        public Parameters ignoreMissingCorpusField(boolean ignoreMissingCorpusField) {
            this.ignoreMissingCorpusField = ignoreMissingCorpusField;
            return this;
        }

    }

}
