# Path: strips/states.py
# class State(object):
#     def __init__(self, atoms, external=None):
#         self.atoms = frozenset(atoms)
#         self.external = external
#         # TODO: refine function where external is updated
#         # TODO: should I assume external is hashable?
#     def identity(self):
#         return self.__class__, frozenset(self.__dict__.items())
#     def holds(self, literal):
#         return literal.negated == (literal.positive() not in self.atoms)
#     __contains__ = holds
#     def __iter__(self):
#         return iter(self.atoms)
#     def __eq__(self, other):
#         return (type(self) == type(other)) and (self.identity() == other.identity())
#     def __ne__(self, other):
#         return not (self == other)
#     def __hash__(self):
#         return hash(self.identity())
#     def __str__(self):
#         #return '{}({})'.format(self.__class__.__name__, id(self))
#         return self.__class__.__name__ + str_object(self.atoms)
#     __repr__ = __str__
# 
# class PartialState(object):
#     def __init__(self, literals, test=lambda state: True):
#         self.conditions = frozenset(literals)
#         self.test = test # TODO: cache the test results
#     def contains(self, state):
#         return all(literal in state for literal in self.conditions) # and self.test(state)
#     __contains__ = contains
#     def __iter__(self):
#         return iter(self.conditions)
#     def __eq__(self, other):
#         return (type(self) == type(other)) and (self.conditions == other.conditions)
#     def __ne__(self, other):
#         return not (self == other)
#     def __hash__(self):
#         return hash((self.__class__, self.conditions))
#     def __str__(self):
#         return self.__class__.__name__ + str_object(self.conditions)
#     __repr__ = __str__
# 
# Path: misc/objects.py
# def str_object(obj):
#   if type(obj) in (list, np.ndarray):
#     return '[%s]' % ', '.join(str_object(item) for item in obj)
#   if type(obj) == tuple:
#     return '(%s)' % ', '.join(str_object(item) for item in obj)
#   if type(obj) == dict:
#     return '{%s}' % ', '.join(str_object(item) + ': ' +  str_object(obj[item])
#                               for item in sorted(obj.keys(), key=lambda k: str_object(k)))
#   if type(obj) in (set, frozenset):
#     return '{%s}' % ', '.join(sorted(str_object(item) for item in obj))
#   if type(obj) in (float, np.float64):
#     obj = round(obj, 3)
#     if obj == 0:
#       obj = 0 # NOTE - catches -0.0 bug
#     return '%.3f'%obj
#   if isinstance(obj, types.FunctionType):
#     return obj.__name__
#   return str(obj)


from collections import defaultdict, deque
from .states import State, PartialState
from misc.objects import str_object

def derive_state(state, axioms):
    # TODO: assumes there isn't any contradiction
    unprocessed = defaultdict(list)
    unsatisfied = {}
    for axiom in axioms:
        unsatisfied[axiom] = len(axiom.conditions)
        for literal in axiom.conditions:
            unprocessed[literal].append(axiom)

    reached_literals = {literal for literal in unprocessed if literal in state}
    queue = deque(reached_literals)

    def process_operator(axiom):
        for effect in axiom.effects:
            if effect not in reached_literals:
                reached_literals.add(effect)
                queue.append(effect)

    for axiom in unsatisfied:
        if unsatisfied[axiom] == 0:
            process_operator(axiom)
    while queue:
        literal = queue.popleft()
        for axiom in unprocessed[literal]:
            unsatisfied[axiom] -= 1
            if unsatisfied[axiom] == 0:
                process_operator(axiom)
        del unprocessed[literal]


# Path: strips/states.py
# class State(object):
#     def __init__(self, atoms, external=None):
#         self.atoms = frozenset(atoms)
#         self.external = external
#         # TODO: refine function where external is updated
#         # TODO: should I assume external is hashable?
#     def identity(self):
#         return self.__class__, frozenset(self.__dict__.items())
#     def holds(self, literal):
#         return literal.negated == (literal.positive() not in self.atoms)
#     __contains__ = holds
#     def __iter__(self):
#         return iter(self.atoms)
#     def __eq__(self, other):
#         return (type(self) == type(other)) and (self.identity() == other.identity())
#     def __ne__(self, other):
#         return not (self == other)
#     def __hash__(self):
#         return hash(self.identity())
#     def __str__(self):
#         #return '{}({})'.format(self.__class__.__name__, id(self))
#         return self.__class__.__name__ + str_object(self.atoms)
#     __repr__ = __str__
# 
# class PartialState(object):
#     def __init__(self, literals, test=lambda state: True):
#         self.conditions = frozenset(literals)
#         self.test = test # TODO: cache the test results
#     def contains(self, state):
#         return all(literal in state for literal in self.conditions) # and self.test(state)
#     __contains__ = contains
#     def __iter__(self):
#         return iter(self.conditions)
#     def __eq__(self, other):
#         return (type(self) == type(other)) and (self.conditions == other.conditions)
#     def __ne__(self, other):
#         return not (self == other)
#     def __hash__(self):
#         return hash((self.__class__, self.conditions))
#     def __str__(self):
#         return self.__class__.__name__ + str_object(self.conditions)
#     __repr__ = __str__
# 
# Path: misc/objects.py
# def str_object(obj):
#   if type(obj) in (list, np.ndarray):
#     return '[%s]' % ', '.join(str_object(item) for item in obj)
#   if type(obj) == tuple:
#     return '(%s)' % ', '.join(str_object(item) for item in obj)
#   if type(obj) == dict:
#     return '{%s}' % ', '.join(str_object(item) + ': ' +  str_object(obj[item])
#                               for item in sorted(obj.keys(), key=lambda k: str_object(k)))
#   if type(obj) in (set, frozenset):
#     return '{%s}' % ', '.join(sorted(str_object(item) for item in obj))
#   if type(obj) in (float, np.float64):
#     obj = round(obj, 3)
#     if obj == 0:
#       obj = 0 # NOTE - catches -0.0 bug
#     return '%.3f'%obj
#   if isinstance(obj, types.FunctionType):
#     return obj.__name__
#   return str(obj)


# Path: strips/operators.py
from collections import defaultdict, deque
from .states import State, PartialState
from misc.objects import str_object


def derive_state(state, axioms):
    # TODO: assumes there isn't any contradiction
    unprocessed = defaultdict(list)
    unsatisfied = {}
    for axiom in axioms:
        unsatisfied[axiom] = len(axiom.conditions)
        for literal in axiom.conditions:
            unprocessed[literal].append(axiom)

    reached_literals = {literal for literal in unprocessed if literal in state}
    queue = deque(reached_literals)

    def process_operator(axiom):
        for effect in axiom.effects:
            if effect not in reached_literals:
                reached_literals.add(effect)
                queue.append(effect)

    for axiom in unsatisfied:
        if unsatisfied[axiom] == 0:
            process_operator(axiom)
    while queue:
        literal = queue.popleft()
        for axiom in unprocessed[literal]:
            unsatisfied[axiom] -= 1
            if unsatisfied[axiom] == 0:
                process_operator(axiom)
        del unprocessed[literal]


    state = State({atom for atom in state.atoms if atom.negate() not in reached_literals} |