package eu.dnetlib.validator2.engine.builtins;

import eu.dnetlib.validator2.engine.Helper;
import eu.dnetlib.validator2.engine.contexts.NodeListAction;
import eu.dnetlib.validator2.engine.contexts.NodeListActionProperty;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Predicate;

class StandardNodeListActionProperty extends StandardRuleProperty implements NodeListActionProperty {

    private static final Map<String, NamedNodeListAction> SUPPORTED_ACTIONS = new HashMap<>();

    static {
        Arrays.asList(new CUSTOM(), new ALL(), new ANY(), new NONE(), new ONE(), new LENGTH()).forEach(action ->
                SUPPORTED_ACTIONS.put(action.name, action)
        );
    }

    private NamedNodeListAction action;

    StandardNodeListActionProperty(String name) {
        super(name);
    }

    @Override
    public String getValue() {
        return action == null ? null : action.name;
    }

    @Override
    public void setValue(String value) throws IllegalArgumentException {
        NamedNodeListAction action = SUPPORTED_ACTIONS.get(value);
        if (action == null) throw new IllegalArgumentException("Invalid node list action: " + value);
        this.action = action;
    }

    @Override
    public boolean test(NodeList nodes, Predicate<?> predicate) throws IllegalStateException {
        if (action == null) throw new IllegalStateException("Node list action property is empty");
        return action.test(nodes, predicate);
    }

    private static abstract class NamedNodeListAction implements NodeListAction {
        final String name;
        NamedNodeListAction(String name) {
            this.name = name;
        }
    }

    private static class CUSTOM extends NamedNodeListAction {

        CUSTOM() { super("custom"); }

        @Override
        public boolean test(NodeList nodes, Predicate<?> predicate) throws IllegalStateException {
            throw new IllegalStateException("Configuration error: a custom node list action was expected");
        }
    }

    private static class ALL extends NamedNodeListAction {

        ALL() {
            super("all");
        }

        @SuppressWarnings("unchecked")
        @Override
        public boolean test(NodeList nodes, Predicate<?> appliesToAllNodes) throws IllegalStateException {
            return Helper.predicateSucceedsForAllNodes(
                nodes,
                Node::getNodeValue,
                (Predicate<String>)appliesToAllNodes
            );
        }
    }

    private static class ANY extends NamedNodeListAction {

        ANY() {
            super(">0");
        }

        @SuppressWarnings("unchecked")
        @Override
        public boolean test(NodeList nodes, Predicate<?> applyToAtLeastOne) throws IllegalStateException {
            return Helper.predicateSucceedsForAtLeastOneNode(
                nodes,
                Node::getNodeValue,
                (Predicate<String>)applyToAtLeastOne
            );
        }
    }

    private static class NONE extends NamedNodeListAction { // Success 0

        NONE() {
            super("0");
        }

        @SuppressWarnings("unchecked")
        @Override
        public boolean test(NodeList nodes, Predicate<?> applyToNone) throws IllegalStateException {
            return Helper.predicateFailsForAllNodes(
                nodes,
                Node::getNodeValue,
                (Predicate<String>)applyToNone
            );
        }
    }

    private static class ONE extends NamedNodeListAction { // Success 1

        ONE() {
            super("1");
        }

        @SuppressWarnings("unchecked")
        @Override
        public boolean test(NodeList nodes, Predicate<?> applyToExactlyOne) throws IllegalStateException {
            return Helper.predicateSucceedsForExactlyOneNode(
                nodes,
                Node::getNodeValue,
                (Predicate<String>) applyToExactlyOne
            );
        }
    }

    private static class LENGTH extends NamedNodeListAction {
        LENGTH() {
            super("length");
        }

        @SuppressWarnings("unchecked")
        @Override
        public boolean test(NodeList nodes, Predicate<?> lengthInRange) throws IllegalStateException {
            return lengthOfNodesMatches(nodes, (Predicate<Integer>)lengthInRange);
        }
    }

    private static boolean lengthOfNodesMatches(NodeList nodes, Predicate<Integer> predicate) {
        return predicate.test(nodes.getLength());
    }

}
