/* BdfServer_Get - Copyright (c) 2023-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.get.access;

import fr.exemole.bdfserver.api.BdfServer;
import fr.exemole.bdfserver.tools.BdfServerUtils;
import fr.exemole.bdfserver.tools.exportation.table.CellConversionInit;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
import net.fichotheque.Fichotheque;
import net.fichotheque.Subset;
import net.fichotheque.SubsetItem;
import net.fichotheque.SubsetKey;
import net.fichotheque.addenda.Document;
import net.fichotheque.album.Illustration;
import net.fichotheque.corpus.Corpus;
import net.fichotheque.corpus.FicheMeta;
import net.fichotheque.exportation.access.AccessDef;
import net.fichotheque.exportation.table.CellConverter;
import net.fichotheque.exportation.table.ColDef;
import net.fichotheque.exportation.table.ColDefTitleProvider;
import net.fichotheque.exportation.table.SubsetTable;
import net.fichotheque.extraction.ExtractionContext;
import net.fichotheque.junction.Junctions;
import net.fichotheque.permission.PermissionSummary;
import net.fichotheque.selection.FicheQuery;
import net.fichotheque.selection.FicheSelector;
import net.fichotheque.selection.FichothequeQueries;
import net.fichotheque.selection.MotcleQuery;
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.TableDefUtils;
import net.fichotheque.utils.TableExportUtils;
import net.fichotheque.utils.selection.FicheSelectorBuilder;
import net.fichotheque.utils.selection.MotcleSelectorBuilder;
import net.fichotheque.utils.selection.SelectionContextBuilder;
import net.mapeadores.util.localisation.Lang;
import net.mapeadores.util.localisation.LocalisationUtils;
import net.mapeadores.util.logging.ErrorMessageException;


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

    private final BdfServer bdfServer;
    private final AccessDef accessDef;
    private final Lang lang;
    private final Predicate<FicheMeta> fichePredicate;
    private final Predicate<Motcle> motclePredicate;
    private final Predicate<SubsetItem> commonPredicate;
    private final Predicate<Subset> subsetPredicate;
    private final SelectionContext selectionContext;
    private final PermissionSummary permissionSummary;


    private AccessGate(BdfServer bdfServer, AccessDef accessDef, Lang lang, Predicate<FicheMeta> fichePredicate, Predicate<Motcle> motclePredicate, Predicate<Subset> subsetPredicate, SelectionContext selectionContext, PermissionSummary permissionSummary) {
        this.bdfServer = bdfServer;
        this.accessDef = accessDef;
        this.lang = lang;
        this.fichePredicate = fichePredicate;
        this.motclePredicate = motclePredicate;
        this.commonPredicate = new CommonPredicate();
        this.subsetPredicate = subsetPredicate;
        this.selectionContext = selectionContext;
        this.permissionSummary = permissionSummary;
    }

    public BdfServer getBdfServer() {
        return bdfServer;
    }

    public AccessDef getAccessDef() {
        return accessDef;
    }

    public Lang getLang() {
        return lang;
    }

    public Predicate<FicheMeta> getFichePredicate() {
        return fichePredicate;
    }

    public Predicate<Motcle> getMotclePredicate() {
        return motclePredicate;
    }

    public Predicate<SubsetItem> getSubsetItemPredicate() {
        return commonPredicate;
    }

    public Predicate<Subset> getSubsetPredicate() {
        return subsetPredicate;
    }

    public SelectionContext getSelectionContext() {
        return selectionContext;
    }

    public PermissionSummary getPermissionSummary() {
        return permissionSummary;
    }

    public CellConverter getCellConverter() {
        String tableExportName = accessDef.getTableExportName();
        if (tableExportName.isEmpty()) {
            return TableExportUtils.EMPTY_SUMCELLCONVERTER;
        }
        ExtractionContext extractionContext = BdfServerUtils.initExtractionContextBuilder(bdfServer, LocalisationUtils.toUserLangContext(selectionContext.getWorkingLang()), permissionSummary).toExtractionContext();
        try {
            return CellConversionInit.fromTableExportName(bdfServer, extractionContext, tableExportName, commonPredicate).getCellConverter();
        } catch (ErrorMessageException eme) {
            return TableExportUtils.EMPTY_CELLCONVERTER;
        }
    }

    public List<ColDef> getColDefList(Subset subset) {
        String tableExportName = accessDef.getTableExportName();
        if (tableExportName.isEmpty()) {
            return TableExportUtils.EMPTY_COLDEFLIST;
        }
        ExtractionContext extractionContext = BdfServerUtils.initExtractionContextBuilder(bdfServer, LocalisationUtils.toUserLangContext(selectionContext.getWorkingLang()), permissionSummary).toExtractionContext();
        try {
            CellConversionInit.Result result = CellConversionInit.fromTableExportName(bdfServer, extractionContext, tableExportName, commonPredicate);
            SubsetTable subsetTable = result.getSubsetTable(subset);
            if (subsetTable != null) {
                return subsetTable.getColDefList();
            } else {
                return TableExportUtils.EMPTY_COLDEFLIST;
            }
        } catch (ErrorMessageException eme) {
            return TableExportUtils.EMPTY_COLDEFLIST;
        }
    }

    public ColDefTitleProvider getColDefTitleProvider(Subset subset) {
        return TableDefUtils.getLabelColDefTitleProvider(selectionContext.getWorkingLang(), bdfServer.getTableExportContext().getSourceLabelProvider(), subset);
    }

    public static AccessGate build(BdfServer bdfServer, AccessDef accessDef, PermissionSummary permissionSummary, Lang lang) {
        FichothequeQueries fichothequeQueries = BdfServerUtils.resolveSelectionOptions(bdfServer, accessDef.getSelectionOptions());
        List<FicheQuery> ficheQueryList = fichothequeQueries.getFicheQueryList();
        FicheSelector ficheSelector = null;
        Predicate<FicheMeta> fichePredicate = null;
        if (!ficheQueryList.isEmpty()) {
            SelectionContextBuilder builder = BdfServerUtils.initSelectionContextBuilder(bdfServer, lang);
            if (permissionSummary != null) {
                builder.setSubsetAccessPredicate(permissionSummary.getSubsetAccessPredicate());
            } else {
                builder.setSubsetAccessPredicate(EligibilityUtils.ALL_SUBSET_PREDICATE);
            }
            FicheSelectorBuilder ficheSelectorBuilder = FicheSelectorBuilder.init(builder.toSelectionContext());
            for (FicheQuery ficheQuery : ficheQueryList) {
                ficheSelectorBuilder.add(ficheQuery);
            }
            ficheSelector = ficheSelectorBuilder.toFicheSelector();
            fichePredicate = new FichePredicateBuffer(ficheSelectorBuilder.toFicheSelector());
        }
        List<MotcleQuery> motcleQueryList = fichothequeQueries.getMotcleQueryList();
        MotcleSelector motcleSelector = null;
        Predicate<Motcle> motclePredicate;
        if (!motcleQueryList.isEmpty()) {
            SelectionContextBuilder builder = BdfServerUtils.initSelectionContextBuilder(bdfServer, lang)
                    .setFichePredicate(fichePredicate);
            if (permissionSummary != null) {
                builder.setSubsetAccessPredicate(permissionSummary.getSubsetAccessPredicate());
            }
            MotcleSelectorBuilder motcleSelectorBuilder = MotcleSelectorBuilder.init(builder.toSelectionContext());
            for (MotcleQuery motcleQuery : motcleQueryList) {
                motcleSelectorBuilder.add(motcleQuery, null);
            }
            motcleSelector = motcleSelectorBuilder.toMotcleSelector();
            motclePredicate = new MotclePredicateBuffer(motcleSelector);
        } else {
            motclePredicate = EligibilityUtils.ALL_MOTCLE_PREDICATE;
        }
        if (fichePredicate == null) {
            fichePredicate = EligibilityUtils.ALL_FICHE_PREDICATE;
        }
        Set<SubsetKey> subsetKeySet = getAccessSubsetKey(bdfServer.getFichotheque(), ficheSelector, motcleSelector);
        Predicate<Subset> subsetPredicate = new SubsetPredicate(subsetKeySet);
        SelectionContext selectionContext = BdfServerUtils.initSelectionContextBuilder(bdfServer, lang)
                .setFichePredicate(fichePredicate)
                .setSubsetAccessPredicate(subsetPredicate)
                .toSelectionContext();
        if (permissionSummary == null) {
            permissionSummary = new PublicPermissionSummary(fichePredicate, subsetPredicate);
        }
        return new AccessGate(bdfServer, accessDef, lang, fichePredicate, motclePredicate, subsetPredicate, selectionContext, permissionSummary);
    }

    private static Set<SubsetKey> getAccessSubsetKey(Fichotheque fichotheque, FicheSelector ficheSelector, MotcleSelector motcleSelector) {
        Set<SubsetKey> set = new HashSet<SubsetKey>();
        List<Corpus> corpusList;
        if (ficheSelector != null) {
            corpusList = ficheSelector.getCorpusList();
        } else {
            corpusList = fichotheque.getCorpusList();
        }
        for (Corpus corpus : corpusList) {
            set.add(corpus.getSubsetKey());
        }
        List<Thesaurus> thesaurusList;
        if (motcleSelector != null) {
            thesaurusList = motcleSelector.getThesaurusList();
        } else {
            thesaurusList = fichotheque.getThesaurusList();
        }
        for (Thesaurus thesaurus : thesaurusList) {
            set.add(thesaurus.getSubsetKey());
        }
        return set;
    }


    private static class FichePredicateBuffer implements Predicate<FicheMeta> {

        private final Map<FicheMeta, Boolean> map = new HashMap<FicheMeta, Boolean>();
        private final FicheSelector ficheSelector;

        private FichePredicateBuffer(FicheSelector ficheSelector) {
            this.ficheSelector = ficheSelector;
        }

        @Override
        public boolean test(FicheMeta ficheMeta) {
            Boolean bool = map.get(ficheMeta);
            if (bool != null) {
                return bool;
            }
            if (ficheSelector.test(ficheMeta)) {
                map.put(ficheMeta, Boolean.TRUE);
                return true;
            } else {
                map.put(ficheMeta, Boolean.FALSE);
                return false;
            }
        }

    }


    private static class MotclePredicateBuffer implements Predicate<Motcle> {

        private final Map<Motcle, Boolean> map = new HashMap<Motcle, Boolean>();
        private final MotcleSelector motcleSelector;

        private MotclePredicateBuffer(MotcleSelector motcleSelector) {
            this.motcleSelector = motcleSelector;
        }

        @Override
        public boolean test(Motcle motcle) {
            Boolean bool = map.get(motcle);
            if (bool != null) {
                return bool;
            }
            if (motcleSelector.test(motcle)) {
                map.put(motcle, Boolean.TRUE);
                return true;
            } else {
                map.put(motcle, Boolean.FALSE);
                return false;
            }
        }

    }


    private class CommonPredicate implements Predicate<SubsetItem> {


        private CommonPredicate() {
        }

        @Override
        public boolean test(SubsetItem subsetItem) {
            if (subsetItem instanceof FicheMeta) {
                return fichePredicate.test((FicheMeta) subsetItem);
            }
            if (subsetItem instanceof Motcle) {
                return motclePredicate.test((Motcle) subsetItem);
            }
            return true;
        }

    }


    private static class SubsetPredicate implements Predicate<Subset> {

        private final Set<SubsetKey> set;

        private SubsetPredicate(Set<SubsetKey> set) {
            this.set = set;
        }

        @Override
        public boolean test(Subset subset) {
            SubsetKey subsetKey = subset.getSubsetKey();
            if ((subsetKey.isAddendaSubset()) || (subsetKey.isAlbumSubset())) {
                return true;
            }
            return set.contains(subsetKey);
        }

    }


    private static class PublicPermissionSummary implements PermissionSummary {

        private final Predicate<FicheMeta> fichePredicate;
        private final Predicate<Subset> subsetPredicate;

        private PublicPermissionSummary(Predicate<FicheMeta> fichePredicate, Predicate<Subset> subsetPredicate) {
            this.fichePredicate = fichePredicate;
            this.subsetPredicate = subsetPredicate;
        }

        @Override
        public int getReadLevel(SubsetKey subsetKey) {
            if ((subsetKey.isAddendaSubset()) || (subsetKey.isAlbumSubset())) {
                return PermissionSummary.SUMMARYLEVEL_3_JUNCTION_TEST;
            } else {
                return PermissionSummary.SUMMARYLEVEL_4_ALL;
            }
        }

        @Override
        public int getWriteLevel(SubsetKey subsetKey) {
            return PermissionSummary.SUMMARYLEVEL_0_NONE;
        }


        @Override
        public boolean canCreate(SubsetKey subsetKey) {
            return false;
        }

        @Override
        public boolean isSubsetAdmin(SubsetKey subsetKey) {
            return false;
        }

        @Override
        public boolean isFichothequeAdmin() {
            return false;
        }

        @Override
        public boolean hasRole(String roleName) {
            return false;
        }

        @Override
        public boolean canDo(String actionName) {
            return false;
        }

        @Override
        public Predicate<Subset> getSubsetAccessPredicate() {
            return subsetPredicate;
        }

        @Override
        public boolean canRead(SubsetItem subsetItem) {
            if (subsetItem instanceof FicheMeta) {
                return fichePredicate.test((FicheMeta) subsetItem);
            }
            if ((subsetItem instanceof Document) || (subsetItem instanceof Illustration)) {
                return testJunctions(subsetItem);
            }
            return true;
        }

        @Override
        public boolean canWrite(SubsetItem subsetItem) {
            return false;
        }

        private boolean testJunctions(SubsetItem subsetItem) {
            Fichotheque fichotheque = subsetItem.getFichotheque();
            for (Corpus corpus : subsetItem.getFichotheque().getCorpusList()) {
                if (subsetPredicate.test(corpus)) {
                    for (Junctions.Entry entry : fichotheque.getJunctions(subsetItem, corpus).getEntryList()) {
                        if (fichePredicate.test((FicheMeta) entry.getSubsetItem())) {
                            return true;
                        }
                    }
                }
            }
            return false;
        }

    }


}
