from fnmatch import fnmatch
from heuristics.heuristic_base import Heuristic


def get_parts(fact):
    """Extract the components of a PDDL fact by removing parentheses and splitting the string."""
    return fact[1:-1].split()


def match(fact, *args):
    """
    Check if a PDDL fact matches a given pattern.

    - `fact`: The complete fact as a string, e.g., "(at rover1 waypoint1)".
    - `args`: The expected pattern (wildcards `*` allowed).
    - Returns `True` if the fact matches the pattern, else `False`.
    """
    parts = get_parts(fact)
    return all(fnmatch(part, arg) for part, arg in zip(parts, args))


class roversHeuristic(Heuristic):
    """
    A domain-dependent heuristic for the rovers domain.

    # Summary
    This heuristic estimates the number of actions required to achieve all goal conditions in the rovers domain.
    It counts the number of goal predicates that are not yet satisfied in the current state.
    This serves as a simple and efficient estimate, prioritizing states that satisfy more goal conditions.

    # Assumptions:
    - Each goal predicate requires at least one action to be achieved.
    - The heuristic is non-admissible and designed for greedy best-first search to quickly find a solution,
      not necessarily the optimal one.
    - Navigation costs and action preconditions (like visibility, can_traverse, calibration) are not explicitly considered
      in the heuristic value for simplicity and efficiency. The heuristic focuses on the count of remaining goal predicates.

    # Heuristic Initialization
    - The heuristic initializes by storing the goal predicates from the task definition.
    - No static facts are pre-processed in this simple heuristic.

    # Step-By-Step Thinking for Computing Heuristic
    1. Initialize the heuristic value to zero.
    2. Iterate through each goal predicate defined in the task.
    3. For each goal predicate, check if it is present in the current state.
    4. If a goal predicate is NOT present in the current state, increment the heuristic value by 1.
    5. The final heuristic value is the total count of unsatisfied goal predicates.
    6. Return the heuristic value.

    This heuristic essentially counts the number of remaining tasks (goal predicates) to be accomplished.
    It is a simplification and does not consider the complexity of achieving each individual goal,
    but it provides a quick estimate of progress towards the goal state.
    """

    def __init__(self, task):
        """
        Initialize the rovers heuristic.
        Extracts goal predicates from the task.
        """
        self.goals = task.goals

    def __call__(self, node):
        """
        Compute the heuristic value for a given state.
        The heuristic value is the number of goal predicates not satisfied in the state.
        """
        state = node.state
        heuristic_value = 0
        for goal in self.goals:
            if goal not in state:
                heuristic_value += 1
        return heuristic_value
