/* 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.eligibility.SubsetEligibility;
import net.fichotheque.include.IncludeMode;
import net.fichotheque.include.LiageTest;
import net.fichotheque.junction.Junction;
import net.fichotheque.junction.JunctionChange;
import net.fichotheque.junction.JunctionChanges;
import net.fichotheque.junction.JunctionKey;
import net.fichotheque.junction.Junctions;
import net.fichotheque.junction.JunctionsBySubset;
import net.fichotheque.junction.Liaison;
import net.fichotheque.junction.LiaisonSortKey;
import net.fichotheque.junction.LiaisonWeightSortKey;
import net.fichotheque.junction.Tie;
import net.fichotheque.namespaces.JunctionSpace;
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.Attributes;
import net.mapeadores.util.attr.CheckedNameSpace;
import net.mapeadores.util.primitives.Ranges;


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

    public final static Junctions EMPTY_JUNCTIONS = new EmptyJunctions();
    public final static List<Liaison> EMPTY_LIAISONLIST = Collections.emptyList();
    public final static Collection<Liaison> EMPTY_LIAISONCOLLECTION = Collections.emptyList();
    public final static List<Tie> EMPTY_TIELIST = Collections.emptyList();
    public final static List<String> EMPTY_REMOVEDMODELIST = Collections.emptyList();
    private final static AttributeKey OLD_SHOULD_NOT_JUNCTION_KEY = AttributeKey.build(CheckedNameSpace.build("croisement"), "should_not");
    private final static AttributeKey VERY_OLD_SHOULD_NOT_JUNCTION_KEY = AttributeKey.build(CheckedNameSpace.build("bdf.croisement"), "should_not");


    private JunctionUtils() {
    }

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

    public static Junctions.Entry toEntry(SubsetItem subsetItem, Junction junction) {
        return new InternalJunctionsEntry(subsetItem, junction);
    }

    public static JunctionChanges.Entry toEntry(SubsetItem subsetItem, JunctionChange junctionChange) {
        return new InternalJunctionChangesEntry(subsetItem, junctionChange);
    }

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

    public static SortedMap<String, Collection<Liaison>> sortByMode(Junctions junctions) {
        SortedMap<String, SortedMap<LiaisonWeightSortKey, Liaison>> buffer = new TreeMap<String, SortedMap<LiaisonWeightSortKey, Liaison>>();
        for (Junctions.Entry entry : junctions.getEntryList()) {
            Junction junction = entry.getJunction();
            SubsetItem subsetItem = entry.getSubsetItem();
            for (Tie tie : junction.getTieList()) {
                String mode = tie.getMode();
                SortedMap<LiaisonWeightSortKey, Liaison> map = buffer.get(mode);
                if (map == null) {
                    map = new TreeMap<LiaisonWeightSortKey, Liaison>();
                    buffer.put(mode, map);
                }
                Liaison liaison = toLiaison(subsetItem, junction, tie);
                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(Junctions junctions, String mode) {
        if (junctions.isEmpty()) {
            return EMPTY_LIAISONLIST;
        }
        SortedMap<LiaisonWeightSortKey, Liaison> liaisonMap = new TreeMap<LiaisonWeightSortKey, Liaison>();
        for (Junctions.Entry entry : junctions.getEntryList()) {
            Junction junction = entry.getJunction();
            Tie tie = junction.getTieByMode(mode);
            if (tie != null) {
                SubsetItem subsetItem = entry.getSubsetItem();
                Liaison liaison = toLiaison(subsetItem, junction, tie);
                liaisonMap.put(toLiaisonWeightSortKey(liaison), liaison);
            }
        }
        return liaisonMap.values();
    }

    public static Collection<Liaison> excludeWeight(Junctions junctions, String mode, Ranges excludeWeightRanges) {
        return JunctionUtils.excludeWeight(junctions, mode, excludeWeightRanges, null);
    }

    public static Collection<Liaison> excludeWeight(Junctions junctions, String mode, Ranges excludeWeightRanges, @Nullable Predicate<SubsetItem> predicate) {
        if (junctions.isEmpty()) {
            return EMPTY_LIAISONLIST;
        }
        SortedMap<LiaisonSortKey, Liaison> liaisonMap = new TreeMap<LiaisonSortKey, Liaison>();
        for (Junctions.Entry entry : junctions.getEntryList()) {
            Junction junction = entry.getJunction();
            Tie tie = junction.getTieByMode(mode);
            if (tie != null) {
                SubsetItem subsetItem = entry.getSubsetItem();
                if (!excludeWeightRanges.contains(tie.getWeight())) {
                    if (predicate != null) {
                        if (!predicate.test(subsetItem)) {
                            continue;
                        }
                    }
                    Liaison liaison = toLiaison(subsetItem, junction, tie);
                    liaisonMap.put(toLiaisonSortKey(liaison), liaison);
                }
            }
        }
        return liaisonMap.values();
    }

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

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

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

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

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

    private static Collection<Liaison> filterWithoutWeightFilter(Junctions junctions, String mode, Predicate<SubsetItem> predicate) {
        if (junctions.isEmpty()) {
            return EMPTY_LIAISONLIST;
        }
        SortedMap<LiaisonSortKey, Liaison> liaisonMap = new TreeMap<LiaisonSortKey, Liaison>();
        for (Junctions.Entry entry : junctions.getEntryList()) {
            Junction junction = entry.getJunction();
            Tie tie = junction.getTieByMode(mode);
            if (tie != null) {
                SubsetItem subsetItem = entry.getSubsetItem();
                if (predicate != null) {
                    if (!predicate.test(subsetItem)) {
                        continue;
                    }
                }
                Liaison liaison = toLiaison(subsetItem, junction, tie);
                liaisonMap.put(toLiaisonSortKey(liaison), liaison);
            }
        }
        return liaisonMap.values();
    }

    private static Collection<Liaison> filterWithWeightFilter(Junctions junctions, String mode, int weightFilter, Predicate<SubsetItem> predicate) {
        if (junctions.isEmpty()) {
            return EMPTY_LIAISONLIST;
        }
        SortedMap<LiaisonSortKey, Liaison> liaisonMap = new TreeMap<LiaisonSortKey, Liaison>();
        for (Junctions.Entry entry : junctions.getEntryList()) {
            Junction junction = entry.getJunction();
            Tie tie = junction.getTieByMode(mode);
            if (tie != null) {
                SubsetItem subsetItem = entry.getSubsetItem();
                if (tie.getWeight() == weightFilter) {
                    if (predicate != null) {
                        if (!predicate.test(subsetItem)) {
                            continue;
                        }
                    }
                    Liaison liaison = toLiaison(subsetItem, junction, tie);
                    liaisonMap.put(toLiaisonSortKey(liaison), liaison);
                }
            }
        }
        return liaisonMap.values();
    }

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

    private static boolean containsWithWeightFilter(Junctions junctions, String mode, int weightFilter, Predicate<SubsetItem> predicate) {
        if (junctions.isEmpty()) {
            return false;
        }
        for (Junctions.Entry entry : junctions.getEntryList()) {
            Junction junction = entry.getJunction();
            Tie tie = junction.getTieByMode(mode);
            if (tie != null) {
                SubsetItem subsetItem = entry.getSubsetItem();
                if (tie.getWeight() == weightFilter) {
                    if (predicate != null) {
                        if (!predicate.test(subsetItem)) {
                            continue;
                        }
                    }
                    return true;
                }
            }
        }
        return false;
    }

    private static boolean containsWithoutWeightFilter(Junctions junctions, String mode, Predicate<SubsetItem> predicate) {
        if (junctions.isEmpty()) {
            return false;
        }
        for (Junctions.Entry entry : junctions.getEntryList()) {
            Junction junction = entry.getJunction();
            Tie tie = junction.getTieByMode(mode);
            if (tie != 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(JunctionUtils.excludeWeight(subsetItemPointeur.getJunctions(otherCorpus), mode, ranges, fichePredicate));
            } else {
                Boolean bool = (Boolean) testResult;
                if (bool.equals(Boolean.TRUE)) {
                    resultList.addAll(filterWithoutWeightFilter(subsetItemPointeur.getJunctions(otherCorpus), mode, fichePredicate));
                }
            }
        }
        return resultList;
    }

    public static Tie toTie(String mode, int weight, int position1, int position2) {
        return new InternalTie(mode, weight, position1, position2);
    }

    public static Liaison toLiaison(SubsetItem subsetItem, Junction junction, Tie tie) {
        return new InternalLiaison(subsetItem, junction, tie);
    }

    public static int getPosition(SubsetItem subsetItem, JunctionKey junctionKey, Tie tie) {
        int id = subsetItem.getId();
        int position;
        if ((junctionKey.getId1() == id) && (junctionKey.getSubsetKey1().equals(subsetItem.getSubsetKey()))) {
            position = tie.getPosition1();
        } else {
            position = tie.getPosition2();
        }
        return position;
    }

    public static SubsetItem getOther(SubsetItem subsetItem, JunctionKey junctionKey, Subset otherSubset) {
        int order = junctionKey.getOrder(subsetItem);
        if (order == 1) {
            return otherSubset.getSubsetItemById(junctionKey.getId2());
        } else if (order == 2) {
            return otherSubset.getSubsetItemById(junctionKey.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.getTie().getWeight(), liaison.getSubsetItem().getId(), position);
    }

    public static boolean areEquals(Tie tie1, Tie tie2) {
        if (tie1.getWeight() != tie2.getWeight()) {
            return false;
        }
        if (tie1.getPosition1() != tie2.getPosition1()) {
            return false;
        }
        if (tie1.getPosition2() != tie2.getPosition2()) {
            return false;
        }
        if ((!tie1.getMode().equals(tie2.getMode()))) {
            return false;
        }
        return true;
    }

    public static boolean isShouldNotJunction(SubsetItem subsetItem, SubsetKey subsetKey) {
        Attributes attributes = subsetItem.getAttributes();
        Attribute attribute = attributes.getAttribute(JunctionSpace.SHOULD_NOT_KEY);
        if (attribute == null) {
            attribute = attributes.getAttribute(OLD_SHOULD_NOT_JUNCTION_KEY);
            if (attribute == null) {
                attribute = subsetItem.getAttributes().getAttribute(VERY_OLD_SHOULD_NOT_JUNCTION_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 Junctions toJunctions(Collection<Junctions.Entry> entries) {
        return new InternalJunctions(entries.toArray(new Junctions.Entry[entries.size()]));
    }

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

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

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


    private static class EmptyJunctions implements Junctions {

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

        private EmptyJunctions() {
        }

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

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

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

    }


    private static class InternalTie implements Tie {

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

        private InternalTie(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 Junction junction;
        private final Tie tie;

        private InternalLiaison(SubsetItem subsetItem, Junction junction, Tie tie) {
            this.subsetItem = subsetItem;
            this.junction = junction;
            this.tie = tie;
        }

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

        @Override
        public Junction getJunction() {
            return junction;
        }

        @Override
        public Tie getTie() {
            return tie;
        }

    }


    private static class InternalJunctions implements Junctions {

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

        private InternalJunctions(Junctions.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 InternalJunctionsEntry implements Junctions.Entry {

        private final SubsetItem subsetItem;
        private final Junction junction;

        private InternalJunctionsEntry(SubsetItem subsetItem, Junction junction) {
            this.subsetItem = subsetItem;
            this.junction = junction;
        }

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

        @Override
        public Junction getJunction() {
            return junction;
        }

    }


    private static class JunctionsEntryList extends AbstractList<Junctions.Entry> implements RandomAccess {

        private final Junctions.Entry[] array;

        private JunctionsEntryList(Junctions.Entry[] array) {
            this.array = array;
        }

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

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

    }


    private static class InternalJunctionChangesEntry implements JunctionChanges.Entry {

        private final SubsetItem subsetItem;
        private final JunctionChange junctionChange;

        private InternalJunctionChangesEntry(SubsetItem subsetItem, JunctionChange junctionChange) {
            this.subsetItem = subsetItem;
            this.junctionChange = junctionChange;
        }

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

        @Override
        public JunctionChange getJunctionChange() {
            return junctionChange;
        }

    }


    private static class JunctionChangesEntryList extends AbstractList<JunctionChanges.Entry> implements RandomAccess {

        private final JunctionChanges.Entry[] array;

        private JunctionChangesEntryList(JunctionChanges.Entry[] array) {
            this.array = array;
        }

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

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

    }


    private static class TieList extends AbstractList<Tie> implements RandomAccess {

        private final Tie[] array;

        private TieList(Tie[] array) {
            this.array = array;
        }

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

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

    }


    private static class InternalBySubset implements JunctionsBySubset {

        private final Subset subset;
        private final Junctions junctions;

        private InternalBySubset(Subset subset, Junctions junctions) {
            this.subset = subset;
            this.junctions = junctions;
        }

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

        @Override
        public Junctions getJunctions() {
            return junctions;
        }

    }

}
