/* BdfServer - 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.tools.exportation.transformation;

import fr.exemole.bdfserver.api.BdfServer;
import fr.exemole.bdfserver.api.BdfServerConstants;
import fr.exemole.bdfserver.api.namespaces.CellSpace;
import fr.exemole.bdfserver.api.namespaces.FicheFormSpace;
import fr.exemole.bdfserver.api.namespaces.RoleSpace;
import fr.exemole.bdfserver.api.ui.CommentUi;
import fr.exemole.bdfserver.api.ui.DataUi;
import fr.exemole.bdfserver.api.ui.FieldUi;
import fr.exemole.bdfserver.api.ui.SpecialIncludeUi;
import fr.exemole.bdfserver.api.ui.SubsetIncludeUi;
import fr.exemole.bdfserver.api.ui.UiComponent;
import fr.exemole.bdfserver.api.ui.UiComponents;
import fr.exemole.bdfserver.tools.BdfServerUtils;
import fr.exemole.bdfserver.tools.ui.UiUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;
import net.fichotheque.Fichotheque;
import net.fichotheque.FichothequeConstants;
import net.fichotheque.Subset;
import net.fichotheque.SubsetKey;
import net.fichotheque.corpus.Corpus;
import net.fichotheque.exportation.transformation.TransformationKey;
import net.fichotheque.extraction.ExtractionConstants;
import net.fichotheque.extraction.FilterParameters;
import net.fichotheque.extraction.def.CorpusExtractDef;
import net.fichotheque.extraction.def.ExtractionDef;
import net.fichotheque.extraction.def.FicheFilter;
import net.fichotheque.extraction.def.MotcleFilter;
import net.fichotheque.extraction.def.SubsetExtractDef;
import net.fichotheque.extraction.def.TagNameInfo;
import net.fichotheque.extraction.def.ThesaurusExtractDef;
import net.fichotheque.include.ExtendedIncludeKey;
import net.fichotheque.include.LiageTest;
import net.fichotheque.namespaces.ExtractionSpace;
import net.fichotheque.namespaces.FormatSpace;
import net.fichotheque.selection.FicheCondition;
import net.fichotheque.selection.FicheQuery;
import net.fichotheque.selection.JunctionCondition;
import net.fichotheque.thesaurus.Thesaurus;
import net.fichotheque.tools.extraction.builders.CorpusExtractDefBuilder;
import net.fichotheque.tools.extraction.builders.ExtractionBuilderUtils;
import net.fichotheque.tools.extraction.builders.ExtractionDefBuilder;
import net.fichotheque.tools.extraction.builders.FicheFilterBuilder;
import net.fichotheque.tools.extraction.builders.FilterParametersBuilder;
import net.fichotheque.tools.extraction.builders.MotcleFilterBuilder;
import net.fichotheque.tools.extraction.builders.ThesaurusExtractDefBuilder;
import net.fichotheque.tools.extraction.builders.TitleClauseBuilder;
import net.fichotheque.tools.selection.FicheQueryBuilder;
import net.fichotheque.tools.selection.JunctionConditionBuilder;
import net.fichotheque.utils.ExtractionUtils;
import net.fichotheque.utils.FichothequeUtils;
import net.fichotheque.utils.FilterUnitCatalog;
import net.fichotheque.utils.SelectionUtils;
import net.fichotheque.utils.TransformationUtils;
import net.fichotheque.utils.selection.RangeConditionBuilder;
import net.fichotheque.xml.extraction.ExtractionXMLUtils;
import net.mapeadores.util.attr.Attribute;
import net.mapeadores.util.attr.AttributeKey;
import net.mapeadores.util.attr.Attributes;
import net.mapeadores.util.primitives.Ranges;


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

    private final static Predicate<UiComponent> STANDARD_PREDICATE = new ExtractionComponentPredicate(UiUtils.ALL_COMPONENTPREDICATE);
    private final static Predicate<UiComponent> WITH_SATELLITES_PREDICATE = new ExtractionComponentPredicate(UiUtils.MAIN_COMPONENTPREDICATE);
    private final static Predicate<UiComponent> SATELLITE_PREDICATE = new ExtractionComponentPredicate(UiUtils.SATELLITE_COMPONENTPREDICATE);


    private DefaultExtractionDefFactory() {

    }

    public static ExtractionDef newInstance(BdfServer bdfServer, TransformationKey transformationKey, Attributes attributes) {
        if (transformationKey.isCorpusTransformationKey()) {
            return getFicheExtractionDef(bdfServer, transformationKey.toCorpusKey(), attributes);
        } else {
            switch (transformationKey.getKeyString()) {
                case TransformationKey.COMPILATION:
                    return getCompilationExtractionDef(bdfServer, attributes);
                case TransformationKey.STATTHESAURUS:
                    MotcleFilter thesaurusFilter = MotcleFilterBuilder.init(MotcleFilter.DEFAULT_TYPE)
                            .setWithIcon(true)
                            .setWithLabels(true)
                            .addDefaultCorpusExtractDef()
                            .setChildrenRecursive()
                            .toMotcleFilter();
                    return ExtractionDefBuilder.init()
                            .setDynamicThesaurusExtractDef(ThesaurusExtractDefBuilder.init(thesaurusFilter)
                                    .setBoolean(ThesaurusExtractDef.WITHTHESAURUSTITLE_BOOLEAN, true)
                                    .toThesaurusExtractDef())
                            .toExtractionDef();
                default:
                    return null;
            }
        }
    }

    private static ExtractionDef getCompilationExtractionDef(BdfServer bdfServer, Attributes attributes) {
        ExtractionDefBuilder extractionDefBuilder = new ExtractionDefBuilder();
        for (Corpus corpus : BdfTransformationUtils.getCorpusList(bdfServer.getFichotheque(), attributes)) {
            Converter converter = new Converter(bdfServer, corpus);
            FicheFilter ficheFilter = converter.convertUiComponentList(STANDARD_PREDICATE)
                    .add(FilterUnitCatalog.CHRONO)
                    .add(FilterUnitCatalog.FICHEPHRASE)
                    .toFicheFilter();
            FicheQuery ficheQuery = FicheQueryBuilder.init()
                    .addCorpus(corpus)
                    .toFicheQuery();
            CorpusExtractDef corpusExtractDef = CorpusExtractDefBuilder.init(ficheFilter)
                    .setClause(CorpusExtractDef.TITLE_CLAUSE, TitleClauseBuilder.DEFAULT)
                    .setEntryList(SelectionUtils.toFicheConditionEntry(ficheQuery))
                    .toCorpusExtractDef();
            extractionDefBuilder.addStatic(corpusExtractDef);
        }
        return extractionDefBuilder.toExtractionDef();
    }

    private static ExtractionDef getFicheExtractionDef(BdfServer bdfServer, SubsetKey corpusKey, Attributes attributes) {
        Converter converter = new Converter(bdfServer, corpusKey);
        List<Corpus> includedSatelliteList = converter.getIncludeSatelliteList();
        FicheFilterBuilder builder = converter.convertUiComponentList((includedSatelliteList.isEmpty()) ? STANDARD_PREDICATE : WITH_SATELLITES_PREDICATE)
                .add(FilterUnitCatalog.CHRONO)
                .add(FilterUnitCatalog.FICHEPHRASE);
        for (Corpus satellite : includedSatelliteList) {
            Converter satelliteConverter = new Converter(bdfServer, satellite);
            FicheFilter satelliteFicheFilter = satelliteConverter.convertUiComponentList(SATELLITE_PREDICATE)
                    .toFicheFilter();
            builder.add(FilterUnitCatalog.ficheParentage(satelliteFicheFilter, ExtractionUtils.EMPTY_FILTERPARAMETERS, Collections.singleton(satellite.getSubsetKey())));
        }
        TagNameInfo tagNameInfo = getDynamicTagNameInfo(attributes);
        return ExtractionDefBuilder.init()
                .setDynamicCorpusExtractDef(CorpusExtractDefBuilder
                        .init(builder.toFicheFilter())
                        .setTagNameInfo(tagNameInfo)
                        .toCorpusExtractDef())
                .toExtractionDef();
    }

    private static TagNameInfo getDynamicTagNameInfo(Attributes initAttributes) {
        int extractVersion = TransformationUtils.getExtractVersion(initAttributes);
        if (extractVersion == ExtractionConstants.INITIAL_VERSION) {
            return TagNameInfo.DEFAULT;
        } else {
            return TagNameInfo.NULL;
        }
    }


    private static class Converter {

        protected final BdfServer bdfServer;
        protected final Corpus corpus;
        protected final UiComponents uiComponents;
        protected final Fichotheque fichotheque;


        private Converter(BdfServer bdfServer, Corpus corpus) {
            this.bdfServer = bdfServer;
            this.corpus = corpus;
            this.uiComponents = bdfServer.getUiManager().getMainUiComponents(corpus);
            this.fichotheque = bdfServer.getFichotheque();
        }

        private Converter(BdfServer bdfServer, SubsetKey subsetKey) {
            this(bdfServer, (Corpus) bdfServer.getFichotheque().getSubset(subsetKey));
        }

        private List<Corpus> getIncludeSatelliteList() {
            return BdfServerUtils.getIncludeSatelliteList(corpus);
        }

        private FicheFilterBuilder convertUiComponentList(Predicate<UiComponent> predicate) {
            FicheFilterBuilder builder = FicheFilterBuilder.init();
            for (UiComponent uiComponent : uiComponents.getUiComponentList()) {
                if (!predicate.test(uiComponent)) {
                    continue;
                }
                FilterParameters filterParameters = initFilterParametersBuilder(uiComponent).toFilterParameters();
                convertUiComponent(builder, uiComponent, filterParameters);
            }
            return builder;
        }

        protected void convertUiComponent(FicheFilterBuilder builder, UiComponent uiComponent, FilterParameters filterParameters) {
            if (uiComponent instanceof FieldUi) {
                builder.add(FilterUnitCatalog.fieldKey(((FieldUi) uiComponent).getFieldKey(), filterParameters));
            } else if (uiComponent instanceof SpecialIncludeUi) {
                switch (uiComponent.getName()) {
                    case FichothequeConstants.LIAGE_NAME:
                        builder.add(FilterUnitCatalog.corpusExtract(getLiageCorpusExtractDef(), filterParameters));
                        break;
                    case FichothequeConstants.PARENTAGE_NAME:
                        Subset masterSubset = corpus.getMasterSubset();
                        if ((masterSubset != null) && (masterSubset instanceof Thesaurus)) {
                            MotcleFilter motcleFilter = MotcleFilterBuilder.init(MotcleFilter.DEFAULT_TYPE)
                                    .setWithIcon(true)
                                    .setWithLabels(true)
                                    .setWithFicheStylePhrase(((Thesaurus) masterSubset).getThesaurusMetadata().isWithFicheStyle())
                                    .toMotcleFilter();
                            builder.add(FilterUnitCatalog.masterMotcle(motcleFilter, filterParameters));
                        }
                        builder.add(FilterUnitCatalog.ficheParentage(ExtractionXMLUtils.TITLE_FICHEFILTER, filterParameters, FichothequeUtils.EMPTY_SUBSETKEYLIST));
                        break;
                }
            } else if (uiComponent instanceof SubsetIncludeUi) {
                convertSubsetIncludeUi(builder, (SubsetIncludeUi) uiComponent, filterParameters);
            } else if (uiComponent instanceof DataUi) {
                DataUi dataUi = (DataUi) uiComponent;
                builder.add(FilterUnitCatalog.data(dataUi.getDataName(), filterParameters));
            }
        }

        private void convertSubsetIncludeUi(FicheFilterBuilder builder, SubsetIncludeUi subsetIncludeUi, FilterParameters filterParameters) {
            ExtendedIncludeKey includeKey = subsetIncludeUi.getExtendedIncludeKey();
            if (subsetIncludeUi.isFicheTable()) {
                FicheTableConverter ficheTableConverter = new FicheTableConverter(bdfServer, includeKey.getSubsetKey(), subsetIncludeUi);
                FicheFilter ficheFilter = ficheTableConverter.convertFicheTable()
                        .add(FilterUnitCatalog.FICHEPHRASE)
                        .toFicheFilter();
                CorpusExtractDef extractDef = ExtractionBuilderUtils.getCorpusExtractDef(includeKey, ficheFilter);
                builder.add(FilterUnitCatalog.corpusExtract(extractDef, filterParameters));
            } else {
                if (filterParameters.isEmpty()) {
                    builder.add(ExtractionBuilderUtils.getDefaultFilterUnit(includeKey, fichotheque));
                } else {
                    SubsetExtractDef extractDef = ExtractionBuilderUtils.getDefaultExtractDef(includeKey, fichotheque);
                    builder.add(FilterUnitCatalog.subsetExtract(extractDef, filterParameters));
                }
            }
        }

        private CorpusExtractDef getLiageCorpusExtractDef() {
            CorpusExtractDefBuilder builder = CorpusExtractDefBuilder
                    .init(ExtractionXMLUtils.TITLE_FICHEFILTER)
                    .setName(FichothequeConstants.LIAGE_NAME);
            List<FicheCondition.Entry> entryList = new ArrayList<FicheCondition.Entry>();
            List<SubsetKey> excludeList = new ArrayList<SubsetKey>();
            LiageTest liageTest = UiUtils.checkLiageTest(uiComponents);
            for (Corpus otherCorpus : fichotheque.getCorpusList()) {
                SubsetKey otherKey = otherCorpus.getSubsetKey();
                Object testResult = liageTest.ownsToLiage(otherKey);
                if (testResult instanceof Ranges) {
                    Ranges excludeRanges = (Ranges) testResult;
                    FicheQuery ficheQuery = FicheQueryBuilder.init()
                            .addCorpus(otherKey)
                            .toFicheQuery();
                    JunctionCondition junctionCondition = JunctionConditionBuilder.init()
                            .addTieMode("")
                            .setWeightRangeCondition(RangeConditionBuilder.init(excludeRanges).setExclude(true).toRangeCondition())
                            .toJunctionCondition();
                    entryList.add(SelectionUtils.toFicheConditionEntry(ficheQuery, junctionCondition, false));
                    excludeList.add(otherKey);
                } else {
                    Boolean bool = (Boolean) testResult;
                    if (!bool) {
                        excludeList.add(otherKey);
                    }
                }
            }
            FicheQueryBuilder globalBuilder = FicheQueryBuilder.init();
            if (!excludeList.isEmpty()) {
                globalBuilder
                        .setExclude(true)
                        .addCorpus(excludeList);
            }
            entryList.add(SelectionUtils.toFicheConditionEntry(globalBuilder.toFicheQuery(), SelectionUtils.toJunctionCondition("", -1), false));
            builder.setEntryList(entryList);
            return builder.toCorpusExtractDef();
        }

        protected FilterParametersBuilder initFilterParametersBuilder(UiComponent uiComponent) {
            FilterParametersBuilder filterParametersBuilder = new FilterParametersBuilder();
            Attributes componentAttributes = uiComponent.getAttributes();
            String extractionSpace = ExtractionSpace.EXTRACTION_NAMESPACE.toString();
            for (Attribute attribute : componentAttributes) {
                AttributeKey attributeKey = attribute.getAttributeKey();
                if (attributeKey.getNameSpace().equals(extractionSpace)) {
                    filterParametersBuilder.appendValues(attributeKey.getLocalKey(), attribute);
                }
            }
            Attribute groups = componentAttributes.getAttribute(FicheFormSpace.GROUPS_ATTRIBUTEKEY);
            if (groups != null) {
                filterParametersBuilder.appendValues(ExtractionConstants.GROUPS_PARAM, groups);
            }
            Attribute roles = componentAttributes.getAttribute(RoleSpace.READ_KEY);
            if (roles != null) {
                filterParametersBuilder.appendValues(ExtractionConstants.ROLES_PARAM, roles);
            }
            Attribute dateStyle = componentAttributes.getAttribute(FormatSpace.DATESTYLE_KEY);
            if (dateStyle != null) {
                filterParametersBuilder.appendValue(ExtractionConstants.DATESTYLE_PARAM, dateStyle.getFirstValue());
            }
            Attribute amountSubunit = componentAttributes.getAttribute(FormatSpace.AMOUNTSUBUNIT_KEY);
            if (amountSubunit != null) {
                filterParametersBuilder.appendValue(ExtractionConstants.AMOUNTSUBUNIT_PARAM, amountSubunit.getFirstValue());
            }
            Attribute dateLabelTruncate = componentAttributes.getAttribute(FormatSpace.DATELABELTRUNCATE_KEY);
            if (dateLabelTruncate != null) {
                filterParametersBuilder.appendValue(ExtractionConstants.DATELABELTRUNCATE_PARAM, dateLabelTruncate.getFirstValue());
            }
            switch (uiComponent.getStatus()) {
                case BdfServerConstants.OPTIONAL_STATUS:
                case BdfServerConstants.OBSOLETE_STATUS:
                    filterParametersBuilder.appendValue(ExtractionConstants.HIDE_PARAM, ExtractionConstants.EMPTY_HIDE);
                    break;
                case BdfServerConstants.MANDATORY_STATUS:
                    filterParametersBuilder.appendValue(ExtractionConstants.HIDE_PARAM, ExtractionConstants.NEVER_HIDE);
                    break;
            }
            return filterParametersBuilder;
        }


    }


    private static class FicheTableConverter extends Converter {

        private final SubsetIncludeUi parentComponent;

        private FicheTableConverter(BdfServer bdfServer, SubsetKey subsetKey, SubsetIncludeUi parentComponent) {
            super(bdfServer, subsetKey);
            this.parentComponent = parentComponent;
        }

        private FicheFilterBuilder convertFicheTable() {
            FicheFilterBuilder builder = FicheFilterBuilder.init();
            List<UiComponent> cellList = UiUtils.filterFicheTableUiComponents(uiComponents, parentComponent.getSubsetKey());
            int defaultOrder = 1;
            for (UiComponent uiComponent : cellList) {
                FilterParametersBuilder parametersBuilder = initFilterParametersBuilder(uiComponent);
                checkCellAttributes(uiComponent, parametersBuilder, defaultOrder);
                defaultOrder++;
                convertUiComponent(builder, uiComponent, parametersBuilder.toFilterParameters());
            }
            return builder;
        }

        private void checkCellAttributes(UiComponent uiComponent, FilterParametersBuilder parametersBuilder, int defaultOrder) {
            Attributes attributes = uiComponent.getAttributes();
            Attribute orderAttribute = attributes.getAttribute(CellSpace.ORDER_KEY);
            boolean orderDone = false;
            if (orderAttribute != null) {
                try {
                    int value = Integer.parseInt(orderAttribute.getFirstValue());
                    if (value > 0) {
                        parametersBuilder.appendValue(ExtractionConstants.CELL_ORDER_PARAM, String.valueOf(value));
                        orderDone = true;
                    }
                } catch (NumberFormatException nfe) {

                }
            }
            Attribute formatAttribute = attributes.getAttribute(CellSpace.FORMAT_KEY);
            if (formatAttribute != null) {
                parametersBuilder.appendValue(ExtractionConstants.CELL_FORMAT_PARAM, formatAttribute.getFirstValue());
            }
            if (!orderDone) {
                parametersBuilder.appendValue(ExtractionConstants.CELL_ORDER_PARAM, String.valueOf(defaultOrder));
            }
        }

    }


    private static class ExtractionComponentPredicate implements Predicate<UiComponent> {

        private final Predicate<UiComponent> rootPredicate;

        private ExtractionComponentPredicate(Predicate<UiComponent> rootPredicate) {
            this.rootPredicate = rootPredicate;
        }

        @Override
        public boolean test(UiComponent uiComponent) {
            if (uiComponent instanceof CommentUi) {
                return false;
            }
            return rootPredicate.test(uiComponent);
        }

    }

}
