class FeatureCheck(Check,
                   namedtuple('_FeatureCheck',
                              ['what', 'how', 'check', 'expected'])):

    _instances = dict()

    def __new__(cls, what, how):
        if not isinstance(how, FieldConstraint):
            how = L(how)

        check_function = cls.get_check_function(how, what)

        key = (what, check_function.key_a, check_function.key_b)

        if key not in cls._instances:
            cls._instances[key] = super(Check, cls).__new__(
                cls,
                what,
                how,
                check_function.check,
                check_function.expected)

        return cls._instances[key]

    def __call__(self, data, is_fact=True):
        if is_fact:
            if isinstance(self.what, str):
                record = data
                if (not self.what.startswith('__')
                        and not self.what.endswith('__')):
                    try:
                        for p in self.what.split('__'):
                            if p.isnumeric():
                                p = int(p)
                            record = record[p]
                    except (IndexError, KeyError, TypeError):
                        return False
            else:
                try:
                    record = data[self.what]
                except (IndexError, KeyError, TypeError):
                    return False
        else:
            record = data

        res = self.check(record, self.expected)

        log = MATCH.info if res else MATCH.debug
        log("what=%r, how=%r, fact=%s = %r", self.what, self.how, data, res)

        return res

    def __str__(self):  # pragma: no cover
        return "%s (%s) %s" % (self.what, self.check.__name__, self.expected)

    @singledispatch
    @staticmethod
    def get_check_function(pce, what=None):
        raise TypeError("Unknown FieldConstraint type.")

    @get_check_function.register(L)
    def _(pce, what=None):
        def equal_literal(actual, expected):
            if expected.value == actual:
                if expected.__bind__ is None:
                    return True
                else:
                    return {expected.__bind__: actual}
            else:
                return False

        return CheckFunction(key_a=L,
                             key_b=(pce.value, pce.__bind__),
                             expected=pce,
                             check=equal_literal)

    @get_check_function.register(P)
    def _(pce, what=None):
        def match_predicate(actual, expected):
            if expected.match(actual):
                if expected.__bind__ is None:
                    return True
                else:
                    return {expected.__bind__: actual}
            else:
                return False

        key_b = ('ID',
                 id(pce.match),
                 pce.__bind__)

        return CheckFunction(key_a=P,
                             key_b=key_b,
                             expected=pce,
                             check=match_predicate)

    @get_check_function.register(W)
    def _(pce, what=None):
        def wildcard_match(actual, expected):
            if expected.__bind__ is None:
                return True
            else:
                return {expected.__bind__: actual}

        return CheckFunction(key_a=W,
                             key_b=pce.__bind__,
                             expected=pce,
                             check=wildcard_match)

    @get_check_function.register(NOTFC)
    def _(pce, what=None):
        def not_equal(actual, expected):
            subresult = expected(actual, is_fact=False)
            if isinstance(subresult, Mapping):
                newresult = {(False, k): v
                             for k, v in subresult.items()}
                return newresult
            else:
                return not subresult

        key_b = FeatureCheck(what, pce[0])

        return CheckFunction(key_a=NOTFC,
                             key_b=key_b,
                             expected=key_b,
                             check=not_equal)

    @get_check_function.register(ANDFC)
    def _(pce, what=None):
        def and_match(actual, expected):
            value = dict()
            for subcheck in expected:
                subres = subcheck(actual, is_fact=False)
                if subres is False:
                    break
                elif subres is True:
                    pass
                elif isinstance(subres, Mapping):
                    value.update(subres)
                else:
                    raise TypeError('Bad check value.')
            else:
                if not value:
                    return True
                else:
                    return value
            return False

        key_b = tuple(FeatureCheck(what, x) for x in pce)

        return CheckFunction(key_a=ANDFC,
                             key_b=key_b,
                             expected=key_b,
                             check=and_match)

    @get_check_function.register(ORFC)
    def _(pce, what=None):
        def or_match(actual, expected):
            for subcheck in expected:
                subres = subcheck(actual, is_fact=False)
                if subres:
                    return subres
            else:
                return False

        key_b = tuple(FeatureCheck(what, x) for x in pce)

        return CheckFunction(key_a=ORFC,
                             key_b=key_b,
                             expected=key_b,
                             check=or_match) class TypeCheck(Check, namedtuple('_TypeCheck', ['fact_type'])):

    _instances = dict()

    def __new__(cls, fact_type):
        if fact_type not in cls._instances:
            cls._instances[fact_type] = super().__new__(cls, fact_type)
        return cls._instances[fact_type]

    def __call__(self, fact):
        res = type(fact) == self.fact_type

        log = MATCH.info if res else MATCH.debug
        log("type(%s) == %s = %r",
            fact, self.fact_type.__name__, res)

        return res

    def __str__(self):  # pragma: no cover
        return "type() == %s" % self.fact_type.__name__ class FactCapture(Check, namedtuple('_FactCapture', ['bind'])):

    _instances = dict()

    def __new__(cls, bind):
        if bind not in cls._instances:
            cls._instances[bind] = super().__new__(cls, bind)
        return cls._instances[bind]

    @property
    def __bind__(self):
        return self.bind

    def __call__(self, fact):
        MATCH.info("%r <= %s", self.__bind__, fact)
        return {self.__bind__: fact}

    def __str__(self):  # pragma: no cover
        return "%s <= <Fact>" % (self.__bind__) class SameContextCheck(Check):
    def __call__(self, l, r):
        for key, value in l.items():
            if key[0] is False:
                raise RuntimeError(
                    'Negated value "%s" present before capture.' % key[1])
            else:
                if key in r and value != r[key]:
                    return False
                if (False, key) in r and value == r[(False, key)]:
                    return False
        else:
            return True class WhereCheck(Check, namedtuple('_WhereCheck', ['test'])):

    _instances = dict()

    def __new__(cls, test):
        if test not in cls._instances:
            obj = super().__new__(cls, test)
            obj.parameters = inspect.signature(test).parameters.keys()
            cls._instances[test] = obj

        return cls._instances[test]

    def __call__(self, context):
        parameters = {k: context.get(k) for k in self.parameters}
        res = self.test(**parameters)

        log = MATCH.info if res else MATCH.debug
        log("TEST %r(%r) == %r", self.test, parameters, res)

        return res @singledispatch
def dnf(exp):
    return exp class ConflictSetNode(mixins.AnyChild,
                      OneInputNode):
    """
    Conflict Set Change Node.

    This node is the final step in the network. Any token activating
    this node will produce an activation (VALID token) or deactivation
    (INVALID token) of the internal `rule` with the token context and
    facts.
    """

    def __init__(self, rule):
        """Initialize the node with the given `rule`."""
        try:
            assert isinstance(rule, Rule)
        except AssertionError as exc:
            raise TypeError(exc) from exc
        else:
            self.rule = rule

        self.added = set()
        self.removed = set()

        super().__init__()

    def _reset(self):
        """Wipe the node internal memory."""
        self.memory = set()

    def _activate(self, token):
        """Activate this node for the given token."""

        info = token.to_info()

        activation = Activation(
            self.rule,
            frozenset(info.data),
            {k: v for k, v in info.context if isinstance(k, str)})

        if token.is_valid():
            if info not in self.memory:
                self.memory.add(info)
                if activation in self.removed:
                    self.removed.remove(activation)
                else:
                    self.added.add(activation)
        else:
            try:
                self.memory.remove(info)
            except ValueError:
                pass
            else:
                if activation in self.added:
                    self.added.remove(activation)
                else:
                    self.removed.add(activation)

    def get_activations(self):
        """Return a list of activations."""
        res = (self.added, self.removed)

        self.added = set()
        self.removed = set()

        return res

    def __str__(self):  # pragma: no cover
        return "%s: %s" % (self.__class__.__name__, self.rule.__name__) class NotNode(mixins.AnyChild,
              mixins.HasMatcher,
              TwoInputNode):
    """
    Not Node.

    This is a special kind of node representing the absence of some
    fact/condition.

    This node is similar to `OrdinaryMatchNode` in the sense it has two
    input ports and try to match tokens arriving in both of them. But
    pass VALID tokens to the children when no matches are found and
    INVALID tokens when they are.
    """

    def _reset(self):
        """Wipe node internal memory."""
        self.left_memory = dict()
        self.right_memory = list()

    def _activate_left(self, token):
        """
        Activate from the left.

        In case of a valid token this activations test the right memory
        with the given token and looks for the number of matches. The
        token and the number of occurences are stored in the left
        memory.

        If the number of matches is zero the token activates all children.

        """
        if not self.right_memory:
            if token.is_valid():
                self.left_memory[token.to_info()] = 0
            else:
                del self.left_memory[token.to_info()]

            for child in self.children:
                child.callback(token)

        elif token.is_valid():
            count = 0

            for _, right_context in self.right_memory:
                if self.matcher(token.context, dict(right_context)):
                    count += 1

            if count == 0:
                for child in self.children:
                    child.callback(token)

            self.left_memory[token.to_info()] = count
        else:
            count = 0
            for _, right_context in self.right_memory:
                if self.matcher(token.context, dict(right_context)):
                    count += 1
                    break

            if count == 0:
                for child in self.children:
                    child.callback(token)

            del self.left_memory[token.to_info()]

    def _activate_right(self, token):
        """
        Activate from the right.

        Go over the left memory and find matching data, when found
        update the counter (substracting if the given token is invalid
        and adding otherwise). Depending on the result of this operation
        a new token is generated and passing to all children.

        """
        if token.is_valid():
            self.right_memory.append(token.to_info())
            inc = 1
        else:
            inc = -1
            self.right_memory.remove(token.to_info())

        for left in self.left_memory:
            if self.matcher(dict(left.context), token.context):
                self.left_memory[left] += inc
                newcount = self.left_memory[left]
                if (newcount == 0 and inc == -1) or \
                        (newcount == 1 and inc == 1):
                    if inc == -1:
                        newtoken = left.to_valid_token()
                    else:
                        newtoken = left.to_invalid_token()

                    for child in self.children:
                        child.callback(newtoken) class OrdinaryMatchNode(mixins.AnyChild,
                        mixins.HasMatcher,
                        TwoInputNode):
    """
    Ordinary Two-input Node.

    This kind of node receive tokens at two ports (left and right) and
    try to match them.

    The matching function is a callable given as a parameter to __init__
    and stored internally. This functions will receive two contexts, one
    from the left and other from the right, and decides if they match
    together (returning True or False).

    Matching pairs will be combined in one token containing facts from
    both and a combined context. This combined tokens will be sent to
    all children.
    """

    def _reset(self):
        """Wipe node memory."""
        self.left_memory = list()
        self.right_memory = list()

    def __activation(self, token, branch_memory, matching_memory,
                     is_left=True):
        """
        Node activation internal function.

        This is a generalization of both activation functions.

        The given token is added or removed from `branch_memory`
        depending of its tag.

        For any other data in `matching_memory` the match function will
        be called and if a match occurs a new token will be produced and
        sent to all children.

        """
        if token.is_valid():
            branch_memory.append(token.to_info())
        else:
            with suppress(ValueError):
                branch_memory.remove(token.to_info())

        for other_data, other_context in matching_memory:
            other_context = dict(other_context)
            if is_left:
                left_context = token.context
                right_context = other_context
            else:
                left_context = other_context
                right_context = token.context

            match = self.matcher(left_context, right_context)

            if match:
                MATCH.info("%s (%s | %s) = True",
                           self.__class__.__name__,
                           left_context,
                           right_context)

                newcontext = {k: v for k, v in token.context.items()
                              if isinstance(k, str)}

                for k, v in other_context.items():
                    if not isinstance(k, tuple):
                        # Negated value are not needed any further
                        newcontext[k] = v

                newtoken = Token(token.tag,
                                 token.data | other_data,
                                 newcontext)

                for child in self.children:
                    child.callback(newtoken)
            else:
                MATCH.debug("%s (%s | %s) = False",
                            self.__class__.__name__,
                            left_context,
                            right_context)

    def _activate_left(self, token):
        """Node left activation."""
        self.__activation(token,
                          self.left_memory,
                          self.right_memory,
                          is_left=True)

    def _activate_right(self, token):
        """Node right activation."""
        self.__activation(token,
                          self.right_memory,
                          self.left_memory,
                          is_left=False) class WhereNode(mixins.AnyChild,
                mixins.HasMatcher,
                mixins.NoMemory,
                OneInputNode):
    """
    Check some conditions over a token context.

    """
    def _activate(self, token):
        if self.matcher(token.context):
            for child in self.children:
                child.callback(token) class NOT(OperableCE, ConditionalElement):
    pass class OR(OperableCE, ConditionalElement):
    pass class AND(OperableCE, ConditionalElement):
    pass class TEST(OperableCE, ConditionalElement):
    pass class EXISTS(OperableCE, ConditionalElement):
    pass class FORALL(OperableCE, ConditionalElement):
    pass class InitialFact(Fact):
    """
    InitialFact
    """
    pass class Fact(OperableCE, Bindable, dict, metaclass=Validable):
    """Base Fact class"""

    def __init__(self, *args, **kwargs):
        self.update(dict(chain(enumerate(args), kwargs.items())))
        self.__defaults = dict()

    def __missing__(self, key):
        if key not in self.__fields__:
            raise KeyError(key)
        else:
            default = self.__fields__[key].default
            if default is Field.NODEFAULT:
                raise KeyError(key)
            elif key in self.__defaults:
                return self.__defaults[key]
            elif isinstance(default, collections.abc.Callable):
                return self.__defaults.setdefault(key, default())
            else:
                return self.__defaults.setdefault(key, default)

    def __setitem__(self, key, value):
        if self.__factid__ is None:
            super().__setitem__(key, freeze(value))
        else:
            raise RuntimeError("A fact can't be modified after declaration.")

    def validate(self):
        for name, field in self.__fields__.items():
            if name in self:
                try:
                    field.validate(self[name])
                except Exception as exc:
                    raise ValueError(
                        "Invalid value on field %r for fact %r"
                        % (name, self))
            elif field.mandatory:
                raise ValueError(
                    "Mandatory field %r is not defined for fact %r"
                    % (name, self))
            else:
                pass

    def update(self, mapping):
        for k, v in mapping.items():
            self[k] = v

    def as_dict(self):
        """Return a dictionary containing this `Fact` data."""
        return {k: unfreeze(v)
                for k, v in self.items()
                if not self.is_special(k)}

    def copy(self):
        """Return a copy of this `Fact`."""
        content = [(k, v) for k, v in self.items()]

        intidx = [(k, v) for k, v in content if isinstance(k, int)]
        args = [v for k, v in sorted(intidx)]

        kwargs = {k: v
                  for k, v in content
                  if not isinstance(k, int) and not self.is_special(k)}
        return self.__class__(*args, **kwargs)

    def has_field_constraints(self):
        return any(isinstance(v, ConditionalElement) for v in self.values())

    def has_nested_accessor(self):
        return any(("__" in str(k).strip('__') for k in self.keys()))

    @staticmethod
    def is_special(key):
        return (isinstance(key, str)
                and key.startswith('__')
                and key.endswith('__'))

    @property
    def __bind__(self):
        return self.get('__bind__', None)

    @__bind__.setter
    def __bind__(self, value):
        super().__setitem__('__bind__', value)

    @property
    def __factid__(self):
        return self.get('__factid__', None)

    @__factid__.setter
    def __factid__(self, value):
        super().__setitem__('__factid__', value)

    @classmethod
    def from_iter(cls, pairs):
        obj = cls()
        obj.update(dict(pairs))
        return obj

    def __str__(self):  # pragma: no cover
        if self.__factid__ is None:
            return "<Undeclared Fact> %r" % self
        else:
            return "<f-%d>" % self.__factid__

    def __repr__(self):  # pragma: no cover
        return "{}({})".format(
            self.__class__.__name__,
            ", ".join(
                (repr(v) if isinstance(k, int) else "{}={!r}".format(k, v)
                 for k, v in self.items()
                 if not self.is_special(k))))

    def __hash__(self):
        try:
            return self._hash
        except AttributeError:
            self._hash = hash(frozenset(self.items()))
            return self._hash

    def __eq__(self, other):
        return (self.__class__ == other.__class__
                and super().__eq__(other)) class Rule(ConditionalElement):
    """
    Base ``CE``, all ``CE`` are to derive from this class.

    This class is used as a decorator, thus provoking __call__
    to be called twice:

    #. The first call is when the decorator is been created. At this
       point we assign the function decorated to ``self._wrapped`` and
       return ``self`` to be called the second time.

    #. The second call is to execute the decorated function, se we
       pass all the arguments along.
    """

    def __new__(cls, *args, salience=0):
        obj = super(Rule, cls).__new__(cls, *args)

        obj._wrapped = None
        obj._wrapped_args = []
        obj._wrapped_self = None
        obj.salience = salience

        return obj

    def new_conditions(self, *args):
        """
        Generate a new rule with the same attributes but with the given
        conditions.

        """
        obj = self.__class__(*args, salience=self.salience)

        if self._wrapped:
            obj = obj(self._wrapped)

        return obj

    def __hash__(self):
        return hash(tuple(self)
                    + (self._wrapped, self._wrapped_self, self.salience))

    def __eq__(self, other):
        if isinstance(other, self.__class__):
            self_data = tuple(self) + (self._wrapped,
                                       self._wrapped_self,
                                       self.salience)
            other_data = tuple(other) + (other._wrapped,
                                         other._wrapped_self,
                                         other.salience)
            return self_data == other_data
        else:
            return False

    def __call__(self, *args, **kwargs):
        """
        Make method checks if it's the first call, and update wrapper.
        Othersise execute the RHS.
        """
        if self._wrapped is None:
            if not args:
                raise AttributeError("Mandatory function not provided.")
            else:
                self._wrapped = args[0]

                signature = inspect.signature(self._wrapped)
                if not any(p.kind == inspect.Parameter.VAR_KEYWORD
                           for p in signature.parameters.values()):
                    # There is not **kwargs defined. Pass only the defined
                    # names.
                    self._wrapped_args = set(signature.parameters.keys())

                return update_wrapper(self, self._wrapped)
        else:
            if self._wrapped_args:
                kwargs = {k: v for k, v in kwargs.items()
                          if k in self._wrapped_args}
            if self._wrapped_self is None:
                return self._wrapped(*args, **kwargs)
            else:
                return self._wrapped(self._wrapped_self, *args, **kwargs)

    def __repr__(self):  # pragma: no cover
        return "%s => %r" % (super().__repr__(), self._wrapped)

    def __get__(self, instance, owner):
        self._wrapped_self = instance
        return self
from functools import singledispatch
from .check import FeatureCheck, TypeCheck, FactCapture, SameContextCheck
from .check import WhereCheck
from .dnf import dnf
from .nodes import ConflictSetNode, NotNode, OrdinaryMatchNode
from .nodes import WhereNode
from pyknow.conditionalelement import NOT, OR, AND, TEST, EXISTS, FORALL
from pyknow.fact import InitialFact, Fact
from pyknow.rule import Rule



@singledispatch
def prepare_rule(exp):
    """
    Given a rule, build a new one suitable for RETE network generation.

    Meaning:

        #. Rule is in disjuntive normal form (DNF).

        #. If the `rule` is empty is filled with an `InitialFact`.

        #. If the `rule` starts with a `NOT`, an `InitialFact` is prepended.

        #. If any AND starts with a `NOT`, an `InitialFact` is prepended.

        #. If the `rule` is an OR condition, each NOT inside will be
        converted to AND(InitialFact(), NOT(...))

    """
    return exp


@prepare_rule.register(Rule)
def _(exp):
    dnf_rule = dnf(exp)
    prep_rule = dnf_rule.new_conditions(
        *[prepare_rule(e) for e in dnf_rule])
    if not prep_rule:
        return prep_rule.new_conditions(InitialFact())
    elif isinstance(prep_rule[0], NOT) or len(extract_facts(prep_rule)) == 0:
        return prep_rule.new_conditions(InitialFact(), *prep_rule)
    else:
        return dnf(prep_rule)


@prepare_rule.register(OR)
def _(exp):
    or_exp = []
    for e in exp:
        if isinstance(e, NOT):
            or_exp.append(AND(InitialFact(), e))
        elif isinstance(e, AND):
            or_exp.append(prepare_rule(e))
        else:
            or_exp.append(e)
    return OR(*or_exp)


@prepare_rule.register(AND)
def _(exp):
    if isinstance(exp[0], NOT):
        return AND(InitialFact(), *exp)
    else:
        return exp


def extract_facts(rule):
    """Given a rule, return a set containing all rule LHS facts."""
    def _extract_facts(ce):
        if isinstance(ce, Fact):
            yield ce
        elif isinstance(ce, TEST):
            pass
        else:
            for e in ce:
                yield from _extract_facts(e)

    return set(_extract_facts(rule))


def generate_checks(fact):
    """Given a fact, generate a list of Check objects for checking it."""

    yield TypeCheck(type(fact))

    fact_captured = False
    for key, value in fact.items():
        if (isinstance(key, str)
                and key.startswith('__')
                and key.endswith('__')):
            # Special fact feature
            if key == '__bind__':
                yield FactCapture(value)
                fact_captured = True
            else:  # pragma: no cover
                yield FeatureCheck(key, value)
        else:
            yield FeatureCheck(key, value)

    # Assign the matching fact to the context
    if not fact_captured:
        yield FactCapture("__pattern_%s__" % id(fact))


def wire_rule(rule, alpha_terminals, lhs=None):
    if lhs is None:
        lhs = rule

    @singledispatch
    def _wire_rule(elem):
        raise TypeError("Unknown type %s" % type(elem))

    @_wire_rule.register(TEST)
    def _(elem):
        return WhereNode(WhereCheck(elem[0]))

    @_wire_rule.register(FORALL)
    def _(elem):
        leader = elem[0]
        followers = elem[1:]

        initial_fact_node = _wire_rule(InitialFact())
        leader_node = _wire_rule(leader)
        followers_node = _wire_rule(AND(*followers))
        not_node_1 = NotNode(SameContextCheck())

        not_node_2 = NotNode(SameContextCheck())