# 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

# TODO: move this into a shared class
class DomainError(Exception):
    def __init__(self, salary, message="Salary is not in (5000, 15000) range"):
        self.salary = salary
        self.message = message
        super().__init__(self.message)
    # def __str__(self):
    #     return self.message

class Operator(PartialState):
    cost = None
    def __init__(self, args):
        #super(Operator, self).__init__(...)
        for k, v in args.items():
            setattr(self, k, v)
        self.args = args # TODO - use FrozenDict instead
        self._frozen_args = frozenset(args.items())
        self._hash = None
        self.conditions = None
        self.effects = None
        self.test = lambda state: True
        self.forward = lambda state: state
    # TODO: override instead?
    # def test(self, state):
    #     return True
    # def forward(self, state):
    #     return state

# 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


# TODO: move this into a shared class
class DomainError(Exception):
    def __init__(self, salary, message="Salary is not in (5000, 15000) range"):
        self.salary = salary
        self.message = message
        super().__init__(self.message)
    # def __str__(self):
    #     return self.message

class Operator(PartialState):
    cost = None
    def __init__(self, args):
        #super(Operator, self).__init__(...)
        for k, v in args.items():
            setattr(self, k, v)
        self.args = args # TODO - use FrozenDict instead
        self._frozen_args = frozenset(args.items())
        self._hash = None
        self.conditions = None
        self.effects = None
        self.test = lambda state: True
        self.forward = lambda state: state
    # TODO: override instead?
    # def test(self, state):
    #     return True
    # def forward(self, state):
    #     return state

    applicable = PartialState.contains