/* FichothequeLib_Xml - 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.xml.extraction;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
import net.fichotheque.Fichotheque;
import net.fichotheque.Subset;
import net.fichotheque.SubsetKey;
import net.fichotheque.addenda.Addenda;
import net.fichotheque.addenda.Document;
import net.fichotheque.album.Album;
import net.fichotheque.album.Illustration;
import net.fichotheque.corpus.Corpus;
import net.fichotheque.corpus.FicheMeta;
import net.fichotheque.corpus.fiche.DateItem;
import net.fichotheque.corpus.fiche.FicheBlocks;
import net.fichotheque.corpus.fiche.FicheItem;
import net.fichotheque.corpus.fiche.FicheItems;
import net.fichotheque.corpus.fiche.LanguageItem;
import net.fichotheque.corpus.fiche.ParaItem;
import net.fichotheque.corpus.metadata.CorpusField;
import net.fichotheque.corpus.metadata.CorpusMetadata;
import net.fichotheque.corpus.metadata.FieldKey;
import net.fichotheque.corpus.metadata.FieldOptionConstants;
import net.fichotheque.croisement.Croisement;
import net.fichotheque.croisement.Croisements;
import net.fichotheque.extraction.DataResolver;
import net.fichotheque.extraction.ExtractParameters;
import net.fichotheque.extraction.ExtractionConstants;
import net.fichotheque.extraction.ExtractionContext;
import net.fichotheque.extraction.IrefConverter;
import net.fichotheque.extraction.def.AddendaExtractDef;
import net.fichotheque.extraction.def.AlbumExtractDef;
import net.fichotheque.extraction.def.CorpusExtractDef;
import net.fichotheque.extraction.def.FicheFilter;
import net.fichotheque.extraction.def.TagNameInfo;
import net.fichotheque.extraction.def.ThesaurusExtractDef;
import net.fichotheque.extraction.filterunit.AddendaExtractFilterUnit;
import net.fichotheque.extraction.filterunit.AlbumExtractFilterUnit;
import net.fichotheque.extraction.filterunit.BodyFilterUnit;
import net.fichotheque.extraction.filterunit.ChronoFilterUnit;
import net.fichotheque.extraction.filterunit.CorpusExtractFilterUnit;
import net.fichotheque.extraction.filterunit.DataFilterUnit;
import net.fichotheque.extraction.filterunit.FicheParentageFilterUnit;
import net.fichotheque.extraction.filterunit.FieldKeyFilterUnit;
import net.fichotheque.extraction.filterunit.FieldNamePrefixFilterUnit;
import net.fichotheque.extraction.filterunit.FilterUnit;
import net.fichotheque.extraction.filterunit.HeadFilterUnit;
import net.fichotheque.extraction.filterunit.LotFilterUnit;
import net.fichotheque.extraction.filterunit.MasterMotcleFilterUnit;
import net.fichotheque.extraction.filterunit.PhraseFilterUnit;
import net.fichotheque.extraction.filterunit.ThesaurusExtractFilterUnit;
import net.fichotheque.extraction.run.AddendaExtractor;
import net.fichotheque.extraction.run.AlbumExtractor;
import net.fichotheque.extraction.run.CorpusExtractor;
import net.fichotheque.extraction.run.FicheExtractInfo;
import net.fichotheque.extraction.run.ThesaurusExtractor;
import net.fichotheque.pointeurs.FichePointeur;
import net.fichotheque.pointeurs.SubsetItemPointeur;
import net.fichotheque.selection.DocumentSelector;
import net.fichotheque.selection.FicheSelector;
import net.fichotheque.selection.IllustrationSelector;
import net.fichotheque.selection.MotcleSelector;
import net.fichotheque.selection.SelectionContext;
import net.fichotheque.thesaurus.Motcle;
import net.fichotheque.thesaurus.Thesaurus;
import net.fichotheque.utils.EligibilityUtils;
import net.fichotheque.utils.ExtractionUtils;
import net.fichotheque.utils.FichothequeUtils;
import net.fichotheque.utils.PointeurUtils;
import net.fichotheque.utils.pointeurs.PointeurFactory;
import net.fichotheque.utils.selection.DocumentSelectorBuilder;
import net.fichotheque.utils.selection.FicheSelectorBuilder;
import net.fichotheque.utils.selection.IllustrationSelectorBuilder;
import net.fichotheque.utils.selection.MotcleSelectorBuilder;
import net.fichotheque.utils.selection.SelectionContextBuilder;
import net.mapeadores.util.exceptions.SwitchException;
import net.mapeadores.util.localisation.Lang;
import net.mapeadores.util.primitives.FuzzyDate;
import net.mapeadores.util.text.alphabet.AlphabeticEntry;
import net.mapeadores.util.xml.FillControllerXMLPart;
import net.mapeadores.util.xml.XMLPart;
import net.mapeadores.util.xml.XMLUtils;
import net.mapeadores.util.xml.XMLWriter;


/**
 *
 * @author Vincent Calame
 */
public class FicheXMLPart extends XMLPart {

    private final FilterUnitXMLPart filterUnitPart;
    private final TagNames tagNames;
    private FicheFilter ficheFilter;
    private FichePointeur fichePointeur;

    public FicheXMLPart(XMLWriter xmlWriter, ExtractParameters extractParameters) {
        super(xmlWriter);
        this.tagNames = new TagNames(extractParameters.getExtractVersion());
        this.filterUnitPart = new FilterUnitXMLPart(xmlWriter, extractParameters, tagNames);
    }

    public void addFiche(FicheExtractInfo ficheExtractInfo) throws IOException {
        updatePointeur(ficheExtractInfo);
        ficheFilter = ficheExtractInfo.getFicheFilter();
        FicheMeta ficheMeta = ficheExtractInfo.getFicheMeta();
        Croisement croisement = ficheExtractInfo.getCroisement();
        String tagName = getTagName(ficheFilter.getTagNameInfo());
        if (tagName != null) {
            startOpenTag(tagName);
            addAttribute("id", String.valueOf(ficheMeta.getId()));
            XMLUtils.addXmlLangAttribute(this, ficheMeta.getLang());
            int weight = ExtractionXMLUtils.getMainWeight(croisement);
            if (weight > 0) {
                addAttribute(tagNames.getWeightAttributeName(), String.valueOf(weight));
            }
            addAttribute("corpus", ficheMeta.getSubsetName());
            ExtractionXMLUtils.addCellMax(this, ficheFilter);
            if (ficheFilter.getFilterUnitList().isEmpty()) {
                closeEmptyTag();
                return;
            }

            endOpenTag();
            ExtractionXMLUtils.writeCroisement(this, croisement, tagNames);
        }
        addGroupClauseObject(ficheExtractInfo.getGroupClauseObject());
        filterUnitPart.addFilterUnitList(ficheFilter.getFilterUnitList());
        if (tagName != null) {
            closeTag(tagName);
        }
    }

    private void updatePointeur(FicheExtractInfo ficheExtractInfo) {
        boolean withSection = (ficheExtractInfo.getFicheFilter().isWithCorpsdefiche());
        FicheMeta ficheMeta = ficheExtractInfo.getFicheMeta();
        Corpus corpus = ficheMeta.getCorpus();
        if (fichePointeur != null) {
            if (!fichePointeur.getCorpus().equals(corpus)) {
                fichePointeur = null;
            } else if (fichePointeur.isWithSection() != withSection) {
                fichePointeur = null;
            }
        }
        if (fichePointeur == null) {
            fichePointeur = PointeurFactory.newFichePointeur(corpus, withSection);
        }
        fichePointeur.setCurrentSubsetItem(ficheMeta);
        filterUnitPart.updatePointeur(fichePointeur);
    }

    private void addGroupClauseObject(Object groupClauseObject) throws IOException {
        if (groupClauseObject == null) {
            return;
        }
        if (groupClauseObject instanceof AlphabeticEntry) {
            AlphabeticEntry alphabeticEntry = (AlphabeticEntry) groupClauseObject;
            startOpenTag("alphabet");
            if (alphabeticEntry.isWithArticle()) {
                addAttribute("article", alphabeticEntry.getArticleString());
                addAttribute("espace", (alphabeticEntry.isWithSeparationSpace()) ? 1 : 0);
            }
            addAttribute("titre", alphabeticEntry.getEntryString());
            closeEmptyTag();
        }
    }

    private String getTagName(TagNameInfo tagNameInfo) {
        switch (tagNameInfo.getType()) {
            case TagNameInfo.CUSTOM_TYPE:
                return tagNameInfo.getCustomTagName();
            case TagNameInfo.NULL_TYPE:
                return null;
            default:
                return "fiche";
        }
    }


    private static class FilterUnitXMLPart extends XMLPart {

        private final ExtractParameters extractParameters;
        private final ExtractionContext extractionContext;
        private final int extractVersion;
        private final FicheItemXMLPart ficheItemXMLPart;
        private final FicheBlockXMLPart ficheBlockXMLPart;
        private final TagNames tagNames;
        private CorpusExtractXMLPart corpusExtractXMLPart = null;
        private ThesaurusExtractXMLPart thesaurusExtractXMLPart = null;
        private AlbumExtractXMLPart albumExtractXMLPart = null;
        private AddendaExtractXMLPart addendaExtractXMLPart = null;
        private FichePointeur fichePointeur;
        private IrefConverter currentIrefConverter = ExtractionUtils.SAME_CONVERTER;

        private FilterUnitXMLPart(XMLWriter xmlWriter, ExtractParameters extractParameters, TagNames tagNames) {
            super(xmlWriter);
            this.extractParameters = extractParameters;
            this.extractionContext = extractParameters.getExtractionContext();
            this.extractVersion = extractParameters.getExtractVersion();
            this.tagNames = tagNames;
            ficheItemXMLPart = new FicheItemXMLPart(xmlWriter, extractParameters);
            ficheBlockXMLPart = new FicheBlockXMLPart(xmlWriter, extractParameters);
        }

        private void updatePointeur(FichePointeur fichePointeur) {
            this.fichePointeur = fichePointeur;
            ficheBlockXMLPart.setDefaultCorpus(fichePointeur.getCorpus());
            currentIrefConverter = extractParameters.newIrefConverter();
        }

        private void addFilterUnitList(List<FilterUnit> filterUnitList) throws IOException {
            SelectionContext selectionContext = SelectionContextBuilder.build(extractionContext)
                    .setSubsetAccessPredicate(extractionContext.getSubsetAccessPredicate())
                    .toSelectionContext();
            for (FilterUnit filterUnit : filterUnitList) {
                if (filterUnit instanceof HeadFilterUnit) {
                    addEntete((HeadFilterUnit) filterUnit);
                } else if (filterUnit instanceof BodyFilterUnit) {
                    addCorpsdefiche((BodyFilterUnit) filterUnit);
                } else if (filterUnit instanceof FieldKeyFilterUnit) {
                    addFieldKey((FieldKeyFilterUnit) filterUnit);
                } else if (filterUnit instanceof FieldNamePrefixFilterUnit) {
                    addPrefixedField((FieldNamePrefixFilterUnit) filterUnit);
                } else if (filterUnit instanceof LotFilterUnit) {
                    addLot((LotFilterUnit) filterUnit);
                } else if (filterUnit instanceof ChronoFilterUnit) {
                    addChrono();
                } else if (filterUnit instanceof PhraseFilterUnit) {
                    ExtractionXMLUtils.writePhrase(this, fichePointeur.getCurrentSubsetItem(), ((PhraseFilterUnit) filterUnit).getName(), extractionContext.getLangContext(), extractVersion);
                } else if (filterUnit instanceof AddendaExtractFilterUnit) {
                    addAddendaExtract((AddendaExtractFilterUnit) filterUnit, selectionContext);
                } else if (filterUnit instanceof CorpusExtractFilterUnit) {
                    addCorpusExtract((CorpusExtractFilterUnit) filterUnit);
                } else if (filterUnit instanceof ThesaurusExtractFilterUnit) {
                    addThesaurusExtract((ThesaurusExtractFilterUnit) filterUnit, selectionContext);
                } else if (filterUnit instanceof AlbumExtractFilterUnit) {
                    addAlbumExtract((AlbumExtractFilterUnit) filterUnit, selectionContext);
                } else if (filterUnit instanceof FicheParentageFilterUnit) {
                    addFicheParentage((FicheParentageFilterUnit) filterUnit);
                } else if (filterUnit instanceof MasterMotcleFilterUnit) {
                    addMasterMotcle((MasterMotcleFilterUnit) filterUnit);
                } else if (filterUnit instanceof DataFilterUnit) {
                    addData((DataFilterUnit) filterUnit);
                }
            }
        }

        private void addData(DataFilterUnit dataFilterUnit) throws IOException {
            DataResolver dataResolver = extractionContext.getDataResolverProvider().getDataResolver(fichePointeur.getCorpus().getSubsetKey(), dataFilterUnit.getName());
            DataResolver.Writer resolverWriter = dataResolver.getWriter(fichePointeur, extractionContext.getLangContext());
            if ((resolverWriter.isEmpty()) && (ignoreEmpty(dataFilterUnit))) {
                return;
            }
            startOpenTag("data");
            addAttribute("name", dataFilterUnit.getName());
            addAttribute("type", dataResolver.getType());
            endOpenTag();
            resolverWriter.write(this, extractVersion);
            closeTag("data");
        }

        private void addLot(LotFilterUnit lotFilterUnit) throws IOException {
            LotXMLPart lotXMLPart = new LotXMLPart(this, lotFilterUnit);
            FilterUnitXMLPart child = new FilterUnitXMLPart(lotXMLPart, extractParameters, tagNames);
            child.updatePointeur(fichePointeur);
            child.addFilterUnitList(lotFilterUnit.getFilterUnitList());
            lotXMLPart.end();
        }

        private void addEntete(HeadFilterUnit filterUnit) throws IOException {
            CorpusMetadata corpusMetadata = getCorpusMetatdata();
            addTitle(null);
            addSubtitle(null);
            for (CorpusField corpusField : corpusMetadata.getPropList()) {
                addProp(corpusField, null, filterUnit);
            }
            for (CorpusField corpusField : corpusMetadata.getInfoList()) {
                addInfo(corpusField, null, filterUnit);
            }
            addRedacteurs(null);
        }

        private Object getFieldOptionObject(CorpusField corpusField, FilterUnit filterUnit) {
            switch (corpusField.getFicheItemType()) {
                case CorpusField.IMAGE_FIELD:
                case CorpusField.LINK_FIELD:
                    return corpusField.getStringOption(FieldOptionConstants.BASEURL_OPTION);
                case CorpusField.DATE_FIELD: {
                    String labelType = filterUnit.getFirstValue(ExtractionConstants.LABELTYPE_PARAM);
                    if (labelType != null) {
                        try {
                            return typeToShort(labelType);
                        } catch (IllegalArgumentException iae) {
                        }
                    }
                    return null;
                }
                case CorpusField.AMOUNT_FIELD: {
                    return filterUnit.getFirstValue(ExtractionConstants.SUBUNIT_PARAM);
                }
                default:
                    return null;
            }
        }

        private void addCorpsdefiche(BodyFilterUnit filterUnit) throws IOException {
            CorpusMetadata corpusMetadata = getCorpusMetatdata();
            for (CorpusField corpusField : corpusMetadata.getSectionList()) {
                addSection(corpusField.getFieldKey(), null, filterUnit);
            }
        }

        private void addFieldKey(FieldKeyFilterUnit fieldKeyFilterUnit) throws IOException {
            FieldKey fieldKey = fieldKeyFilterUnit.getFieldKey();
            CorpusMetadata corpusMetadata = getCorpusMetatdata();
            CorpusField corpusField = corpusMetadata.getCorpusField(fieldKey);
            if (corpusField == null) {
                return;
            }
            switch (fieldKey.getCategory()) {
                case FieldKey.PROP_CATEGORY:
                    addProp(corpusField, null, fieldKeyFilterUnit);
                    break;
                case FieldKey.INFO_CATEGORY:
                    addInfo(corpusField, null, fieldKeyFilterUnit);
                    break;
                case FieldKey.SECTION_CATEGORY:
                    addSection(fieldKey, null, fieldKeyFilterUnit);
                    break;
                case FieldKey.SPECIAL_CATEGORY:
                    switch (fieldKey.getKeyString()) {
                        case FieldKey.SPECIAL_TITLE:
                            addTitle(fieldKeyFilterUnit);
                            break;
                        case FieldKey.SPECIAL_SUBTITLE:
                            addSubtitle(fieldKeyFilterUnit);
                            break;
                        case FieldKey.SPECIAL_LANG:
                            addLang(fieldKeyFilterUnit);
                            break;
                        case FieldKey.SPECIAL_OWNERS:
                            addRedacteurs(fieldKeyFilterUnit);
                            break;
                        case FieldKey.SPECIAL_ID:
                            break;
                        default:
                            throw new SwitchException("Unknown special field = " + fieldKey.getKeyString());
                    }
                    break;
                default:
                    throw new SwitchException("Unknown category = " + fieldKey.getCategory());
            }
        }

        private void addPrefixedField(FieldNamePrefixFilterUnit filterUnit) throws IOException {
            String prefix = filterUnit.getPrefix();
            switch (filterUnit.getCategory()) {
                case FieldKey.PROP_CATEGORY:
                    addPrefixedProp(prefix, filterUnit);
                    break;
                case FieldKey.INFO_CATEGORY:
                    addPrefixedInfo(prefix, filterUnit);
                    break;
                case FieldKey.SECTION_CATEGORY:
                    addPrefixedSection(prefix, filterUnit);
                    break;
            }
        }

        private void addPrefixedProp(String prefix, FilterUnit filterUnit) throws IOException {
            CorpusMetadata corpusMetadata = getCorpusMetatdata();
            for (CorpusField corpusField : corpusMetadata.getPropList()) {
                FieldNameInfo fieldNameInfo = FieldNameInfo.testPrefix(corpusField.getFieldKey(), prefix);
                if (fieldNameInfo != null) {
                    addProp(corpusField, fieldNameInfo, filterUnit);
                }
            }
        }

        private void addPrefixedInfo(String prefix, FilterUnit filterUnit) throws IOException {
            CorpusMetadata corpusMetadata = getCorpusMetatdata();
            for (CorpusField corpusField : corpusMetadata.getInfoList()) {
                FieldNameInfo fieldNameInfo = FieldNameInfo.testPrefix(corpusField.getFieldKey(), prefix);
                if (fieldNameInfo != null) {
                    addInfo(corpusField, fieldNameInfo, filterUnit);
                }
            }
        }

        private void addPrefixedSection(String prefix, FilterUnit filterUnit) throws IOException {
            CorpusMetadata corpusMetadata = getCorpusMetatdata();
            for (CorpusField corpusField : corpusMetadata.getSectionList()) {
                FieldKey fieldKey = corpusField.getFieldKey();
                FieldNameInfo fieldNameInfo = FieldNameInfo.testPrefix(fieldKey, prefix);
                if (fieldNameInfo != null) {
                    addSection(fieldKey, fieldNameInfo, filterUnit);
                }
            }
        }

        private void addTitle(FilterUnit filterUnit) throws IOException {
            String title = (String) fichePointeur.getValue(FieldKey.TITLE);
            if (title == null) {
                return;
            }
            String titleTagName = tagNames.getTitleTagName();
            startOpenTag(titleTagName);
            ExtractionXMLUtils.addFilterParameters(this, filterUnit);
            endOpenTag();
            addSimpleElement("item", title);
            closeTag(titleTagName);
        }

        private void addSubtitle(FilterUnit filterUnit) throws IOException {
            ParaItem para = (ParaItem) fichePointeur.getValue(FieldKey.SUBTITLE);
            if (para == null) {
                return;
            }
            String subtitleTagName = tagNames.getSubtitleTagName();
            startOpenTag(subtitleTagName);
            ExtractionXMLUtils.addFilterParameters(this, filterUnit);
            endOpenTag();
            ficheItemXMLPart.addPara(para, currentIrefConverter);
            closeTag(subtitleTagName);
        }

        private void addRedacteurs(FilterUnit filterUnit) throws IOException {
            FicheItems ficheItems = (FicheItems) fichePointeur.getValue(FieldKey.OWNERS);
            if (ficheItems == null) {
                return;
            }
            String ownersTagName = tagNames.getOwnersTagName();
            startOpenTag(ownersTagName);
            ExtractionXMLUtils.addFilterParameters(this, filterUnit);
            endOpenTag();
            ficheItemXMLPart.addFicheItems(ficheItems, null, currentIrefConverter);
            closeTag(ownersTagName);
        }

        private void addProp(CorpusField corpusField, FieldNameInfo fieldNameInfo, FilterUnit filterUnit) throws IOException {
            String propTagName = tagNames.getPropTagName();
            FieldKey fieldKey = corpusField.getFieldKey();
            Object fieldOptionObject = getFieldOptionObject(corpusField, filterUnit);
            FicheItem ficheItem = (FicheItem) fichePointeur.getValue(fieldKey);
            if ((ficheItem == null) && (ignoreEmpty(filterUnit))) {
                return;
            }
            startOpenTag(propTagName);
            addAttribute("name", fieldKey.getFieldName());
            if (extractVersion == ExtractionConstants.INITIAL_VERSION) {
                addAttribute("type", fieldKey.getFieldName());
            }
            if (fieldNameInfo != null) {
                addAttribute("part1", fieldNameInfo.getPart1());
                addAttribute("part2", fieldNameInfo.getPart2());
            }
            ExtractionXMLUtils.addFilterParameters(this, filterUnit);
            endOpenTag();
            if (ficheItem != null) {
                ficheItemXMLPart.addFicheItem(ficheItem, fieldOptionObject, currentIrefConverter);
            }
            closeTag(propTagName);
        }


        private void addInfo(CorpusField corpusField, FieldNameInfo fieldNameInfo, FilterUnit filterUnit) throws IOException {
            FieldKey fieldKey = corpusField.getFieldKey();
            Object fieldOptionObject = getFieldOptionObject(corpusField, filterUnit);
            FicheItems ficheItems = (FicheItems) fichePointeur.getValue(fieldKey);
            if ((ficheItems == null) && (ignoreEmpty(filterUnit))) {
                return;
            }
            String infoTagName = tagNames.getInfoTagName();
            startOpenTag(infoTagName);
            addAttribute("name", fieldKey.getFieldName());
            if (extractVersion == ExtractionConstants.INITIAL_VERSION) {
                addAttribute("type", fieldKey.getFieldName());
            }
            if (fieldNameInfo != null) {
                addAttribute("part1", fieldNameInfo.getPart1());
                addAttribute("part2", fieldNameInfo.getPart2());
            }
            ExtractionXMLUtils.addFilterParameters(this, filterUnit);
            endOpenTag();
            if (ficheItems != null) {
                ficheItemXMLPart.addFicheItems(ficheItems, fieldOptionObject, currentIrefConverter);
            }
            closeTag(infoTagName);
        }

        private void addSection(FieldKey fieldKey, FieldNameInfo fieldNameInfo, FilterUnit filterUnit) throws IOException {
            FicheBlocks blockList = (FicheBlocks) fichePointeur.getValue(fieldKey);
            if ((blockList == null) && (ignoreEmpty(filterUnit))) {
                return;
            }
            startOpenTag("section");
            addAttribute("name", fieldKey.getFieldName());
            if (extractVersion == ExtractionConstants.INITIAL_VERSION) {
                addAttribute("type", fieldKey.getFieldName());
            }
            if (fieldNameInfo != null) {
                addAttribute("part1", fieldNameInfo.getPart1());
                addAttribute("part2", fieldNameInfo.getPart2());
            }
            ExtractionXMLUtils.addFilterParameters(this, filterUnit);
            endOpenTag();
            if (blockList != null) {
                ficheBlockXMLPart.addFicheBlocks(blockList, currentIrefConverter);
            }
            closeTag("section");
        }

        private void addChrono() throws IOException {
            FicheMeta ficheMeta = (FicheMeta) fichePointeur.getCurrentSubsetItem();
            FuzzyDate creationDate = ficheMeta.getCreationDate();
            if (creationDate != null) {
                openTag("chrono");
                ficheItemXMLPart.addDate(new DateItem(creationDate), null);
                FuzzyDate modificationDate = ficheMeta.getModificationDate();
                if (!modificationDate.equals(creationDate)) {
                    ficheItemXMLPart.addDate(new DateItem(modificationDate), null);
                }
                closeTag("chrono");
            }
        }

        private void addLang(FilterUnit filterUnit) throws IOException {
            Lang lang = ((FicheMeta) fichePointeur.getCurrentSubsetItem()).getLang();
            if ((lang == null) && (ignoreEmpty(filterUnit))) {
                return;
            }
            openTag("lang");
            if (lang != null) {
                ficheItemXMLPart.addLanguage(new LanguageItem(lang));
            }
            closeTag("lang");
        }


        private void addFicheParentage(FicheParentageFilterUnit filterUnit) throws IOException {
            Corpus currentCorpus = fichePointeur.getCorpus();
            List<Corpus> corpusList = toCorpusList(currentCorpus, filterUnit.getSubsetKeyList());
            if (corpusList.isEmpty()) {
                return;
            }
            int id = fichePointeur.getCurrentSubsetItem().getId();
            FicheXMLPart ficheXMLPart = new FicheXMLPart(getXMLWriter(), extractParameters);
            FicheFilter ficheFilter = filterUnit.getFicheFilter();
            Predicate<FicheMeta> fichePredicate = extractParameters.getFichePredicate();
            if (fichePredicate == null) {
                fichePredicate = EligibilityUtils.ALL_FICHE_PREDICATE;
            }
            for (Corpus otherCorpus : corpusList) {
                if (!extractionContext.getSubsetAccessPredicate().test(otherCorpus)) {
                    continue;
                }
                FicheMeta ficheMeta = otherCorpus.getFicheMetaById(id);
                if ((ficheMeta != null) && (fichePredicate.test(ficheMeta))) {
                    FicheExtractInfo ficheExtractInfo = ExtractionXMLUtils.toFicheExtractInfo(ficheMeta, ficheFilter);
                    ficheXMLPart.addFiche(ficheExtractInfo);
                }
            }
        }

        private void addMasterMotcle(MasterMotcleFilterUnit filterUnit) throws IOException {
            Corpus currentCorpus = fichePointeur.getCorpus();
            Subset masterSubset = currentCorpus.getMasterSubset();
            if ((masterSubset == null) || (!(masterSubset instanceof Thesaurus))) {
                return;
            }
            if (!extractionContext.getSubsetAccessPredicate().test(masterSubset)) {
                return;
            }
            Motcle masterMotcle = (Motcle) masterSubset.getSubsetItemById(fichePointeur.getCurrentSubsetItem().getId());
            MotcleXMLPart motcleXMLPart = new MotcleXMLPart(getXMLWriter(), extractParameters);
            motcleXMLPart.addMotcle(ExtractionXMLUtils.toMotcleExtractInfo(masterMotcle, filterUnit.getMotcleFilter()));
        }

        private void addCorpusExtract(CorpusExtractFilterUnit corpusExtractFilterUnit) throws IOException {
            CorpusExtractDef corpusExtractDef = corpusExtractFilterUnit.getCorpusExtractDef();
            CorpusExtractor corpusExtractor = extractionContext.getExtractorProvider().getCorpusExtractor(corpusExtractDef);
            SelectionContext selectionContext = SelectionContextBuilder.build(extractionContext)
                    .setSubsetAccessPredicate(extractionContext.getSubsetAccessPredicate())
                    .setFichePredicate(extractParameters.getFichePredicate())
                    .setCurrentCorpus(fichePointeur.getCorpus())
                    .toSelectionContext();
            FicheSelector ficheSelector = FicheSelectorBuilder.init(selectionContext)
                    .addAll(corpusExtractDef.getConditionEntryList())
                    .toFicheSelector();
            SubsetItemPointeur pointeur = PointeurUtils.checkMasterPointeur(fichePointeur, corpusExtractDef.isMaster());
            for (Corpus other : ficheSelector.getCorpusList()) {
                Croisements croisements = pointeur.getCroisements(other);
                for (Croisements.Entry entry : croisements.getEntryList()) {
                    FicheMeta ficheMeta2 = (FicheMeta) entry.getSubsetItem();
                    Croisement filteredCroisement = ficheSelector.isSelected(ficheMeta2, entry.getCroisement());
                    if (filteredCroisement != null) {
                        corpusExtractor.add(ficheMeta2, filteredCroisement);
                    }
                }
            }
            if (corpusExtractXMLPart == null) {
                corpusExtractXMLPart = new CorpusExtractXMLPart(getXMLWriter(), extractParameters);
            }
            corpusExtractXMLPart.addCorpusExtract(corpusExtractor.getCorpusExtractResult(), corpusExtractFilterUnit);
        }

        private void addThesaurusExtract(ThesaurusExtractFilterUnit thesaurusExtractFilterUnit, SelectionContext selectionContext) throws IOException {
            ThesaurusExtractDef thesaurusExtractDef = thesaurusExtractFilterUnit.getThesaurusExtractDef();
            ThesaurusExtractor thesaurusExtractor = extractionContext.getExtractorProvider().getThesaurusExtractor(thesaurusExtractDef);
            MotcleSelector motcleSelector = MotcleSelectorBuilder.init(selectionContext).addAll(thesaurusExtractDef.getConditionEntryList()).toMotcleSelector();
            SubsetItemPointeur pointeur = PointeurUtils.checkMasterPointeur(fichePointeur, thesaurusExtractDef.isMaster());
            for (Thesaurus thesaurus : motcleSelector.getThesaurusList()) {
                Croisements croisements = pointeur.getCroisements(thesaurus);
                for (Croisements.Entry entry : croisements.getEntryList()) {
                    Motcle motcle = (Motcle) entry.getSubsetItem();
                    Croisement filteredCroisement = motcleSelector.isSelected(motcle, entry.getCroisement());
                    if (filteredCroisement != null) {
                        thesaurusExtractor.add(motcle, filteredCroisement);
                    }
                }
            }
            if (thesaurusExtractXMLPart == null) {
                thesaurusExtractXMLPart = new ThesaurusExtractXMLPart(getXMLWriter(), extractParameters);
            }
            thesaurusExtractXMLPart.addThesaurusExtract(thesaurusExtractor.getThesaurusExtractResult(), thesaurusExtractFilterUnit);
        }

        private void addAlbumExtract(AlbumExtractFilterUnit albumExtractFilterUnit, SelectionContext selectionContext) throws IOException {
            AlbumExtractDef albumExtractDef = albumExtractFilterUnit.getAlbumExtractDef();
            AlbumExtractor albumExtractor = extractionContext.getExtractorProvider().getAlbumExtractor(albumExtractDef);
            IllustrationSelector illustrationSelector = IllustrationSelectorBuilder.init(selectionContext).addAll(albumExtractDef.getConditionEntryList()).toIllustrationSelector();
            for (Album album : illustrationSelector.getAlbumList()) {
                Croisements croisements = fichePointeur.getCroisements(album);
                for (Croisements.Entry entry : croisements.getEntryList()) {
                    Illustration illustration = (Illustration) entry.getSubsetItem();
                    Croisement filteredCroisement = illustrationSelector.isSelected(illustration, entry.getCroisement());
                    if (filteredCroisement != null) {
                        albumExtractor.add(illustration, filteredCroisement);
                    }
                }
            }
            if (albumExtractXMLPart == null) {
                albumExtractXMLPart = new AlbumExtractXMLPart(getXMLWriter(), extractParameters);
            }
            albumExtractXMLPart.addAlbumExtract(albumExtractor.getAlbumExtractResult(), albumExtractFilterUnit);
        }

        private void addAddendaExtract(AddendaExtractFilterUnit addendaExtractFilterUnit, SelectionContext selectionContext) throws IOException {
            AddendaExtractDef addendaExtractDef = addendaExtractFilterUnit.getAddendaExtractDef();
            AddendaExtractor addendaExtractor = extractionContext.getExtractorProvider().getAddendaExtractor(addendaExtractDef);
            DocumentSelector documentSelector = DocumentSelectorBuilder.init(selectionContext).addAll(addendaExtractDef.getConditionEntryList()).toDocumentSelector();
            for (Addenda addenda : documentSelector.getAddendaList()) {
                Croisements croisements = fichePointeur.getCroisements(addenda);
                for (Croisements.Entry entry : croisements.getEntryList()) {
                    Document document = (Document) entry.getSubsetItem();
                    Croisement filteredCroisement = documentSelector.isSelected(document, entry.getCroisement());
                    if (filteredCroisement != null) {
                        addendaExtractor.add(document, filteredCroisement);
                    }
                }
            }
            if (addendaExtractXMLPart == null) {
                addendaExtractXMLPart = new AddendaExtractXMLPart(getXMLWriter(), extractParameters);
            }
            addendaExtractXMLPart.addAddendaExtract(addendaExtractor.getAddendaExtractResult(), addendaExtractFilterUnit);
        }

        private CorpusMetadata getCorpusMetatdata() {
            return fichePointeur.getCorpus().getCorpusMetadata();
        }

        private boolean ignoreEmpty(FilterUnit filterUnit) {
            if (extractVersion == ExtractionConstants.INITIAL_VERSION) {
                return true;
            }
            if (extractParameters.hideIfEmpty(filterUnit)) {
                return true;
            }
            return false;
        }

    }


    private static class LotXMLPart extends FillControllerXMLPart {

        private final String tagName;
        private final XMLWriter xmlWriter;
        private boolean openDone = false;

        private LotXMLPart(XMLWriter xmlWriter, LotFilterUnit lotFilterUnit) {
            super(xmlWriter);
            tagName = getTagName(lotFilterUnit.getTagNameInfo());
            this.xmlWriter = xmlWriter;
        }

        @Override
        public void controlFill() throws IOException {
            if (tagName != null) {
                xmlWriter.openTag(tagName);
                openDone = true;
            }
        }

        private void end() throws IOException {
            if (tagName != null) {
                if (openDone) {
                    xmlWriter.closeTag(tagName);
                }
            }
        }

        private String getTagName(TagNameInfo tagNameInfo) {
            switch (tagNameInfo.getType()) {
                case TagNameInfo.CUSTOM_TYPE:
                    return tagNameInfo.getCustomTagName();
                case TagNameInfo.NULL_TYPE:
                    return null;
                default:
                    return "lot";
            }
        }

    }

    private static List<Corpus> toCorpusList(Corpus mainCorpus, List<SubsetKey> subsetKeyList) {
        if (subsetKeyList.isEmpty()) {
            return FichothequeUtils.getParentageCorpusList(mainCorpus);
        }
        Fichotheque fichotheque = mainCorpus.getFichotheque();
        Subset mainMasterSubset = mainCorpus.getMasterSubset();
        List<Corpus> list = new ArrayList<Corpus>();
        for (SubsetKey subsetKey : subsetKeyList) {
            Corpus corpus = (Corpus) fichotheque.getSubset(subsetKey);
            if ((corpus != null) && (!corpus.equals(mainCorpus))) {
                Subset masterSubset = corpus.getMasterSubset();
                if (mainMasterSubset == null) {
                    if ((masterSubset != null) && (masterSubset.equals(mainCorpus))) {
                        list.add(corpus);
                    }
                } else {
                    if ((corpus.equals(mainMasterSubset)) || ((masterSubset != null) && (masterSubset.equals(mainMasterSubset)))) {
                        list.add(corpus);
                    }
                }
            }
        }
        return list;
    }

    private static short typeToShort(String typeString) {
        switch (typeString) {
            case "day":
            case "jour":
                return FuzzyDate.DAY_TYPE;
            case "month":
            case "mois":
                return FuzzyDate.MONTH_TYPE;
            case "quarter":
            case "trimestre":
                return FuzzyDate.QUARTER_TYPE;
            case "halfyear":
            case "semestre":
                return FuzzyDate.HALFYEAR_TYPE;
            case "year":
            case "annee":
                return FuzzyDate.YEAR_TYPE;
            default:
                throw new IllegalArgumentException("unknown type = " + typeString);
        }
    }

}
