# 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/functions.py
# def some(function, iterable):
#   return any(function(item) for item in iterable)


from state_expanders import *
from strips.states import State, PartialState
from misc.functions import some

# TODO
# - Replanning
# - Non-aggressive hierarchy (allowing open subproblems)
# - Other factored planning

def search(start, goal, hierarchy, level=0):
  level_start = State({v: start.get(v) for v in start.values
                       if some(lambda item: v in item[0], hierarchy[:level + 1])})

  print('-----------------------------------------------------------------\n')

  print(level, level_start, goal)
  plan, data = hierarchy[level][1](level_start, goal)


  time, iterations, states = data.time, data.iterations, data.states
  if plan is None: return None, planner.main.SearchData(time, iterations, states)


  print([action.name for (action, _) in plan.sequence if action is not None])
  print()

  sequence = [(None, start)]
  cost, length = plan.cost, plan.length
  for action, _ in plan.sequence:
    if action is not None:
      current_state = sequence[-1][1]
      if not action.applicable(current_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/functions.py
# def some(function, iterable):
#   return any(function(item) for item in iterable)


# Path: planner/meta/retired/enforced_hierarchy.py
from state_expanders import *
from strips.states import State, PartialState
from misc.functions import some


# TODO
# - Replanning
# - Non-aggressive hierarchy (allowing open subproblems)
# - Other factored planning

def search(start, goal, hierarchy, level=0):
  level_start = State({v: start.get(v) for v in start.values
                       if some(lambda item: v in item[0], hierarchy[:level + 1])})

  print('-----------------------------------------------------------------\n')

  print(level, level_start, goal)
  plan, data = hierarchy[level][1](level_start, goal)


  time, iterations, states = data.time, data.iterations, data.states
  if plan is None: return None, planner.main.SearchData(time, iterations, states)


  print([action.name for (action, _) in plan.sequence if action is not None])
  print()

  sequence = [(None, start)]
  cost, length = plan.cost, plan.length
  for action, _ in plan.sequence:
    if action is not None:
      current_state = sequence[-1][1]
      if not action.applicable(current_state):

        subplan, subdata = search(current_state, PartialState(action.pre()), hierarchy, level=level+1)