/* FichothequeLib_API - 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.utils;

import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.RandomAccess;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.function.Predicate;
import net.fichotheque.Fichotheque;
import net.fichotheque.Subset;
import net.fichotheque.SubsetItem;
import net.fichotheque.SubsetKey;
import net.fichotheque.corpus.Corpus;
import net.fichotheque.croisement.Croisement;
import net.fichotheque.croisement.CroisementChange;
import net.fichotheque.croisement.CroisementChanges;
import net.fichotheque.croisement.CroisementKey;
import net.fichotheque.croisement.Croisements;
import net.fichotheque.croisement.CroisementsBySubset;
import net.fichotheque.croisement.Liaison;
import net.fichotheque.croisement.LiaisonWeightSortKey;
import net.fichotheque.croisement.LiaisonSortKey;
import net.fichotheque.croisement.Lien;
import net.fichotheque.eligibility.SubsetEligibility;
import net.fichotheque.include.IncludeMode;
import net.fichotheque.include.LiageTest;
import net.fichotheque.namespaces.CroisementSpace;
import net.fichotheque.pointeurs.SubsetItemPointeur;
import net.mapeadores.util.annotation.Nullable;
import net.mapeadores.util.attr.Attribute;
import net.mapeadores.util.attr.AttributeKey;
import net.mapeadores.util.attr.CheckedNameSpace;
import net.mapeadores.util.primitives.Ranges;


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

    public final static Croisements EMPTY_CROISEMENTS = new EmptyCroisements();
    public final static List<Liaison> EMPTY_LIAISONLIST = Collections.emptyList();
    public final static Collection<Liaison> EMPTY_LIAISONCOLLECTION = Collections.emptyList();
    public final static List<Lien> EMPTY_LIENLIST = Collections.emptyList();
    public final static List<String> EMPTY_REMOVEDMODELIST = Collections.emptyList();
    private final static AttributeKey OLD_SHOULD_NOT_CROISEMENT_KEY = AttributeKey.build(CheckedNameSpace.build("bdf.croisement"), "should_not");

    private CroisementUtils() {
    }

    public static List<SubsetItem> toSubsetItemList(Croisements croisements) {
        List<SubsetItem> result = new ArrayList<SubsetItem>();
        for (Croisements.Entry entry : croisements.getEntryList()) {
            result.add(entry.getSubsetItem());
        }
        return result;
    }

    public static Croisements.Entry toEntry(SubsetItem subsetItem, Croisement croisement) {
        return new InternalCroisementsEntry(subsetItem, croisement);
    }

    public static CroisementChanges.Entry toEntry(SubsetItem subsetItem, CroisementChange croisementChange) {
        return new InternalCroisementChangesEntry(subsetItem, croisementChange);
    }

    public static List<CroisementsBySubset> filterCroisements(SubsetItem subsetItem, @Nullable SubsetEligibility subsetEligibility, short subsetCategory) {
        if (subsetEligibility == null) {
            subsetEligibility = EligibilityUtils.ALL_SUBSET_ELIGIBILITY;
        }
        List<CroisementsBySubset> result = new ArrayList<CroisementsBySubset>();
        Fichotheque fichotheque = subsetItem.getFichotheque();
        for (Subset subset : FichothequeUtils.getSubsetList(fichotheque, subsetCategory)) {
            if (subsetEligibility.accept(subset)) {
                List<Croisements.Entry> entryList = new ArrayList<Croisements.Entry>();
                Predicate<SubsetItem> predicate = subsetEligibility.getPredicate(subset);
                Croisements croisements = fichotheque.getCroisements(subsetItem, subset);
                for (Croisements.Entry entry : croisements.getEntryList()) {
                    SubsetItem other = entry.getSubsetItem();
                    if (predicate.test(other)) {
                        entryList.add(entry);
                    }
                }
                if (!entryList.isEmpty()) {
                    result.add(new InternalBySubset(subset, toCroisements(entryList)));
                }
            }
        }
        return result;
    }

    public static SortedMap<String, Collection<Liaison>> sortByMode(Croisements croisements) {
        SortedMap<String, SortedMap<LiaisonWeightSortKey, Liaison>> buffer = new TreeMap<String, SortedMap<LiaisonWeightSortKey, Liaison>>();
        for (Croisements.Entry entry : croisements.getEntryList()) {
            Croisement croisement = entry.getCroisement();
            SubsetItem subsetItem = entry.getSubsetItem();
            for (Lien lien : croisement.getLienList()) {
                String mode = lien.getMode();
                SortedMap<LiaisonWeightSortKey, Liaison> map = buffer.get(mode);
                if (map == null) {
                    map = new TreeMap<LiaisonWeightSortKey, Liaison>();
                    buffer.put(mode, map);
                }
                Liaison liaison = toLiaison(subsetItem, croisement, lien);
                map.put(toLiaisonWeightSortKey(liaison), liaison);
            }
        }
        SortedMap<String, Collection<Liaison>> result = new TreeMap<String, Collection<Liaison>>();
        for (Map.Entry<String, SortedMap<LiaisonWeightSortKey, Liaison>> entry : buffer.entrySet()) {
            result.put(entry.getKey(), entry.getValue().values());
        }
        return result;
    }

    public static Collection<Liaison> sortByWeight(Croisements croisements, String mode) {
        if (croisements.isEmpty()) {
            return EMPTY_LIAISONLIST;
        }
        SortedMap<LiaisonWeightSortKey, Liaison> liaisonMap = new TreeMap<LiaisonWeightSortKey, Liaison>();
        for (Croisements.Entry entry : croisements.getEntryList()) {
            Croisement croisement = entry.getCroisement();
            Lien lien = croisement.getLienByMode(mode);
            if (lien != null) {
                SubsetItem subsetItem = entry.getSubsetItem();
                Liaison liaison = toLiaison(subsetItem, croisement, lien);
                liaisonMap.put(toLiaisonWeightSortKey(liaison), liaison);
            }
        }
        return liaisonMap.values();
    }

    public static Collection<Liaison> excludeWeight(Croisements croisements, String mode, Ranges excludeWeightRanges) {
        return CroisementUtils.excludeWeight(croisements, mode, excludeWeightRanges, null);
    }

    public static Collection<Liaison> excludeWeight(Croisements croisements, String mode, Ranges excludeWeightRanges, @Nullable Predicate<SubsetItem> predicate) {
        if (croisements.isEmpty()) {
            return EMPTY_LIAISONLIST;
        }
        SortedMap<LiaisonSortKey, Liaison> liaisonMap = new TreeMap<LiaisonSortKey, Liaison>();
        for (Croisements.Entry entry : croisements.getEntryList()) {
            Croisement croisement = entry.getCroisement();
            Lien lien = croisement.getLienByMode(mode);
            if (lien != null) {
                SubsetItem subsetItem = entry.getSubsetItem();
                if (!excludeWeightRanges.contains(lien.getWeight())) {
                    if (predicate != null) {
                        if (!predicate.test(subsetItem)) {
                            continue;
                        }
                    }
                    Liaison liaison = toLiaison(subsetItem, croisement, lien);
                    liaisonMap.put(toLiaisonSortKey(liaison), liaison);
                }
            }
        }
        return liaisonMap.values();
    }

    public static Collection<Liaison> filter(Croisements croisements, IncludeMode includeMode) {
        return filter(croisements, includeMode.getMode(), includeMode.getWeightFilter());
    }

    public static Collection<Liaison> filter(Croisements croisements, String mode, int weightFilter) {
        if (weightFilter > -1) {
            return filterWithWeightFilter(croisements, mode, weightFilter, null);
        } else {
            return filter(croisements, mode);
        }
    }

    public static Collection<Liaison> filter(Croisements croisements, String mode) {
        return filterWithoutWeightFilter(croisements, mode, null);
    }

    public static Collection<Liaison> filter(Croisements croisements, IncludeMode includeMode, Predicate<SubsetItem> predicate) {
        return filter(croisements, includeMode.getMode(), includeMode.getWeightFilter(), predicate);
    }

    public static Collection<Liaison> filter(Croisements croisements, String mode, int weightFilter, Predicate<SubsetItem> predicate) {
        if (weightFilter > -1) {
            return filterWithWeightFilter(croisements, mode, weightFilter, predicate);
        } else {
            return filterWithoutWeightFilter(croisements, mode, predicate);
        }
    }

    private static Collection<Liaison> filterWithoutWeightFilter(Croisements croisements, String mode, Predicate<SubsetItem> predicate) {
        if (croisements.isEmpty()) {
            return EMPTY_LIAISONLIST;
        }
        SortedMap<LiaisonSortKey, Liaison> liaisonMap = new TreeMap<LiaisonSortKey, Liaison>();
        for (Croisements.Entry entry : croisements.getEntryList()) {
            Croisement croisement = entry.getCroisement();
            Lien lien = croisement.getLienByMode(mode);
            if (lien != null) {
                SubsetItem subsetItem = entry.getSubsetItem();
                if (predicate != null) {
                    if (!predicate.test(subsetItem)) {
                        continue;
                    }
                }
                Liaison liaison = toLiaison(subsetItem, croisement, lien);
                liaisonMap.put(toLiaisonSortKey(liaison), liaison);
            }
        }
        return liaisonMap.values();
    }

    private static Collection<Liaison> filterWithWeightFilter(Croisements croisements, String mode, int weightFilter, Predicate<SubsetItem> predicate) {
        if (croisements.isEmpty()) {
            return EMPTY_LIAISONLIST;
        }
        SortedMap<LiaisonSortKey, Liaison> liaisonMap = new TreeMap<LiaisonSortKey, Liaison>();
        for (Croisements.Entry entry : croisements.getEntryList()) {
            Croisement croisement = entry.getCroisement();
            Lien lien = croisement.getLienByMode(mode);
            if (lien != null) {
                SubsetItem subsetItem = entry.getSubsetItem();
                if (lien.getWeight() == weightFilter) {
                    if (predicate != null) {
                        if (!predicate.test(subsetItem)) {
                            continue;
                        }
                    }
                    Liaison liaison = toLiaison(subsetItem, croisement, lien);
                    liaisonMap.put(toLiaisonSortKey(liaison), liaison);
                }
            }
        }
        return liaisonMap.values();
    }

    public static boolean contains(Croisements croisements, String mode, int weightFilter) {
        if (weightFilter > -1) {
            return containsWithWeightFilter(croisements, mode, weightFilter, null);
        } else {
            return containsWithoutWeightFilter(croisements, mode, null);
        }
    }

    private static boolean containsWithWeightFilter(Croisements croisements, String mode, int weightFilter, Predicate<SubsetItem> predicate) {
        if (croisements.isEmpty()) {
            return false;
        }
        for (Croisements.Entry entry : croisements.getEntryList()) {
            Croisement croisement = entry.getCroisement();
            Lien lien = croisement.getLienByMode(mode);
            if (lien != null) {
                SubsetItem subsetItem = entry.getSubsetItem();
                if (lien.getWeight() == weightFilter) {
                    if (predicate != null) {
                        if (!predicate.test(subsetItem)) {
                            continue;
                        }
                    }
                    return true;
                }
            }
        }
        return false;
    }

    private static boolean containsWithoutWeightFilter(Croisements croisements, String mode, Predicate<SubsetItem> predicate) {
        if (croisements.isEmpty()) {
            return false;
        }
        for (Croisements.Entry entry : croisements.getEntryList()) {
            Croisement croisement = entry.getCroisement();
            Lien lien = croisement.getLienByMode(mode);
            if (lien != null) {
                SubsetItem subsetItem = entry.getSubsetItem();
                if (predicate != null) {
                    if (!predicate.test(subsetItem)) {
                        continue;
                    }
                }
                return true;
            }
        }
        return false;
    }

    public static List<Liaison> getNoList(SubsetItemPointeur subsetItemPointeur, String mode, LiageTest liageTest, Predicate<Subset> subsetAccessPredicate, Predicate<SubsetItem> fichePredicate) {
        List<Liaison> resultList = new ArrayList<Liaison>();
        Fichotheque fichotheque = subsetItemPointeur.getFichotheque();
        for (Corpus otherCorpus : fichotheque.getCorpusList()) {
            if (!subsetAccessPredicate.test(otherCorpus)) {
                continue;
            }
            Object testResult = liageTest.ownsToLiage(otherCorpus.getSubsetKey());
            if (testResult instanceof Ranges) {
                Ranges ranges = (Ranges) testResult;
                resultList.addAll(CroisementUtils.excludeWeight(subsetItemPointeur.getCroisements(otherCorpus), mode, ranges, fichePredicate));
            } else {
                Boolean bool = (Boolean) testResult;
                if (bool.equals(Boolean.TRUE)) {
                    resultList.addAll(filterWithoutWeightFilter(subsetItemPointeur.getCroisements(otherCorpus), mode, fichePredicate));
                }
            }
        }
        return resultList;
    }

    public static Lien toLien(String mode, int weight, int position1, int position2) {
        return new InternalLien(mode, weight, position1, position2);
    }

    public static Liaison toLiaison(SubsetItem subsetItem, Croisement croisement, Lien lien) {
        return new InternalLiaison(subsetItem, croisement, lien);
    }

    public static int getPosition(SubsetItem subsetItem, CroisementKey croisementKey, Lien lien) {
        int id = subsetItem.getId();
        int position;
        if ((croisementKey.getId1() == id) && (croisementKey.getSubsetKey1().equals(subsetItem.getSubsetKey()))) {
            position = lien.getPosition1();
        } else {
            position = lien.getPosition2();
        }
        return position;
    }

    public static SubsetItem getOther(SubsetItem subsetItem, CroisementKey croisementKey, Subset otherSubset) {
        int order = croisementKey.getOrder(subsetItem);
        if (order == 1) {
            return otherSubset.getSubsetItemById(croisementKey.getId2());
        } else if (order == 2) {
            return otherSubset.getSubsetItemById(croisementKey.getId1());
        } else {
            return null;
        }
    }

    public static LiaisonSortKey toLiaisonSortKey(Liaison liaison) {
        int position = liaison.getPosition();
        return new LiaisonSortKey(liaison.getSubsetItem().getId(), position);
    }

    public static LiaisonWeightSortKey toLiaisonWeightSortKey(Liaison liaison) {
        int position = liaison.getPosition();
        return new LiaisonWeightSortKey(liaison.getLien().getWeight(), liaison.getSubsetItem().getId(), position);
    }

    public static boolean areEquals(Lien lien1, Lien lien2) {
        if (lien1.getWeight() != lien2.getWeight()) {
            return false;
        }
        if (lien1.getPosition1() != lien2.getPosition1()) {
            return false;
        }
        if (lien1.getPosition2() != lien2.getPosition2()) {
            return false;
        }
        if ((!lien1.getMode().equals(lien2.getMode()))) {
            return false;
        }
        return true;
    }

    public static boolean isShouldNotCroisement(SubsetItem subsetItem, SubsetKey subsetKey) {
        Attribute attribute = subsetItem.getAttributes().getAttribute(CroisementSpace.SHOULD_NOT_KEY);
        if (attribute == null) {
            attribute = subsetItem.getAttributes().getAttribute(OLD_SHOULD_NOT_CROISEMENT_KEY);
        }
        if (attribute == null) {
            return false;
        }
        String subsetKeyString = null;
        if (subsetKey != null) {
            subsetKeyString = subsetKey.getKeyString();
        }
        int valueLength = attribute.size();
        for (int i = 0; i < valueLength; i++) {
            String val = attribute.get(i);
            if (val.equals("_all")) {
                return true;
            } else if ((subsetKeyString != null) && (val.equals(subsetKeyString))) {
                return true;
            }
        }
        return false;
    }

    public static Croisements toCroisements(Collection<Croisements.Entry> entries) {
        return new InternalCroisements(entries.toArray(new Croisements.Entry[entries.size()]));
    }

    public static List<Croisements.Entry> wrap(Croisements.Entry[] array) {
        return new CroisementsEntryList(array);
    }

    public static List<CroisementChanges.Entry> wrap(CroisementChanges.Entry[] array) {
        return new CroisementChangesEntryList(array);
    }

    public static List<Lien> wrap(Lien[] array) {
        return new LienList(array);
    }


    private static class EmptyCroisements implements Croisements {

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

        private EmptyCroisements() {
        }

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

        @Override
        public SubsetItem getFirstSubsetItem() {
            return null;
        }

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

    }


    private static class InternalLien implements Lien {

        private final String mode;
        private final int weight;
        private final int position1;
        private final int position2;

        private InternalLien(String mode, int weight, int position1, int position2) {
            this.mode = mode;
            this.weight = weight;
            this.position1 = position1;
            this.position2 = position2;

        }

        @Override
        public String getMode() {
            return mode;
        }

        @Override
        public int getPosition1() {
            return position1;
        }

        @Override
        public int getPosition2() {
            return position2;
        }

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

    }


    private static class InternalLiaison implements Liaison {

        private final SubsetItem subsetItem;
        private final Croisement croisement;
        private final Lien lien;

        private InternalLiaison(SubsetItem subsetItem, Croisement croisement, Lien lien) {
            this.subsetItem = subsetItem;
            this.croisement = croisement;
            this.lien = lien;
        }

        @Override
        public SubsetItem getSubsetItem() {
            return subsetItem;
        }

        @Override
        public Croisement getCroisement() {
            return croisement;
        }

        @Override
        public Lien getLien() {
            return lien;
        }

    }


    private static class InternalCroisements implements Croisements {

        private final Croisements.Entry[] array;
        private final List<Croisements.Entry> entryList;

        private InternalCroisements(Croisements.Entry[] array) {
            this.array = array;
            this.entryList = wrap(array);
        }

        @Override
        public boolean isEmpty() {
            return (array.length == 0);
        }

        @Override
        public SubsetItem getFirstSubsetItem() {
            if (array.length > 0) {
                return array[0].getSubsetItem();
            } else {
                return null;
            }
        }

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

    }


    private static class InternalCroisementsEntry implements Croisements.Entry {

        private final SubsetItem subsetItem;
        private final Croisement croisement;

        private InternalCroisementsEntry(SubsetItem subsetItem, Croisement croisement) {
            this.subsetItem = subsetItem;
            this.croisement = croisement;
        }

        @Override
        public SubsetItem getSubsetItem() {
            return subsetItem;
        }

        @Override
        public Croisement getCroisement() {
            return croisement;
        }

    }


    private static class CroisementsEntryList extends AbstractList<Croisements.Entry> implements RandomAccess {

        private final Croisements.Entry[] array;

        private CroisementsEntryList(Croisements.Entry[] array) {
            this.array = array;
        }

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

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

    }


    private static class InternalCroisementChangesEntry implements CroisementChanges.Entry {

        private final SubsetItem subsetItem;
        private final CroisementChange croisementChange;

        private InternalCroisementChangesEntry(SubsetItem subsetItem, CroisementChange croisementChange) {
            this.subsetItem = subsetItem;
            this.croisementChange = croisementChange;
        }

        @Override
        public SubsetItem getSubsetItem() {
            return subsetItem;
        }

        @Override
        public CroisementChange getCroisementChange() {
            return croisementChange;
        }

    }


    private static class CroisementChangesEntryList extends AbstractList<CroisementChanges.Entry> implements RandomAccess {

        private final CroisementChanges.Entry[] array;

        private CroisementChangesEntryList(CroisementChanges.Entry[] array) {
            this.array = array;
        }

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

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

    }


    private static class LienList extends AbstractList<Lien> implements RandomAccess {

        private final Lien[] array;

        private LienList(Lien[] array) {
            this.array = array;
        }

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

        @Override
        public Lien get(int index) {
            return array[index];
        }

    }


    private static class InternalBySubset implements CroisementsBySubset {

        private final Subset subset;
        private final Croisements croisements;

        private InternalBySubset(Subset subset, Croisements croisements) {
            this.subset = subset;
            this.croisements = croisements;
        }

        @Override
        public Subset getSubset() {
            return subset;
        }

        @Override
        public Croisements getCroisements() {
            return croisements;
        }

    }

}
