# Path: strips/hsp.py
# def compute_costs(state, goal, operators, op=max, unit=False):
#     unprocessed = defaultdict(list) # TODO: store this in memory
#     unsatisfied = {}
#     for operator in operators + [goal]:
#         unsatisfied[operator] = len(operator.conditions)
#         for literal in operator.conditions:
#             unprocessed[literal].append(operator)
# 
#     literal_costs = {literal: Pair(0, 0) for literal in unprocessed if literal in state}
#     operator_costs = {}
#     queue = [(pair.cost, literal) for literal, pair in literal_costs.items()]
# 
#     def process_operator(operator):
#         operator_cost = operator_level = 0
#         if len(operator.conditions) != 0:
#             operator_cost = op(literal_costs[literal].cost for literal in operator.conditions)
#             operator_level = max(literal_costs[literal].level for literal in operator.conditions)
#         operator_costs[operator] = Pair(operator_cost, operator_level)
#         if operator == goal:
#             # TODO: terminate earlier if the goal is achieved
#             return True
#         literal_cost = operator_cost + (operator.cost if not unit else 1)
#         literal_level = operator_level + 1
#         # literal_level = operator_level + (1 if not operator.is_axiom() else 0)
#         for effect in operator.effects:
#             # Only computes costs for relevant operators
#             if (effect in unprocessed) and (effect not in literal_costs or
#                                             (literal_cost < literal_costs[effect].cost)):
#                 literal_costs[effect] = Pair(literal_cost, literal_level)
#                 heappush(queue, (literal_cost, effect))
#         return False
# 
#     for operator in unsatisfied:
#         if unsatisfied[operator] == 0:
#             process_operator(operator)
#     while queue:
#         _, literal = heappop(queue)
#         if literal not in unprocessed:
#             continue
#         for operator in unprocessed[literal]:
#             unsatisfied[operator] -= 1
#             if unsatisfied[operator] == 0:
#                 process_operator(operator)
#         del unprocessed[literal]
# 
#     return literal_costs, operator_costs
# 
# Path: strips/operators.py
# class Action(Operator): 
#     cost = 1 # TODO: cost function
# 
# Path: misc/functions.py
# def elapsed_time(start_time):
# def irange(start, stop=None, step=1): #np.arange
# def erange(start, stop=None, step=1):
# def some(function, iterable):
# def every(function, iterable):
# def first(function, iterable):
# def argmax(function, sequence):
# def argmin(function, sequence):
# def pop_max(function, array):
# def pop_min(function, array):
# def safe_remove(sequence, item):
# def safe_function(function, sequence, default):
# def safe_choice(sequence, default=None):
# def safe_max(sequence, default=-INF):
# def safe_min(sequence, default=INF):
# def safe_maxmin(iterable_of_iterables):
# def safe_minmax(iterable_of_iterables):
# def safe_index(sequence, value, default=-1):
# def pairs(lst):
# def randomize(sequence):
# def random_sequence(sequence):
# def reverse(iterable):
# def flatten(iterable_of_iterables):
# def squash(nested_iterables):
# def round_robin(*iterables):
# def merge_unique(iterables, sort=False):
# def merge_dicts(*args):
# def in_add(x, s):
# def implies(a, b):


from strips.hsp import compute_costs
from strips.operators import Action
from misc.functions import argmin, flatten, INF
def none(operator_costs, relaxed_plan, relaxed_goals):
    return []

def applicable(operator_costs, relaxed_plan, relaxed_goals):
    return [o for o, (_, level) in operator_costs.items()
            if isinstance(o, Action) and (level == 0)]

def backpointers(operator_costs, relaxed_plan, relaxed_goals):
    # Retrace actions without using extract_relaxed_plan
    # Could also do h_max and h_add versions
    raise NotImplementedError()

def first_goals(operator_costs, relaxed_plan, relaxed_goals):
    if len(relaxed_goals) <= 1:
        return []
    return [o for o, (_, level) in operator_costs.items()
            if isinstance(o, Action) and (level == 0) and any(effect in relaxed_goals[1] for effect in o.effects)]

def first_operators(operator_costs, relaxed_plan, relaxed_goals):
    if not relaxed_plan:
        return []
    return [o for o in relaxed_plan[0] if isinstance(o, Action)]

# TODO: prioritize helpful actions

###########################################################################

def ff(state, goal, operators, heuristic, helpful_actions, op=max, unit=False):
    literal_costs, operator_costs = compute_costs(state, goal, operators, op=op, unit=unit)
    if goal not in operator_costs:

# Path: strips/hsp.py
# def compute_costs(state, goal, operators, op=max, unit=False):
#     unprocessed = defaultdict(list) # TODO: store this in memory
#     unsatisfied = {}
#     for operator in operators + [goal]:
#         unsatisfied[operator] = len(operator.conditions)
#         for literal in operator.conditions:
#             unprocessed[literal].append(operator)
# 
#     literal_costs = {literal: Pair(0, 0) for literal in unprocessed if literal in state}
#     operator_costs = {}
#     queue = [(pair.cost, literal) for literal, pair in literal_costs.items()]
# 
#     def process_operator(operator):
#         operator_cost = operator_level = 0
#         if len(operator.conditions) != 0:
#             operator_cost = op(literal_costs[literal].cost for literal in operator.conditions)
#             operator_level = max(literal_costs[literal].level for literal in operator.conditions)
#         operator_costs[operator] = Pair(operator_cost, operator_level)
#         if operator == goal:
#             # TODO: terminate earlier if the goal is achieved
#             return True
#         literal_cost = operator_cost + (operator.cost if not unit else 1)
#         literal_level = operator_level + 1
#         # literal_level = operator_level + (1 if not operator.is_axiom() else 0)
#         for effect in operator.effects:
#             # Only computes costs for relevant operators
#             if (effect in unprocessed) and (effect not in literal_costs or
#                                             (literal_cost < literal_costs[effect].cost)):
#                 literal_costs[effect] = Pair(literal_cost, literal_level)
#                 heappush(queue, (literal_cost, effect))
#         return False
# 
#     for operator in unsatisfied:
#         if unsatisfied[operator] == 0:
#             process_operator(operator)
#     while queue:
#         _, literal = heappop(queue)
#         if literal not in unprocessed:
#             continue
#         for operator in unprocessed[literal]:
#             unsatisfied[operator] -= 1
#             if unsatisfied[operator] == 0:
#                 process_operator(operator)
#         del unprocessed[literal]
# 
#     return literal_costs, operator_costs
# 
# Path: strips/operators.py
# class Action(Operator): 
#     cost = 1 # TODO: cost function
# 
# Path: misc/functions.py
# def elapsed_time(start_time):
# def irange(start, stop=None, step=1): #np.arange
# def erange(start, stop=None, step=1):
# def some(function, iterable):
# def every(function, iterable):
# def first(function, iterable):
# def argmax(function, sequence):
# def argmin(function, sequence):
# def pop_max(function, array):
# def pop_min(function, array):
# def safe_remove(sequence, item):
# def safe_function(function, sequence, default):
# def safe_choice(sequence, default=None):
# def safe_max(sequence, default=-INF):
# def safe_min(sequence, default=INF):
# def safe_maxmin(iterable_of_iterables):
# def safe_minmax(iterable_of_iterables):
# def safe_index(sequence, value, default=-1):
# def pairs(lst):
# def randomize(sequence):
# def random_sequence(sequence):
# def reverse(iterable):
# def flatten(iterable_of_iterables):
# def squash(nested_iterables):
# def round_robin(*iterables):
# def merge_unique(iterables, sort=False):
# def merge_dicts(*args):
# def in_add(x, s):
# def implies(a, b):


# Path: strips/ff.py
from strips.hsp import compute_costs
from strips.operators import Action
from misc.functions import argmin, flatten, INF

def none(operator_costs, relaxed_plan, relaxed_goals):
    return []

def applicable(operator_costs, relaxed_plan, relaxed_goals):
    return [o for o, (_, level) in operator_costs.items()
            if isinstance(o, Action) and (level == 0)]

def backpointers(operator_costs, relaxed_plan, relaxed_goals):
    # Retrace actions without using extract_relaxed_plan
    # Could also do h_max and h_add versions
    raise NotImplementedError()

def first_goals(operator_costs, relaxed_plan, relaxed_goals):
    if len(relaxed_goals) <= 1:
        return []
    return [o for o, (_, level) in operator_costs.items()
            if isinstance(o, Action) and (level == 0) and any(effect in relaxed_goals[1] for effect in o.effects)]

def first_operators(operator_costs, relaxed_plan, relaxed_goals):
    if not relaxed_plan:
        return []
    return [o for o in relaxed_plan[0] if isinstance(o, Action)]

# TODO: prioritize helpful actions

###########################################################################

def ff(state, goal, operators, heuristic, helpful_actions, op=max, unit=False):
    literal_costs, operator_costs = compute_costs(state, goal, operators, op=op, unit=unit)
    if goal not in operator_costs:

        return INF, []