/* BdfServer - Copyright (c) 2008-2025 Vincent Calame - Exemole
 * Logiciel libre donné sous triple licence :
 * 1) selon les termes de la CeCILL V2
 * 2) selon les termes de l’EUPL V.1.1
 * 3) selon les termes de la GNU GPLv3
 * Voir le fichier licences.txt
 */


package fr.exemole.bdfserver.tools;

import fr.exemole.bdfserver.api.BdfExtensionReference;
import fr.exemole.bdfserver.api.BdfServer;
import fr.exemole.bdfserver.api.BdfServerEditor;
import fr.exemole.bdfserver.api.configuration.PathConfiguration;
import fr.exemole.bdfserver.api.instruction.BdfCommand;
import fr.exemole.bdfserver.api.instruction.BdfCommandParameters;
import fr.exemole.bdfserver.api.instruction.BdfParameters;
import fr.exemole.bdfserver.api.instruction.OutputParameters;
import fr.exemole.bdfserver.api.managers.ExtensionManager;
import fr.exemole.bdfserver.api.menu.ActionGroup;
import fr.exemole.bdfserver.api.menu.MenuGroup;
import fr.exemole.bdfserver.api.namespaces.FicheFormSpace;
import fr.exemole.bdfserver.api.providers.ActionProvider;
import fr.exemole.bdfserver.api.providers.BdfCommandProvider;
import fr.exemole.bdfserver.api.providers.HookHandlerProvider;
import fr.exemole.bdfserver.api.providers.HtmlProducerProvider;
import fr.exemole.bdfserver.api.providers.JsonProducerProvider;
import fr.exemole.bdfserver.api.providers.MenuLinkProvider;
import fr.exemole.bdfserver.api.providers.StreamProducerProvider;
import fr.exemole.bdfserver.api.roles.SatelliteOpportunities;
import fr.exemole.bdfserver.api.storage.BdfStorageException;
import fr.exemole.bdfserver.api.ui.DataUi;
import fr.exemole.bdfserver.api.users.BdfUser;
import fr.exemole.bdfserver.tools.configuration.PathConfigurationBuilder;
import fr.exemole.bdfserver.tools.dataresolvers.ErrorDataResolver;
import fr.exemole.bdfserver.tools.dataresolvers.FichesDataResolver;
import fr.exemole.bdfserver.tools.dataresolvers.ResolverErrorException;
import fr.exemole.bdfserver.tools.externalsource.CoreExternalSourceCatalog;
import java.io.IOException;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.RandomAccess;
import java.util.Set;
import java.util.function.Predicate;
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.FicheBlocks;
import net.fichotheque.corpus.fiche.HtmlConverter;
import net.fichotheque.exportation.table.TableExportDef;
import net.fichotheque.exportation.table.TableExportDescription;
import net.fichotheque.externalsource.ExternalSourceDef;
import net.fichotheque.extraction.DataKey;
import net.fichotheque.extraction.DataResolver;
import net.fichotheque.extraction.DataResolverProvider;
import net.fichotheque.extraction.ExtensionDataResolverProvider;
import net.fichotheque.extraction.ExtractionContext;
import net.fichotheque.permission.PermissionSummary;
import net.fichotheque.selection.FicheQuery;
import net.fichotheque.selection.FichothequeQueries;
import net.fichotheque.selection.MotcleQuery;
import net.fichotheque.selection.SelectionContext;
import net.fichotheque.selection.SelectionDef;
import net.fichotheque.selection.SelectionOptions;
import net.fichotheque.thesaurus.Motcle;
import net.fichotheque.thesaurus.Thesaurus;
import net.fichotheque.tools.extraction.ExtractionContextUtils;
import net.fichotheque.tools.extraction.builders.ExtractionContextBuilder;
import net.fichotheque.tools.from.html.ConversionEngine;
import net.fichotheque.tools.permission.PermissionUtils;
import net.fichotheque.tools.selection.FichothequeQueriesBuilder;
import net.fichotheque.utils.EligibilityUtils;
import net.fichotheque.utils.ExtractionUtils;
import net.fichotheque.utils.FichothequeUtils;
import net.fichotheque.utils.SelectionUtils;
import net.fichotheque.utils.ThesaurusUtils;
import net.fichotheque.utils.selection.FicheSelectorBuilder;
import net.fichotheque.utils.selection.MotcleSelectorBuilder;
import net.fichotheque.utils.selection.SelectionContextBuilder;
import net.mapeadores.opendocument.css.parse.CssErrorHandler;
import net.mapeadores.opendocument.css.parse.CssParser;
import net.mapeadores.opendocument.css.parse.CssSource;
import net.mapeadores.opendocument.css.parse.LogCssErrorHandler;
import net.mapeadores.opendocument.elements.ElementMaps;
import net.mapeadores.opendocument.io.odtable.OdsOptions;
import net.mapeadores.util.annotation.Nullable;
import net.mapeadores.util.attr.Attribute;
import net.mapeadores.util.attr.AttributeChange;
import net.mapeadores.util.attr.AttributeKey;
import net.mapeadores.util.hook.HookHandler;
import net.mapeadores.util.hook.MultiHookHandlerBuilder;
import net.mapeadores.util.html.HtmlCleaner;
import net.mapeadores.util.html.HtmlProducer;
import net.mapeadores.util.html.jsoup.HtmlCleaners;
import net.mapeadores.util.io.ResourceStorages;
import net.mapeadores.util.io.StreamProducer;
import net.mapeadores.util.json.JsonProducer;
import net.mapeadores.util.localisation.Lang;
import net.mapeadores.util.localisation.LangContext;
import net.mapeadores.util.localisation.Langs;
import net.mapeadores.util.localisation.ListLangContextBuilder;
import net.mapeadores.util.localisation.LocalisationUtils;
import net.mapeadores.util.logging.ErrorMessageException;
import net.mapeadores.util.text.FileName;
import net.mapeadores.util.text.Label;
import net.mapeadores.util.text.LabelChange;
import net.mapeadores.util.text.RelativePath;


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

    public final static HtmlCleaner DEFAULT_HTMLCLEANER = HtmlCleaners.WELLFORMED;
    public final static HtmlConverter DEFAULT_HTMLCONVERTER = new DefaultHtmlConverter();
    private final static SatelliteOpportunities EMPTY_SATELLITEOPPORTUNITIES = new EmptySatelliteOpportunities();


    private BdfServerUtils() {
    }

    public static Lang checkLangDisponibility(BdfServer bdfServer, Thesaurus thesaurus, Lang lang) {
        return ThesaurusUtils.checkDisponibility(bdfServer.getThesaurusLangChecker(), thesaurus, lang);
    }

    public static String getMillier(int id) {
        if (id < 1000) {
            return "000";
        } else {
            StringBuilder buf = new StringBuilder();
            if (id < 10000) {
                buf.append("00");
            } else if (id < 100000) {
                buf.append("0");
            }
            buf.append(id / 1000);
            return buf.toString();
        }
    }

    public static boolean changeLabels(BdfServerEditor bdfServerEditor, Object labelHolder, LabelChange labelChange) {
        boolean done = false;
        for (Label label : labelChange.getChangedLabels()) {
            boolean stepdone = bdfServerEditor.putLabel(labelHolder, label);
            if (stepdone) {
                done = true;
            }
        }
        for (Lang lang : labelChange.getRemovedLangList()) {
            boolean stepdone = bdfServerEditor.removeLabel(labelHolder, lang);
            if (stepdone) {
                done = true;
            }
        }
        return done;
    }

    public static boolean changeAttributes(BdfServerEditor bdfServerEditor, Object attributesHolder, AttributeChange attributeChange) {
        boolean done = false;
        for (Attribute attribute : attributeChange.getChangedAttributes()) {
            boolean stepdone = bdfServerEditor.putAttribute(attributesHolder, attribute);
            if (stepdone) {
                done = true;
            }
        }
        for (AttributeKey removedAttributeKey : attributeChange.getRemovedAttributeKeyList()) {
            boolean stepdone = bdfServerEditor.removeAttribute(attributesHolder, removedAttributeKey);
            if (stepdone) {
                done = true;
            }
        }
        return done;
    }

    public static SatelliteOpportunities getSatelliteOpportunities(SubsetItem masterSubsetItem, PermissionSummary permissionSummary) {
        Subset masterSubset = masterSubsetItem.getSubset();
        List<Corpus> satelliteCorpusList = masterSubset.getSatelliteCorpusList();
        if (satelliteCorpusList.isEmpty()) {
            return EMPTY_SATELLITEOPPORTUNITIES;
        }
        int masterId = masterSubsetItem.getId();
        List<InternalOpportunity> opportunityList = new ArrayList<InternalOpportunity>();
        boolean onlyReadAllowed = true;
        for (Corpus satelliteCorpus : satelliteCorpusList) {
            FicheMeta otherFicheMeta = satelliteCorpus.getFicheMetaById(masterId);
            if (otherFicheMeta == null) {
                if (permissionSummary.canCreate(satelliteCorpus)) {
                    onlyReadAllowed = false;
                    opportunityList.add(new InternalOpportunity(satelliteCorpus, SatelliteOpportunities.CREATE));
                }
            } else {
                if (permissionSummary.canWrite(otherFicheMeta)) {
                    opportunityList.add(new InternalOpportunity(otherFicheMeta, SatelliteOpportunities.WRITE));
                    onlyReadAllowed = false;
                } else if (permissionSummary.canRead(otherFicheMeta)) {
                    opportunityList.add(new InternalOpportunity(otherFicheMeta, SatelliteOpportunities.READ));
                }
            }
        }
        SatelliteOpportunitiesEntryList entryList = new SatelliteOpportunitiesEntryList(opportunityList.toArray(new SatelliteOpportunities.Entry[opportunityList.size()]));
        return new InternalSatelliteOpportunities(entryList, onlyReadAllowed);
    }

    public static SatelliteOpportunities getSatelliteOpportunities(SubsetItem masterSubsetItem, PermissionSummary permissionSummary, Corpus[] corpusArray) {
        int masterId = masterSubsetItem.getId();
        List<InternalOpportunity> opportunityList = new ArrayList<InternalOpportunity>();
        boolean onlyReadAllowed = true;
        for (Corpus satelliteCorpus : corpusArray) {
            FicheMeta otherFicheMeta = satelliteCorpus.getFicheMetaById(masterId);
            if (otherFicheMeta == null) {
                if (permissionSummary.canCreate(satelliteCorpus)) {
                    onlyReadAllowed = false;
                    opportunityList.add(new InternalOpportunity(satelliteCorpus, SatelliteOpportunities.CREATE));
                }
            } else {
                if (permissionSummary.canWrite(otherFicheMeta)) {
                    opportunityList.add(new InternalOpportunity(otherFicheMeta, SatelliteOpportunities.WRITE));
                    onlyReadAllowed = false;
                } else if (permissionSummary.canRead(otherFicheMeta)) {
                    opportunityList.add(new InternalOpportunity(otherFicheMeta, SatelliteOpportunities.READ));
                }
            }
        }
        SatelliteOpportunitiesEntryList entryList = new SatelliteOpportunitiesEntryList(opportunityList.toArray(new SatelliteOpportunities.Entry[opportunityList.size()]));
        return new InternalSatelliteOpportunities(entryList, onlyReadAllowed);
    }

    public static OdsOptions buildOdsOptions(BdfServer bdfServer, String absoluteUri) {
        LogCssErrorHandler logCssErrorHandler = new LogCssErrorHandler();
        PathConfiguration pathConfiguration = PathConfigurationBuilder.build(bdfServer);
        ElementMaps elementMaps = BdfServerUtils.parseCss(bdfServer, pathConfiguration, absoluteUri, logCssErrorHandler);
        return OdsOptions.init()
                .elementMaps(elementMaps)
                .odLog(logCssErrorHandler);
    }

    public static OdsOptions buildOdsOptions(ResourceStorages resourceStorages, RelativePath relativePath) {
        LogCssErrorHandler logCssErrorHandler = new LogCssErrorHandler();
        ElementMaps elementMaps = BdfServerUtils.parseCss(resourceStorages, relativePath, logCssErrorHandler);
        return OdsOptions.init()
                .elementMaps(elementMaps)
                .odLog(logCssErrorHandler);
    }

    public static Predicate<SubsetItem> toPredicate(BdfParameters bdfParameters, FichothequeQueries fichothequeQueries) {
        return toPredicate(bdfParameters.getBdfServer(), bdfParameters.getPermissionSummary(), bdfParameters.getWorkingLang(), fichothequeQueries);
    }

    public static Predicate<SubsetItem> toPredicate(BdfServer bdfServer, PermissionSummary permissionSummary, Lang lang, FichothequeQueries fichothequeQueries) {
        List<FicheQuery> ficheQueryList = fichothequeQueries.getFicheQueryList();
        Predicate<FicheMeta> fichePredicate = null;
        if (!ficheQueryList.isEmpty()) {
            SelectionContext selectionContext = BdfServerUtils.initSelectionContextBuilder(bdfServer, lang)
                    .setSubsetAccessPredicate(permissionSummary.getSubsetAccessPredicate())
                    .toSelectionContext();
            FicheSelectorBuilder ficheSelectorBuilder = FicheSelectorBuilder.init(selectionContext);
            for (FicheQuery ficheQuery : ficheQueryList) {
                ficheSelectorBuilder.add(ficheQuery);
            }
            fichePredicate = SelectionUtils.toPredicate(ficheSelectorBuilder.toFicheSelector());
        }
        List<MotcleQuery> motcleQueryList = fichothequeQueries.getMotcleQueryList();
        Predicate<Motcle> motclePredicate = null;
        if (!motcleQueryList.isEmpty()) {
            SelectionContext selectionContext = BdfServerUtils.initSelectionContextBuilder(bdfServer, lang)
                    .setSubsetAccessPredicate(permissionSummary.getSubsetAccessPredicate())
                    .setFichePredicate(fichePredicate)
                    .toSelectionContext();
            MotcleSelectorBuilder motcleSelectorBuilder = MotcleSelectorBuilder.init(selectionContext);
            for (MotcleQuery motcleQuery : motcleQueryList) {
                motcleSelectorBuilder.add(motcleQuery, null);
            }
            motclePredicate = SelectionUtils.toPredicate(motcleSelectorBuilder.toMotcleSelector());
        }
        return EligibilityUtils.merge(fichePredicate, motclePredicate);
    }

    public static ElementMaps parseCss(BdfServer bdfServer, PathConfiguration pathConfiguration, String absoluteUri, CssErrorHandler cssErrorHandler) {
        CssSource cssSource = BdfURI.resolveCssSource(bdfServer, pathConfiguration, absoluteUri, null);
        if (cssSource == null) {
            cssErrorHandler.unknownURIFatalError(absoluteUri);
            return null;
        }
        try {
            return CssParser.parse(cssSource, cssErrorHandler);
        } catch (IOException ioe) {
            throw new BdfStorageException(ioe);
        }
    }

    public static ElementMaps parseCss(ResourceStorages resourceStorages, RelativePath relativePath, CssErrorHandler cssErrorHandler) {
        CssSource cssSource = BdfURI.resolveCssSource(resourceStorages, relativePath);
        if (cssSource == null) {
            cssErrorHandler.unknownURIFatalError(relativePath.getPath());
            return null;
        }
        try {
            return CssParser.parse(cssSource, cssErrorHandler);
        } catch (IOException ioe) {
            throw new BdfStorageException(ioe);
        }
    }

    public static List<MenuGroup> getExtensionMenuGroupList(BdfServer bdfServer, BdfUser bdfUser, Object menuObject) {
        ExtensionManager extensionManager = bdfServer.getExtensionManager();
        List<MenuGroup> menuGroupList = new ArrayList<MenuGroup>();
        for (BdfExtensionReference extensionReference : extensionManager.getBdfExtensionReferenceList()) {
            MenuLinkProvider menuLinkProvider = (MenuLinkProvider) extensionReference.getImplementation(MenuLinkProvider.class);
            if (menuLinkProvider != null) {
                MenuGroup[] array = menuLinkProvider.getMenuGroupArray(bdfServer, bdfUser, menuObject);
                if (array != null) {
                    for (MenuGroup menuGroup : array) {
                        menuGroupList.add(menuGroup);
                    }
                }
            }
        }
        return menuGroupList;
    }

    public static List<ActionGroup> getExtensionActionGroupList(String actionKey, BdfServer bdfServer, BdfUser bdfUser) {
        ExtensionManager extensionManager = bdfServer.getExtensionManager();
        List<ActionGroup> actionGroupList = new ArrayList<ActionGroup>();
        for (BdfExtensionReference extensionReference : extensionManager.getBdfExtensionReferenceList()) {
            ActionProvider actionProvider = (ActionProvider) extensionReference.getImplementation(ActionProvider.class);
            if (actionProvider != null) {
                ActionGroup actionGroup = actionProvider.getActionGroup(actionKey, bdfServer, bdfUser);
                if (actionGroup != null) {
                    actionGroupList.add(actionGroup);
                }
            }
        }
        return actionGroupList;
    }

    public static BdfCommand getExtensionBdfCommand(BdfCommandParameters bdfCommandParameters) {
        ExtensionManager extensionManager = bdfCommandParameters.getBdfServer().getExtensionManager();
        for (BdfExtensionReference extensionReference : extensionManager.getBdfExtensionReferenceList()) {
            BdfCommandProvider bdfCommandProvider = (BdfCommandProvider) extensionReference.getImplementation(BdfCommandProvider.class);
            if (bdfCommandProvider != null) {
                BdfCommand bdfCommand = bdfCommandProvider.getBdfCommand(bdfCommandParameters);
                if (bdfCommand != null) {
                    return bdfCommand;
                }
            }
        }
        return null;
    }

    public static HtmlProducer getExtensionHtmlProducer(OutputParameters outputParameters) throws ErrorMessageException {
        ExtensionManager extensionManager = outputParameters.getBdfServer().getExtensionManager();
        for (BdfExtensionReference extensionReference : extensionManager.getBdfExtensionReferenceList()) {
            HtmlProducerProvider htmlProducerProvider = (HtmlProducerProvider) extensionReference.getImplementation(HtmlProducerProvider.class);
            if (htmlProducerProvider != null) {
                HtmlProducer htmlProducer = htmlProducerProvider.getHtmlProducer(outputParameters);
                if (htmlProducer != null) {
                    return htmlProducer;
                }
            }
        }
        return null;
    }

    public static JsonProducer getExtensionJsonProducer(OutputParameters outputParameters) throws ErrorMessageException {
        ExtensionManager extensionManager = outputParameters.getBdfServer().getExtensionManager();
        for (BdfExtensionReference extensionReference : extensionManager.getBdfExtensionReferenceList()) {
            JsonProducerProvider jsonProducerProvider = (JsonProducerProvider) extensionReference.getImplementation(JsonProducerProvider.class);
            if (jsonProducerProvider != null) {
                JsonProducer jsonProducer = jsonProducerProvider.getJsonProducer(outputParameters);
                if (jsonProducer != null) {
                    return jsonProducer;
                }
            }
        }
        return null;
    }

    public static StreamProducer getExtensionStreamProducer(OutputParameters outputParameters) throws ErrorMessageException {
        ExtensionManager extensionManager = outputParameters.getBdfServer().getExtensionManager();
        for (BdfExtensionReference extensionReference : extensionManager.getBdfExtensionReferenceList()) {
            StreamProducerProvider streamProducerProvider = (StreamProducerProvider) extensionReference.getImplementation(StreamProducerProvider.class);
            if (streamProducerProvider != null) {
                StreamProducer streamProducer = streamProducerProvider.getStreamProducer(outputParameters);
                if (streamProducer != null) {
                    return streamProducer;
                }
            }
        }
        return null;
    }

    public static HookHandler getHookHandler(BdfServer bdfServer, BdfUser bdfUser, String hookHandlerName) {
        MultiHookHandlerBuilder builder = new MultiHookHandlerBuilder();
        ExtensionManager extensionManager = bdfServer.getExtensionManager();
        for (BdfExtensionReference extensionReference : extensionManager.getBdfExtensionReferenceList()) {
            HookHandlerProvider hookHandlerProvider = (HookHandlerProvider) extensionReference.getImplementation(HookHandlerProvider.class);
            if (hookHandlerProvider != null) {
                HookHandler hookHandler = hookHandlerProvider.getHookHandler(bdfServer, bdfUser, hookHandlerName);
                if (hookHandler != null) {
                    builder.addHookHandler(hookHandler);
                }
            }
        }
        if (builder.isEmpty()) {
            return null;
        } else {
            return builder.toHookHandler();
        }
    }

    public static LangContext checkLangContext(BdfServer bdfServer, @Nullable TableExportDef tableExportDef, @Nullable BdfUser bdfUser) {
        LangContext langContext = null;
        if (tableExportDef != null) {
            langContext = BdfServerUtils.checkLangMode(bdfServer, tableExportDef);
        }
        if (langContext == null) {
            if (bdfUser != null) {
                langContext = bdfUser;
            } else {
                langContext = bdfServer.getLangConfiguration().toLangContext();
            }
        }
        return langContext;
    }

    public static List<String> toActiveExtensionList(Collection<BdfExtensionReference> references) {
        List<String> activeExtensionList = new ArrayList<String>();
        for (BdfExtensionReference reference : references) {
            if (reference.isActive()) {
                activeExtensionList.add(reference.getRegistrationName());
            }
        }
        return activeExtensionList;
    }

    public static void checkResourceChange(BdfServer bdfServer, RelativePath relativePath) {
        String extension = FileName.getExtension(relativePath.getLastName());
        switch (extension) {
            case "ini":
                bdfServer.getL10nManager().update();
                break;
            case "xsl":
                bdfServer.getTransformationManager().update();
                break;
        }
        bdfServer.getJsAnalyser().clearCache();
    }

    public static SelectionContextBuilder initSelectionContextBuilder(BdfParameters bdfParameters) {
        return initSelectionContextBuilder(bdfParameters.getBdfServer(), bdfParameters.getWorkingLang());
    }

    public static SelectionContextBuilder initSelectionContextBuilder(BdfServer bdfServer, Lang workingLang) {
        return SelectionContextBuilder.init(bdfServer.getFichotheque(), bdfServer.getL10nManager(), workingLang);
    }

    public static ExtractionContextBuilder initAdminExtractionContextBuilder(BdfServer bdfServer) {
        return initExtractionContextBuilder(bdfServer, LocalisationUtils.toUserLangContext(bdfServer.getDefaultWorkingLang()), PermissionUtils.FICHOTHEQUEADMIN_PERMISSIONSUMMARY);
    }

    public static ExtractionContextBuilder initExtractionContextBuilder(BdfServer bdfServer, LangContext langContext, PermissionSummary permissionSummary) {
        return ExtractionContextBuilder.init(bdfServer.getFichotheque(), bdfServer.getL10nManager(), langContext, permissionSummary)
                .setMimeTypeResolver(bdfServer.getMimeTypeResolver())
                .setDataResolverProvider(buildDataResolverProvider(bdfServer))
                .setPolicyProvider(bdfServer.getPolicyManager().getPolicyProvider());
    }

    public static DataResolverProvider buildDataResolverProvider(BdfServer bdfServer) {
        return new InternalDataResolverProvider(bdfServer);
    }

    public static LangContext checkLangMode(BdfServer bdfServer, TableExportDef tableExportDef) {
        String langMode = tableExportDef.getLangMode();
        if (langMode != null) {
            switch (langMode) {
                case TableExportDescription.WORKING_LANGMODE:
                    return bdfServer.getLangConfiguration().toLangContext(false);
                case TableExportDescription.SUPPLEMENTARY_LANGMODE:
                    return bdfServer.getLangConfiguration().toLangContext(true);
                case TableExportDescription.CUSTOMLIST_LANGMODE:
                    Langs langs = tableExportDef.getLangs();
                    if (!langs.isEmpty()) {
                        return ListLangContextBuilder.build(langs);
                    }
            }
        }
        return null;
    }

    public static ExtractionContext checkLangMode(ExtractionContext extractionContext, BdfServer bdfServer, TableExportDef tableExportDef) {
        if (tableExportDef != null) {
            LangContext checkedLangContext = BdfServerUtils.checkLangMode(bdfServer, tableExportDef);
            if (checkedLangContext != null) {
                return ExtractionUtils.derive(extractionContext, checkedLangContext);
            }
        }
        return extractionContext;
    }

    public static List<Corpus> getIncludeSatelliteList(Corpus mainCorpus) {
        Attribute attribute = mainCorpus.getCorpusMetadata().getAttributes().getAttribute(FicheFormSpace.SATELLITES_KEY);
        if (attribute != null) {
            List<Corpus> satelliteList = mainCorpus.getSatelliteCorpusList();
            if (!satelliteList.isEmpty()) {
                Map<String, Corpus> availableMap = new HashMap<String, Corpus>();
                Set<String> addedSet = new HashSet<String>();
                for (Corpus satellite : satelliteList) {
                    availableMap.put(satellite.getSubsetName(), satellite);
                }
                List<Corpus> includedSatelliteList = new ArrayList<Corpus>();
                for (String value : attribute) {
                    if (!addedSet.contains(value)) {
                        Corpus satellite = availableMap.get(value);
                        if (satellite != null) {
                            includedSatelliteList.add(satellite);
                        }
                        addedSet.add(value);
                    }
                }
                return includedSatelliteList;
            }
        }
        return FichothequeUtils.EMPTY_CORPUSLIST;
    }

    public static FichothequeQueries resolveSelectionOptions(BdfServer bdfServer, SelectionOptions selectionOptions) {
        return mergeFichothequeQueries(bdfServer, selectionOptions.getSelectionDefName(), selectionOptions.getCustomFichothequeQueries());
    }

    public static FichothequeQueries mergeFichothequeQueries(BdfServer bdfServer, String selectionDefName, FichothequeQueries customFichothequeQueries) {
        SelectionDef selectionDef = bdfServer.getSelectionManager().getSelectionDef(selectionDefName);
        if (selectionDef == null) {
            return customFichothequeQueries;
        }
        if (customFichothequeQueries.isEmpty()) {
            return selectionDef.getFichothequeQueries();
        }
        return FichothequeQueriesBuilder.init()
                .addFichothequeQueries(selectionDef.getFichothequeQueries())
                .addFichothequeQueries(customFichothequeQueries)
                .toFichothequeQueries();

    }

    public static HtmlConverter getCleanerHtmlConverter(HtmlCleaner htmlCleaner) {
        return new CleanerHtmlConverter(htmlCleaner);
    }


    private static class InternalSatelliteOpportunities implements SatelliteOpportunities {

        private final List<SatelliteOpportunities.Entry> opportunityList;
        private final boolean onlyReadAllowed;

        private InternalSatelliteOpportunities(List<SatelliteOpportunities.Entry> opportunityList, boolean onlyReadAllowed) {
            this.opportunityList = opportunityList;
            this.onlyReadAllowed = onlyReadAllowed;
        }

        @Override
        public List<Entry> getEntryList() {
            return opportunityList;
        }

        @Override
        public boolean onlyReadAllowed() {
            return onlyReadAllowed;
        }

    }


    private static class EmptySatelliteOpportunities implements SatelliteOpportunities {

        private final List<Entry> entryList = Collections.emptyList();

        private EmptySatelliteOpportunities() {

        }

        @Override
        public List<Entry> getEntryList() {
            return entryList;
        }

        @Override
        public boolean onlyReadAllowed() {
            return true;
        }

    }


    private static class InternalOpportunity implements SatelliteOpportunities.Entry {

        private final Corpus corpus;
        private final FicheMeta ficheMeta;
        private final short availableActionCategory;

        private InternalOpportunity(Corpus corpus, short availableActionCategory) {
            this.corpus = corpus;
            this.availableActionCategory = availableActionCategory;
            this.ficheMeta = null;
        }

        private InternalOpportunity(FicheMeta ficheMeta, short availableActionCategory) {
            this.corpus = ficheMeta.getCorpus();
            this.availableActionCategory = availableActionCategory;
            this.ficheMeta = ficheMeta;
        }

        @Override
        public Corpus getSatelliteCorpus() {
            return corpus;
        }

        @Override
        public FicheMeta getFicheMeta() {
            return ficheMeta;
        }

        @Override
        public short getAvailableActionCategory() {
            return availableActionCategory;
        }

    }


    private static class SatelliteOpportunitiesEntryList extends AbstractList<SatelliteOpportunities.Entry> implements RandomAccess {

        private final SatelliteOpportunities.Entry[] array;

        private SatelliteOpportunitiesEntryList(SatelliteOpportunities.Entry[] array) {
            this.array = array;
        }

        @Override
        public int size() {
            return array.length;
        }

        @Override
        public SatelliteOpportunities.Entry get(int index) {
            return array[index];
        }

    }


    private static class InternalDataResolverProvider implements DataResolverProvider {

        private final BdfServer bdfServer;
        private final Map<DataKey, DataResolver> resolverMap = new HashMap<DataKey, DataResolver>();

        private InternalDataResolverProvider(BdfServer bdfServer) {
            this.bdfServer = bdfServer;
        }

        @Override
        public DataResolver getDataResolver(SubsetKey subsetKey, String name) {
            DataKey dataKey = new DataKey(subsetKey, name);
            DataResolver dataResolver = resolverMap.get(dataKey);
            if (dataResolver == null) {
                ExternalSourceDef externalSourceDef = getExternalSourceDef(dataKey);
                if (externalSourceDef != null) {
                    dataResolver = buildDataResolver(dataKey, externalSourceDef);
                } else {
                    dataResolver = ExtractionContextUtils.DEFAULT_DATARESOLVER;
                }
                resolverMap.put(dataKey, dataResolver);
            }
            return dataResolver;
        }

        private DataResolver buildDataResolver(DataKey dataKey, ExternalSourceDef externalSourceDef) {
            String type = externalSourceDef.getType();
            int idx = type.indexOf(':');
            if (idx != -1) {
                String registrationName = type.substring(0, idx);
                ExtensionDataResolverProvider extensionDataResolverProvider = getExtensionDataResolverProvider(registrationName);
                if (extensionDataResolverProvider != null) {
                    type = type.substring(idx + 1);
                    return extensionDataResolverProvider.getDataResolver(dataKey, new ExtensionExternalSourceDef(type, externalSourceDef));
                } else {
                    return new ErrorDataResolver(type, "Unknown extension or extension without dataResolverProvider: " + registrationName);
                }
            } else {
                switch (type) {
                    case CoreExternalSourceCatalog.DATAUI_FICHES_TYPENAME: {
                        try {
                            return FichesDataResolver.build(bdfServer, externalSourceDef);
                        } catch (ResolverErrorException ree) {
                            return new ErrorDataResolver(type, ree.getMessage());
                        }
                    }
                    default:
                        return new ErrorDataResolver(type, "Unknown type: " + type);
                }
            }
        }

        private ExtensionDataResolverProvider getExtensionDataResolverProvider(String registrationName) {
            BdfExtensionReference bdfExtensionReference = bdfServer.getExtensionManager().getBdfExtensionReference(registrationName);
            if (bdfExtensionReference != null) {
                return (ExtensionDataResolverProvider) bdfExtensionReference.getImplementation(ExtensionDataResolverProvider.class);
            }
            return null;
        }

        private ExternalSourceDef getExternalSourceDef(DataKey dataKey) {
            SubsetKey subsetKey = dataKey.getSubsetKey();
            if (!subsetKey.isCorpusSubset()) {
                return null;
            }
            Corpus corpus = (Corpus) bdfServer.getFichotheque().getSubset(subsetKey);
            if (corpus == null) {
                return null;
            }
            DataUi dataUi = (DataUi) bdfServer.getUiManager().getMainUiComponents(corpus).getUiComponent(dataKey);
            if (dataUi == null) {
                return null;
            }
            return dataUi.getExternalSourceDef();
        }


    }


    private static class ExtensionExternalSourceDef implements ExternalSourceDef {

        private final String type;
        private final ExternalSourceDef externalSourceDef;

        private ExtensionExternalSourceDef(String type, ExternalSourceDef externalSourceDef) {
            this.type = type;
            this.externalSourceDef = externalSourceDef;
        }

        @Override
        public String getType() {
            return type;
        }

        @Override
        public String getParam(String parameterName) {
            return externalSourceDef.getParam(parameterName);
        }

        @Override
        public Set<String> getParamNameSet() {
            return externalSourceDef.getParamNameSet();
        }

    }


    private static class DefaultHtmlConverter implements HtmlConverter {

        private DefaultHtmlConverter() {

        }

        @Override
        public FicheBlocks convertHtml(String html) {
            return ConversionEngine.convertHtmlFragment(html);
        }

    }


    private static class CleanerHtmlConverter implements HtmlConverter {

        private final HtmlCleaner htmlCleaner;

        private CleanerHtmlConverter(HtmlCleaner htmlCleaner) {
            this.htmlCleaner = htmlCleaner;
        }

        @Override
        public FicheBlocks convertHtml(String html) {
            return ConversionEngine.convertHtmlFragment(htmlCleaner.cleanHtml(html).toString());
        }

    }

}
