/* BdfServer - Copyright (c) 2015-2022 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.subsettree;

import net.fichotheque.permission.PermissionSummary;
import fr.exemole.bdfserver.api.subsettree.CroisementSubsetNode;
import fr.exemole.bdfserver.api.subsettree.GroupNode;
import fr.exemole.bdfserver.api.subsettree.SubsetNode;
import fr.exemole.bdfserver.api.subsettree.SubsetTree;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import net.fichotheque.Fichotheque;
import net.fichotheque.Subset;
import net.fichotheque.SubsetItem;
import net.fichotheque.SubsetKey;
import net.fichotheque.croisement.Croisements;
import net.fichotheque.eligibility.SubsetEligibility;


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

    private TreeFilterEngine() {

    }

    public static SubsetTree admin(PermissionSummary permissionSummary, SubsetTree subsetTree) {
        if (isUnchanged(permissionSummary, subsetTree)) {
            return subsetTree;
        }
        return filterSubsetTree(new AdminTest(permissionSummary), subsetTree);
    }

    public static SubsetTree read(PermissionSummary permissionSummary, SubsetTree subsetTree) {
        if (isUnchanged(permissionSummary, subsetTree)) {
            return subsetTree;
        }
        return filterSubsetTree(new ReadTest(permissionSummary), subsetTree);
    }

    public static SubsetTree selection(Set<SubsetKey> subsetKeySet, SubsetTree subsetTree) {
        if (subsetTree.getNodeList().isEmpty()) {
            return subsetTree;
        }
        Test test = new SelectionTest(subsetKeySet);
        return filterSubsetTree(test, subsetTree);
    }

    public static SubsetTree selection(SubsetEligibility subsetEligibility, SubsetTree subsetTree) {
        if (subsetTree.getNodeList().isEmpty()) {
            return subsetTree;
        }
        Test test = new SubsetEligibilityTest(subsetEligibility);
        return filterSubsetTree(test, subsetTree);
    }

    public static SubsetTree croisement(SubsetItem subsetItem, SubsetTree subsetTree) {
        CroisementTreeBuilder croisementTreeBuilder = new CroisementTreeBuilder(subsetItem);
        return croisementTreeBuilder.filter(subsetTree);
    }

    private static boolean isUnchanged(PermissionSummary permissionSummary, SubsetTree subsetTree) {
        if (subsetTree.getNodeList().isEmpty()) {
            return true;
        }
        if (permissionSummary.isFichothequeAdmin()) {
            return true;
        }
        return false;
    }

    private static SubsetTree filterSubsetTree(Test test, SubsetTree origin) {
        List<SubsetTree.Node> destinationList = new ArrayList<SubsetTree.Node>();
        for (SubsetTree.Node node : origin.getNodeList()) {
            if (node instanceof SubsetNode) {
                SubsetNode subsetNode = (SubsetNode) node;
                if (test.contains(subsetNode.getSubsetKey())) {
                    destinationList.add(subsetNode);
                }
            } else if (node instanceof GroupNode) {
                GroupNode result = filterGroupNode(test, (GroupNode) node);
                if (!result.getSubnodeList().isEmpty()) {
                    destinationList.add(result);
                }
            }
        }
        return SubsetTreeBuilder.build(destinationList);
    }

    private static GroupNode filterGroupNode(Test test, GroupNode origin) {
        GroupNodeBuilder builder = new GroupNodeBuilder(origin.getName(), true);
        for (SubsetTree.Node subnode : origin.getSubnodeList()) {
            if (subnode instanceof SubsetNode) {
                SubsetNode subsetNode = (SubsetNode) subnode;
                if (test.contains(subsetNode.getSubsetKey())) {
                    builder.addSubsetNode(subsetNode);
                }
            } else if (subnode instanceof GroupNode) {
                GroupNode result = filterGroupNode(test, (GroupNode) subnode);
                if (!result.getSubnodeList().isEmpty()) {
                    builder.addGroupNode(result);
                }
            }
        }
        return builder.toGroupNode();
    }


    private static abstract class Test {

        public abstract boolean contains(SubsetKey subsetKey);

    }


    private static class ReadTest extends Test {

        private final PermissionSummary permissionSummary;

        private ReadTest(PermissionSummary permissionSummary) {
            this.permissionSummary = permissionSummary;
        }

        @Override
        public boolean contains(SubsetKey subsetKey) {
            return permissionSummary.hasAccess(subsetKey);
        }

    }


    private static class AdminTest extends Test {

        private final PermissionSummary permissionSummary;

        private AdminTest(PermissionSummary permissionSummary) {
            this.permissionSummary = permissionSummary;
        }

        @Override
        public boolean contains(SubsetKey subsetKey) {
            return (permissionSummary.isSubsetAdmin(subsetKey));
        }

    }


    private static class SelectionTest extends Test {

        private final Set<SubsetKey> subsetKeySet;

        private SelectionTest(Set<SubsetKey> subsetKeySet) {
            this.subsetKeySet = subsetKeySet;
        }

        @Override
        public boolean contains(SubsetKey subsetKey) {
            return subsetKeySet.contains(subsetKey);
        }

    }


    private static class SubsetEligibilityTest extends Test {

        private final SubsetEligibility subsetEligibility;

        private SubsetEligibilityTest(SubsetEligibility subsetEligibility) {
            this.subsetEligibility = subsetEligibility;
        }

        @Override
        public boolean contains(SubsetKey subsetKey) {
            return subsetEligibility.accept(subsetKey);
        }

    }


    private static class CroisementTreeBuilder {

        private final SubsetItem subsetItem;
        private final Fichotheque fichotheque;

        private CroisementTreeBuilder(SubsetItem subsetItem) {
            this.subsetItem = subsetItem;
            this.fichotheque = subsetItem.getFichotheque();
        }

        private SubsetTree filter(SubsetTree subsetTree) {
            SubsetTreeBuilder subsetTreeBuilder = new SubsetTreeBuilder(true);
            filter(subsetTreeBuilder, subsetTree.getNodeList());
            return subsetTreeBuilder.toSubsetTree();
        }

        private void filter(NodeListBuilder nodeListBuilder, List<SubsetTree.Node> nodeList) {
            for (SubsetTree.Node node : nodeList) {
                if (node instanceof SubsetNode) {
                    CroisementSubsetNode croisementSubsetNode = convert((SubsetNode) node);
                    if (croisementSubsetNode != null) {
                        nodeListBuilder.addSubsetNode(croisementSubsetNode);
                    }
                } else if (node instanceof GroupNode) {
                    GroupNode groupNode = (GroupNode) node;
                    GroupNodeBuilder builder = new GroupNodeBuilder(groupNode.getName(), true);
                    filter(builder, groupNode.getSubnodeList());
                    nodeListBuilder.addGroupNode(builder.toGroupNode());
                }
            }
        }

        private CroisementSubsetNode convert(SubsetNode subsetNode) {
            Subset subset = fichotheque.getSubset(subsetNode.getSubsetKey());
            if (subset == null) {
                return null;
            }
            Croisements croisements = fichotheque.getCroisements(subsetItem, subset);
            if (croisements.isEmpty()) {
                return null;
            }
            return CroisementSubsetNodeBuilder.build(subset, croisements);
        }

    }

}
