/* BdfServer - Copyright (c) 2009-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.ficheform.builders;

import fr.exemole.bdfserver.api.BdfServer;
import fr.exemole.bdfserver.api.exportation.table.FicheTableParameters;
import fr.exemole.bdfserver.api.ficheform.CorpusIncludeElement;
import fr.exemole.bdfserver.api.ficheform.FicheFormParameters;
import fr.exemole.bdfserver.api.ui.IncludeUi;
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.tools.L10nUtils;
import fr.exemole.bdfserver.tools.exportation.table.BdfTableExportUtils;
import fr.exemole.bdfserver.tools.exportation.table.DefaultTableDefFactory;
import fr.exemole.bdfserver.tools.ficheform.FicheFormUtils;
import fr.exemole.bdfserver.tools.ui.UiUtils;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.RandomAccess;
import net.fichotheque.FichothequeConstants;
import net.fichotheque.corpus.Corpus;
import net.fichotheque.corpus.FicheMeta;
import net.fichotheque.croisement.Liaison;
import net.fichotheque.exportation.table.CellConverter;
import net.fichotheque.exportation.table.SubsetTable;
import net.fichotheque.exportation.table.TableDef;
import net.fichotheque.include.ExtendedIncludeKey;
import net.fichotheque.include.LiageTest;
import net.fichotheque.pointeurs.FichePointeur;
import net.fichotheque.pointeurs.SubsetItemPointeur;
import net.fichotheque.tools.exportation.table.SubsetTableBuilder;
import net.fichotheque.utils.CroisementUtils;
import net.mapeadores.util.annotation.Nullable;
import net.mapeadores.util.attr.AttributeUtils;
import net.mapeadores.util.attr.Attributes;


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

    private final static String CHECK_TYPE = "check";
    private final static String TABLE_TYPE = "table";
    private final String name;
    private final Corpus corpus;
    private final List<InternalEntry> entryList = new ArrayList<InternalEntry>();
    private String type = CHECK_TYPE;
    private String label = "";
    private boolean mandatory = false;
    private boolean hasWeightFilter = false;
    private int rows = 1;
    private Attributes attributes = AttributeUtils.EMPTY_ATTRIBUTES;
    private SubsetTable subsetTable;
    private CellConverter cellConverter;

    public CorpusIncludeElementBuilder(String name, Corpus corpus) {
        this.name = name;
        this.corpus = corpus;
    }

    public CorpusIncludeElementBuilder setHasWeightFilter(boolean hasWeightFilter) {
        this.hasWeightFilter = hasWeightFilter;
        return this;
    }

    public CorpusIncludeElementBuilder setLabel(String label) {
        if (label == null) {
            throw new IllegalArgumentException("label is null");
        }
        this.label = label;
        return this;
    }

    public CorpusIncludeElementBuilder setMandatory(boolean mandatory) {
        this.mandatory = mandatory;
        return this;
    }

    public CorpusIncludeElementBuilder setAttributes(Attributes attributes) {
        if (attributes == null) {
            this.attributes = AttributeUtils.EMPTY_ATTRIBUTES;
        } else {
            this.attributes = attributes;
        }
        return this;
    }

    public CorpusIncludeElementBuilder setRows(int rows) {
        this.rows = rows;
        return this;
    }

    public CorpusIncludeElementBuilder setTableInfos(SubsetTable subsetTable, CellConverter cellConverter) {
        if ((subsetTable == null) || (cellConverter == null)) {
            type = CHECK_TYPE;
            this.subsetTable = null;
            this.cellConverter = null;
        } else {
            type = TABLE_TYPE;
            this.subsetTable = subsetTable;
            this.cellConverter = cellConverter;
        }
        return this;
    }

    public CorpusIncludeElementBuilder addFicheMeta(FicheMeta ficheMeta, int weight) {
        boolean globalLiage = (name.equals(FichothequeConstants.LIAGE_NAME));
        String value;
        if (globalLiage) {
            value = ficheMeta.getGlobalId();
        } else {
            value = String.valueOf(ficheMeta.getId());
        }
        if (weight > 1) {
            value = value + "<" + weight + ">";
        }
        entryList.add(new InternalEntry(ficheMeta, weight, value));
        return this;
    }

    public CorpusIncludeElementBuilder populateOptions(FicheFormParameters ficheFormParameters, IncludeUi includeUi) {
        return this
                .setLabel(L10nUtils.toLabelString(ficheFormParameters, includeUi))
                .setMandatory(includeUi.isMandatory())
                .setAttributes(includeUi.getAttributes());
    }

    public CorpusIncludeElement toCorpusIncludeElement() {
        List<CorpusIncludeElement.Entry> finalList = wrap(entryList.toArray(new InternalEntry[entryList.size()]));
        if (type.equals(TABLE_TYPE)) {
            return new InternalTableCorpusIncludeElement(name, corpus, label, mandatory, attributes, finalList, subsetTable, cellConverter);
        } else {
            return new InternalCheckCorpusIncludeElement(name, corpus, hasWeightFilter, label, mandatory, attributes, finalList, rows);
        }
    }

    @Nullable
    public static CorpusIncludeElementBuilder check(FichePointeur fichePointeur, FicheFormParameters ficheFormParameters, SpecialIncludeUi includeUi) {
        if (!includeUi.getName().equals(FichothequeConstants.LIAGE_NAME)) {
            return null;
        }
        CorpusIncludeElementBuilder builder = init(FichothequeConstants.LIAGE_NAME, null)
                .setHasWeightFilter(false)
                .setRows(4);
        if (!fichePointeur.isEmpty()) {
            Collection<Liaison> liaisons = CroisementUtils.getNoList(fichePointeur, "", getLiageTest(fichePointeur, ficheFormParameters), ficheFormParameters.getPermissionSummary().getSubsetAccessPredicate(), null);
            for (Liaison liaison : liaisons) {
                builder.addFicheMeta((FicheMeta) liaison.getSubsetItem(), liaison.getLien().getWeight());
            }
        }
        if (includeUi.isObsolete()) {
            if (fichePointeur.isEmpty()) {
                return null;
            } else {
                if (builder.entryList.isEmpty()) {
                    return null;
                }
            }
        }
        return builder
                .populateOptions(ficheFormParameters, includeUi);
    }

    @Nullable
    public static CorpusIncludeElementBuilder check(FichePointeur fichePointeur, FicheFormParameters ficheFormParameters, SubsetIncludeUi includeUi) {
        Corpus corpus = (Corpus) ficheFormParameters.getBdfServer().getFichotheque().getSubset(includeUi.getSubsetKey());
        if (corpus == null) {
            return null;
        }
        ExtendedIncludeKey includeKey = includeUi.getExtendedIncludeKey();
        SubsetItemPointeur pointeur = FicheFormUtils.checkMasterPointeur(fichePointeur, ficheFormParameters, includeUi);
        CorpusIncludeElementBuilder builder = CorpusIncludeElementBuilder.init(includeUi.getName(), corpus)
                .setHasWeightFilter(includeKey.hasWeightFilter());
        populate(builder, corpus, pointeur, includeKey);
        if ((builder.entryList.isEmpty()) && (includeUi.isObsolete())) {
            return null;
        }
        builder
                .populateOptions(ficheFormParameters, includeUi);
        if (includeUi.isFicheTable()) {
            BdfServer bdfServer = ficheFormParameters.getBdfServer();
            List<UiComponent> uiComponentList = UiUtils.filterFicheTableUiComponents(bdfServer.getUiManager().getMainUiComponents(corpus), fichePointeur.getSubsetKey());
            TableDef tableDef = DefaultTableDefFactory.fromComponentList(bdfServer, corpus, uiComponentList, FicheTableParameters.LABEL_PATTERNMODE, ficheFormParameters.getPermissionSummary());
            SubsetTable subsetTable = SubsetTableBuilder.init(corpus).populate(tableDef, bdfServer.getTableExportContext()).toSubsetTable();
            builder.setTableInfos(subsetTable, BdfTableExportUtils.toCellConverter(bdfServer, ficheFormParameters.getDefaultExtractionContext(), subsetTable));
        }
        return builder;
    }

    private static void populate(CorpusIncludeElementBuilder builder, Corpus corpus, SubsetItemPointeur pointeur, ExtendedIncludeKey includeKey) {
        Collection<Liaison> liaisons = pointeur.getLiaisons(corpus, includeKey);
        if (includeKey.hasWeightFilter()) {
            for (Liaison liaison : liaisons) {
                builder.addFicheMeta((FicheMeta) liaison.getSubsetItem(), -1);
            }
        } else {
            for (Liaison liaison : liaisons) {
                builder.addFicheMeta((FicheMeta) liaison.getSubsetItem(), liaison.getLien().getWeight());
            }
        }
    }

    public static CorpusIncludeElementBuilder init(String name, Corpus corpus) {
        return new CorpusIncludeElementBuilder(name, corpus);
    }

    private static List<CorpusIncludeElement.Entry> wrap(CorpusIncludeElement.Entry[] array) {
        return new EntryList(array);
    }

    private static LiageTest getLiageTest(FichePointeur fichePointeur, FicheFormParameters ficheFormParameters) {
        String liageTestName = LiageTest.class.getName();
        Object obj = fichePointeur.getPointeurObject(liageTestName);
        if ((obj != null) && (obj instanceof LiageTest)) {
            return (LiageTest) obj;
        }
        LiageTest liageTest = UiUtils.checkLiageTest(ficheFormParameters.getBdfServer().getUiManager().getMainUiComponents(fichePointeur.getCorpus()));
        fichePointeur.putPointeurObject(liageTestName, liageTest);
        return liageTest;
    }


    private static class InternalCheckCorpusIncludeElement implements CorpusIncludeElement.Check {

        private final String name;
        private final Corpus corpus;
        private final boolean withWeightFilter;
        private final String label;
        private final boolean mandatory;
        private final Attributes attributes;
        private final List<CorpusIncludeElement.Entry> entryList;
        private final int rows;


        private InternalCheckCorpusIncludeElement(String name, Corpus corpus, boolean withWeightFilter, String label, boolean mandatory, Attributes attributes, List<CorpusIncludeElement.Entry> entryList, int rows) {
            this.name = name;
            this.withWeightFilter = withWeightFilter;
            this.corpus = corpus;
            this.label = label;
            this.mandatory = mandatory;
            this.attributes = attributes;
            this.rows = rows;
            this.entryList = entryList;
        }

        @Override
        public String getIncludeName() {
            return name;
        }

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

        @Override
        public boolean hasWeightFilter() {
            return withWeightFilter;
        }

        @Override
        public String getLabel() {
            return label;
        }

        @Override
        public boolean isMandatory() {
            return mandatory;
        }

        @Override
        public Attributes getAttributes() {
            return attributes;
        }

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

        @Override
        public int getRows() {
            return rows;
        }


    }


    private static class InternalTableCorpusIncludeElement implements CorpusIncludeElement.Table {

        private final String name;
        private final Corpus corpus;
        private final String label;
        private final boolean mandatory;
        private final Attributes attributes;
        private final List<CorpusIncludeElement.Entry> entryList;
        private final SubsetTable subsetTable;
        private final CellConverter cellConverter;


        private InternalTableCorpusIncludeElement(String name, Corpus corpus, String label, boolean mandatory, Attributes attributes, List<CorpusIncludeElement.Entry> entryList, SubsetTable subsetTable, CellConverter cellConverter) {
            this.name = name;
            this.corpus = corpus;
            this.label = label;
            this.mandatory = mandatory;
            this.attributes = attributes;
            this.entryList = entryList;
            this.subsetTable = subsetTable;
            this.cellConverter = cellConverter;
        }

        @Override
        public String getIncludeName() {
            return name;
        }

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

        @Override
        public String getLabel() {
            return label;
        }

        @Override
        public boolean isMandatory() {
            return mandatory;
        }

        @Override
        public Attributes getAttributes() {
            return attributes;
        }

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

        @Override
        public SubsetTable getSubsetTable() {
            return subsetTable;
        }

        @Override
        public CellConverter getCellConverter() {
            return cellConverter;
        }


    }


    private static class InternalEntry implements CorpusIncludeElement.Entry {

        private final FicheMeta ficheMeta;
        private final int weight;
        private final String value;

        private InternalEntry(FicheMeta ficheMeta, int weight, String value) {
            this.ficheMeta = ficheMeta;
            this.weight = weight;
            this.value = value;
        }

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

        @Override
        public int getWeight() {
            return weight;
        }

        @Override
        public String getValue() {
            return value;
        }

    }


    private static class EntryList extends AbstractList<CorpusIncludeElement.Entry> implements RandomAccess {

        private final CorpusIncludeElement.Entry[] array;

        private EntryList(CorpusIncludeElement.Entry[] array) {
            this.array = array;
        }

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

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

    }

}
