from __future__ import generators
from expr import Step, Class, Property
from util.anysets import Set, ImmutableSet
empty_set = ImmutableSet()


class RuleDict(dict):
    """A mapping of resources to GraphPath expressions.

    The expression for a given resource is the Union()
    of all the expressions assigned to that mapping.
    """
    def __setitem__(self, res, expr):
        """Add a definition for a resource"""
        if res in self:
            extant = self[res]
            if extant != expr:
                dict.__setitem__(self, res, extant | expr)

        else:
            dict.__setitem__(self, res, expr)


class ClassRules(RuleDict, Step):
    """A dictionary of class definitions and, simultaneously,
    the rule for rdf:type.

    As a mapping, the key is a class resource and the value
    is a GraphPath  expression.

    As a GraphPath step, match every implied rdf:type path.
    """
    def values(self, pop, members):
        """It is very expensive to ask the rdf.type of a resource
        under this rule evaluation system.  For now, we defer to
        the ground facts when asked a type."""
        return empty_set

        # full implementation disabled....
        result = Set()
        for clss in self:
            if members & self[clss].initials(pop):
                result.add(clss)
        return result

    def match(self, pop, classes):
        """Generate the extent set for a class or classes."""
        result = Set()
        for clss in classes:
            if clss in self:
                result |= self[clss].initials(pop)
        return result

    def __or__(self, other):
        """Prevent accidental modification
        via redefinition or rdf:type."""
        raise NotImplementedError


class PropertyRules(RuleDict):
    """A dictionary of property definitions.

    The key is a property resource and the value is a
    GraphPath expresion.
    """


class RuleBase:
    """A RuleBase is a mapping of classes and properties to their
    definitions.

    A class is indexed by an elementary Class(...)
    step and its mapping is a definition in the form of
    an absolute GraphPath expression.

    A property is indexed by an elementary Property(...) step
    and its mapping is a definition in the form of a relative
    GraphPath expression.

    Two attributes, self.classes and self.properties expose
    the two rule populations individually.
    """
    def __init__(self):
        self.clear()

    def clear(self):
        """Empty the rule base."""
        self.classes = ClassRules()
        self.properties = PropertyRules()

    def __getstate__(self):
        return self.properties, self.classes

    def __setstate__(self, state):
        self.properties, self.classes = state

    def update(self, other):
        """Add all rules from another rulebase."""
        for key in other:
            self[key] = other[key]

    def __setitem__(self, lvalue, expr):
        """Add a definition for a class or property.
        Multiple definitions for the same class or property
        are combined by union.
        """
        if isinstance(lvalue, Class):
            self.classes[lvalue.resource()] = expr
        else:
            assert isinstance(lvalue, Property)
            self.properties[lvalue.resource()] = expr

    def __getitem__(self, lvalue):
        """Map a class or property to its definition."""
        if isinstance(lvalue, Class):
            return self.classes[lvalue.resource()]
        else:
            assert isinstance(lvalue, Property)
            return self.properties[lvalue.resource()]

    def __contains__(self, lvalue):
        """Test if a class or property is defined."""
        try:
            __trial = self[lvalue]
            del __trial
        except KeyError:
            return False
        else:
            return True

    def __iter__(self):
        """Iterate all properties and classes in the rule base."""
        for res in self.classes:
            yield Class(res)
        for res in self.properties:
            if self.properties[res] is not self.classes:
                yield Property(res)

    def get(self, lvalue, default=None):
        """Map a class or property to its definition or None."""
        try:
            return self[lvalue]
        except KeyError:
            return default


class Sandbox:
    """A Sandbox is an environment for rule execution.  It implements the
    Population protocol and so can be queried with
    expricit GraphPath expressions and implicitly by rules.

    Rule dependencies a tracked, circular rules are iterated until stable,
    and results are cached for the lifetime of the sandbox.

    A sandbox requires the ground facts and rules to remain contant and there
    must be only one thread executing in the sandbox.

    Rules should be written so results depend only on information provided
    by calling the sandbox methods.  Rules must support the rule protocol
    (see expr module) but need not be written using the expr module classes.
    """

    def __init__(self, pop, rules):
        """Create a sandbox for the given facts and rules (both constant)."""
        self._pop = pop
        self._rules = rules
        self._cache = {}
        self._stack = []
        self._circular = {}
        # set the rdf:type rule for the local rdf:type symbol
        self.rdf_type = pop.rdf_type
        rules.properties[pop.rdf_type] = rules.classes

    def match(self, prop, value):
        """Delegate the match function to a rule, if any,
        otherwise return ground facts."""
        if prop in self._rules.properties:
            return self._evaluate(False, prop, value)
        else:
            return self._pop.match(prop, value)

    def values(self, subj, prop):
        """Delegate the values function to a rule, if any,
        otherwise return ground facts."""
        if prop in self._rules.properties:
            return self._evaluate(True, prop, subj)
        else:
            return self._pop.values(subj, prop)

    def _evaluate(self, forward, prop, seed):
        """evaluate a rule for a property, prop,
        in the direction, forward, with the argument, seed."""
        pattern = forward, prop, seed  # the present query as a tuple
        stack = self._stack
        circs = self._circular
        cache = self._cache
        # print " "*len(stack),pattern

        # have we seen this query before?
        if pattern in cache:
            # is it a circular query (depends on its own result)?
            if pattern in stack:

                # register the query with its circular dependencies
                depends = circs.setdefault(pattern, Set())
                for ix in range(len(stack) - 1, -1, -1):  # 2.2 syntax
                    depend = stack[ix]
                    if depend == pattern:
                        break
                    depends.add(depend)

            # return previously obtained result
            return cache[pattern]

        # prepare to evaluate from scratch
        seeds = Set([seed])
        result = cache[pattern] = Set()

        # get rule and ground facts
        if forward:
            rule = self._rules.properties[prop].values
            result |= self._pop.values(seed, prop)
        else:
            rule = self._rules.properties[prop].match
            result |= self._pop.match(prop, seed)

        # maintain an evaluation stack to track circular dependencies
        stack.append(pattern)

        # attempt evaluation
        result |= rule(self, seeds)

        # if a circulation was detected we must iterate
        if pattern in circs:
            depends = circs[pattern]
            while True:
                init_count = len(result)

                # invalidate cache for sub-queries that depend on result
                for depend in depends:
                    del cache[depend]

                result |= rule(self, seeds)

                # if no new results were obtained we are finished
                if len(result) == init_count:
                    break

        # evaluation complete: cleanup stack
        stack.pop()
        return result

    def __contains__(self, rid):
        return rid in self._pop

    def __iter__(self):
        return iter(self._pop)

    def __getitem__(self, rid):
        return self._pop[rid]
