#! /usr/bin/env python

##################################################################################################################################
# DeepSeek V3

import argparse
import math
import time


def is_goal_state_20250509_154448(state):
    return len(state["state"]) == 1 and state["state"][0] == state["target"]

def get_successor_states_20250509_154448(state):
    import itertools
    import operator

    numbers = state["state"]
    target = state["target"]
    successors = []

    def apply_operation(a, b, op):
        if op == operator.truediv and b == 0:
            return None
        try:
            result = op(a, b)
            return result
        except:
            return None

    for a, b in itertools.permutations(numbers, 2):
        # Handle cases where a and b are the same but multiple occurrences exist
        remaining_numbers = numbers.copy()
        remaining_numbers.remove(a)
        remaining_numbers.remove(b)

        for op in [operator.add, operator.sub, operator.mul, operator.truediv]:
            result = apply_operation(a, b, op)
            if result is not None:
                new_state = remaining_numbers + [result]
                successors.append({"state": new_state, "target": target})

    return successors


def is_goal_state_20250509_160231(state):
    return len(state["state"]) == 1 and state["state"][0] == state["target"]

def get_successor_states_20250509_160231(state):
    import itertools
    import operator

    def apply_operation(a, b, op):
        if op == operator.add:
            return a + b
        elif op == operator.sub:
            return a - b, b - a  # Consider both orders for subtraction
        elif op == operator.mul:
            return a * b
        elif op == operator.truediv:
            results = []
            if b != 0:
                results.append(a / b)
            if a != 0:
                results.append(b / a)
            return tuple(results) if results else None
        return None

    numbers = state["state"]
    target = state["target"]
    successors = []

    for i, j in itertools.combinations(range(len(numbers)), 2):
        for op in [operator.add, operator.sub, operator.mul, operator.truediv]:
            results = apply_operation(numbers[i], numbers[j], op)
            if results is not None:
                if not isinstance(results, tuple):
                    results = (results,)
                for result in results:
                    if result is not None:  # Ensure result is not None
                        new_numbers = [numbers[k] for k in range(len(numbers)) if k != i and k != j]
                        new_numbers.append(result)
                        successors.append({"state": sorted(new_numbers), "target": target})

    return successors


def is_goal_state_20250509_164317(state):
    return len(state["state"]) == 1 and state["state"][0] == state["target"]

def get_successor_states_20250509_164317(state):
    import itertools

    numbers = state["state"]
    target = state["target"]
    successors = []

    for pair in itertools.permutations(numbers, 2):
        remaining_numbers = list(numbers)
        remaining_numbers.remove(pair[0])
        remaining_numbers.remove(pair[1])

        # Addition
        result = pair[0] + pair[1]
        new_state = {"state": sorted(remaining_numbers + [result]), "target": target}
        successors.append(new_state)

        # Subtraction (forward)
        result = pair[0] - pair[1]
        new_state = {"state": sorted(remaining_numbers + [result]), "target": target}
        successors.append(new_state)

        # Subtraction (reverse)
        result = pair[1] - pair[0]
        new_state = {"state": sorted(remaining_numbers + [result]), "target": target}
        successors.append(new_state)

        # Multiplication
        result = pair[0] * pair[1]
        new_state = {"state": sorted(remaining_numbers + [result]), "target": target}
        successors.append(new_state)

        # Division (forward, avoid division by zero)
        if pair[1] != 0:
            result = pair[0] / pair[1]
            new_state = {"state": sorted(remaining_numbers + [result]), "target": target}
            successors.append(new_state)

        # Division (reverse, avoid division by zero)
        if pair[0] != 0:
            result = pair[1] / pair[0]
            new_state = {"state": sorted(remaining_numbers + [result]), "target": target}
            successors.append(new_state)

    return successors


def is_goal_state_20250509_171121(state):
    return len(state["state"]) == 1 and state["state"][0] == state["target"]

def get_successor_states_20250509_171121(current_state):
    def apply_operation(a, b, operation):
        if operation == '+':
            return a + b
        elif operation == '-':
            return a - b
        elif operation == '*':
            return a * b
        elif operation == '/':
            if b != 0:
                return a / b
            elif a != 0:
                return b / a
            else:
                return None
        return None
    
    numbers = current_state["state"]
    successors = []
    
    for i in range(len(numbers)):
        for j in range(len(numbers)):
            if i == j:
                continue
            a, b = numbers[i], numbers[j]
            for operation in ['+', '-', '*', '/']:
                result = apply_operation(a, b, operation)
                if result is not None:
                    new_numbers = [numbers[k] for k in range(len(numbers)) if k != i and k != j]
                    new_numbers.append(result)
                    new_state = {"state": new_numbers, "target": current_state["target"]}
                    successors.append(new_state)
    
    return successors


def is_goal_state_20250509_183049(state_dict):
    return len(state_dict["state"]) == 1 and state_dict["state"][0] == state_dict["target"]

def get_successor_states_20250509_183049(state_dict):
    from itertools import permutations

    def generate_operations(a, b):
        operations = []
        operations.append((a + b, f"{a}+{b}"))
        operations.append((a - b, f"{a}-{b}"))
        operations.append((b - a, f"{b}-{a}"))
        operations.append((a * b, f"{a}*{b}"))
        if b != 0:
            operations.append((a / b, f"{a}/{b}"))
        if a != 0:
            operations.append((b / a, f"{b}/{a}"))
        return operations

    def generate_successors(numbers):
        if len(numbers) == 1:
            return []
        successors = []
        for i, a in enumerate(numbers):
            for j, b in enumerate(numbers):
                if i == j:
                    continue
                remaining_numbers = [numbers[k] for k in range(len(numbers)) if k != i and k != j]
                for result, _ in generate_operations(a, b):
                    new_state = {
                        "state": remaining_numbers + [result],
                        "target": state_dict["target"]
                    }
                    successors.append(new_state)
        return successors

    initial_numbers = state_dict["state"]
    return generate_successors(initial_numbers)


##################################################################################################################################
# Llama 405B



def is_goal_state_20250512_161832(state):
    """
    Checks if the input state is a goal state in the Countdown Game.

    A goal state is reached when there is only one number left in the state's list,
    and that number is equal to the target number.

    Args:
        state (dict): The input state, containing a list of numbers and a target number.

    Returns:
        bool: True if the state is a goal state, False otherwise.
    """
    return len(state["state"]) == 1 and state["state"][0] == state["target"]


def get_successor_states_20250512_161832(state):
    import operator
    
    """
    Returns a list of successor states for the input state in the Countdown Game.

    A successor state is generated by applying one of the four basic arithmetic operations
    (addition, subtraction, multiplication, division) to two numbers in the state's list,
    and replacing the two numbers with the result.

    Args:
        state (dict): The input state, containing a list of numbers and a target number.

    Returns:
        list: A list of successor states.
    """

    def apply_operation(num1, num2, op):
        if op == operator.truediv and num2 == 0:
            return None  # Division by zero is not allowed
        return op(num1, num2)

    successor_states = []
    numbers = state["state"]
    for i in range(len(numbers)):
        for j in range(i + 1, len(numbers)):
            for op in [operator.add, operator.mul]:
                result = apply_operation(numbers[i], numbers[j], op)
                new_state = numbers[:i] + numbers[i + 1:j] + numbers[j + 1:] + [result]
                successor_states.append({"state": new_state, "target": state["target"]})

            for op in [operator.sub, operator.truediv]:
                result1 = apply_operation(numbers[i], numbers[j], op)
                result2 = apply_operation(numbers[j], numbers[i], op)
                if result1 is not None:
                    new_state1 = numbers[:i] + numbers[i + 1:j] + numbers[j + 1:] + [result1]
                    successor_states.append({"state": new_state1, "target": state["target"]})
                if result2 is not None and result2 != result1:
                    new_state2 = numbers[:i] + numbers[i + 1:j] + numbers[j + 1:] + [result2]
                    successor_states.append({"state": new_state2, "target": state["target"]})
    return successor_states


def is_goal_state_20250512_165326(state):
    """
    This function checks if the given state is a goal state in the Countdown Game.
    
    A goal state is defined as a state where the list of numbers contains a single number 
    that is equal to the target number.
    
    Parameters:
    state (dict): The current state of the game, including a list of numbers and a target number.
    
    Returns:
    bool: True if the state is a goal state, False otherwise.
    """
    return len(state["state"]) == 1 and state["state"][0] == state["target"]

def get_successor_states_20250512_165326(state):
    import itertools
    import operator
    
    """
    This function generates all successor states for the given state in the Countdown Game.
    
    A successor state is defined as a state where two numbers from the current state are 
    combined using one of the four basic mathematical operations (addition, subtraction, 
    multiplication, division). The target number remains the same.
    
    Parameters:
    state (dict): The current state of the game, including a list of numbers and a target number.
    
    Returns:
    list: A list of all possible successor states.
    """

    # Define the operations that can be used to generate successor states
    operations = {
        operator.add: '+',
        operator.sub: '-',
        operator.mul: '*',
        operator.truediv: '/'
    }

    # Initialize an empty list to store the successor states
    successor_states = set()

    # Generate all possible pairs of numbers in the current state
    for i, j in itertools.combinations(range(len(state["state"])), 2):
        # Generate the new state by removing the two numbers
        new_state = tuple(sorted(state["state"][:i] + state["state"][i+1:j] + state["state"][j+1:]))

        # Check all possible operations for the current pair of numbers
        for op, op_str in operations.items():
            # Calculate the result of the current operation
            if op == operator.truediv:
                if state["state"][j] != 0:
                    result = op(state["state"][i], state["state"][j])
                    successor_states.add((new_state + (result,), state["target"]))

                if state["state"][i] != 0:
                    result = op(state["state"][j], state["state"][i])
                    successor_states.add((new_state + (result,), state["target"]))

            else:
                result = op(state["state"][i], state["state"][j])
                successor_states.add((new_state + (result,), state["target"]))

                result = op(state["state"][j], state["state"][i])
                successor_states.add((new_state + (result,), state["target"]))

    # Convert set of successor states to list of dictionaries
    successor_states = [{"state": list(s[0]), "target": s[1]} for s in successor_states]

    return successor_states


def is_goal_state_20250512_171238(state):
    """
    Returns True if the given state is a goal state in the Countdown Game.

    :param state: A dictionary containing a list of numbers and a target number.
    :return: True if the state is a goal state, False otherwise.
    """

    numbers = state["state"]
    target = state["target"]

    return len(numbers) == 1 and numbers[0] == target

def get_successor_states_20250512_171238(state):
    import itertools
    import operator
    
    """
    Returns a list of successor states for the given state in the Countdown Game.

    :param state: A dictionary containing a list of numbers and a target number.
    :return: A list of successor states.
    """

    def apply_operation(num1, num2, op):
        if op == operator.truediv and num2 == 0:
            return None
        return op(num1, num2)

    numbers = state["state"]
    target = state["target"]
    operations = [operator.add, operator.sub, operator.mul, operator.truediv]

    successor_states = []

    # Iterate over all pairs of numbers
    for i, j in itertools.combinations(range(len(numbers)), 2):
        # Create a copy of the numbers list without the two selected numbers
        remaining_numbers = numbers[:i] + numbers[i+1:j] + numbers[j+1:]

        # Apply each operation to the two selected numbers
        for op in operations:
            result = apply_operation(numbers[i], numbers[j], op)
            if result is None:
                continue

            # Create a new state with the result of the operation and the remaining numbers
            new_state = {"state": remaining_numbers + [result], "target": target}

            successor_states.append(new_state)

            # Also apply the operation with swapped operands (for non-commutative operations)
            result_swapped = apply_operation(numbers[j], numbers[i], op)
            if result_swapped is not None and result_swapped != result:
                new_state_swapped = {"state": remaining_numbers + [result_swapped], "target": target}
                successor_states.append(new_state_swapped)

    return successor_states


def is_goal_state_20250512_171658(state):
    """
    Check if the given state is a goal state in the Countdown Game.

    Args:
    state (dict): A dictionary containing a list of numbers and a target number.

    Returns:
    bool: True if the state is a goal state, False otherwise.
    """
    return len(state["state"]) == 1 and state["state"][0] == state["target"]


def get_successor_states_20250512_171658(state):
    """
    Generate all possible successor states from the given state in the Countdown Game.

    Args:
    state (dict): A dictionary containing a list of numbers and a target number.

    Returns:
    list: A list of dictionaries representing the successor states.
    """
    import itertools
    import operator

    # Define operators and their corresponding functions
    operators = {
        '+': operator.add,
        '-': operator.sub,
        '*': operator.mul,
        '/': lambda x, y: x / y if y != 0 else float('inf')
    }

    # Get numbers and target from the state
    numbers = state["state"]
    target = state["target"]

    # Generate all possible pairs of numbers and operators
    pairs = list(itertools.combinations(enumerate(numbers), 2))
    successor_states = []

    def calculate_successor_state(pair, op, reverse=False):
        new_numbers = numbers.copy()
        # Remove the two numbers from the list
        del new_numbers[max(pair[0][0], pair[1][0])]
        del new_numbers[min(pair[0][0], pair[1][0])]
        # Append the result to the list
        if reverse:
            new_numbers.append(operators[op](pair[1][1], pair[0][1]))
        else:
            new_numbers.append(operators[op](pair[0][1], pair[1][1]))
        return {"state": new_numbers, "target": target}

    for pair in pairs:
        for op in operators.keys():
            if op in ['+', '*']:
                successor_states.append(calculate_successor_state(pair, op))
            elif op in ['-', '/']:
                successor_states.append(calculate_successor_state(pair, op))
                successor_states.append(calculate_successor_state(pair, op, reverse=True))

    return successor_states


def is_goal_state_20250512_172254(state):
    """
    Returns True if the given state is a goal state, False otherwise.
    
    Args:
        state (dict): A dictionary containing a list of numbers and a target number.
        
    Returns:
        bool: Whether the given state is a goal state.
    """
    return len(state['state']) == 1 and state['state'][0] == state['target']


def get_successor_states_20250512_172254(state):
    import itertools
    import operator
    
    """
    Returns a list of successor states for the given input state in the Countdown Game.
    
    Args:
        state (dict): A dictionary containing a list of numbers and a target number.
        
    Returns:
        list: A list of dictionaries representing the successor states.
    """
    
    # Define the operators to be used
    operators = {
        '+': operator.add,
        '-': operator.sub,
        '*': operator.mul,
        '/': operator.truediv
    }
    
    # Get the numbers and target from the input state
    numbers = state['state']
    target = state['target']
    
    # Initialize an empty list to store the successor states
    successor_states = []
    
    # Iterate over all possible pairs of numbers
    for (i, num1), (j, num2) in itertools.combinations(enumerate(numbers), 2):
        # Create a new list of numbers without num1 and num2
        new_numbers = [n for k, n in enumerate(numbers) if k not in (i, j)]
        
        # Iterate over all operators
        for op_symbol, op in operators.items():
            # Apply the operator to both orderings of the pair
            for a, b in [(num1, num2), (num2, num1)]:
                # Calculate the result of applying the operator
                try:
                    result = op(a, b)
                except ZeroDivisionError:
                    # Skip division by zero
                    continue
                
                # Create a new state with the result and the remaining numbers
                new_state = {'state': new_numbers + [result], 'target': target}
                
                # Add the new state to the list of successor states
                successor_states.append(new_state)
    
    return successor_states

##################################################################################################################################
# Qwen 2.5

def is_goal_state_20250512_175943(state):
    """
    Check if the given state is a goal state.

    Parameters:
    state (dict): A dictionary with keys 'state' (list of integers) and 'target' (integer).

    Returns:
    bool: True if the state is a goal state, False otherwise.
    """
    return len(state['state']) == 1 and state['state'][0] == state['target']

def get_successor_states_20250512_175943(state):
    from itertools import permutations, product
    
    """
    Generate all possible successor states for the given Countdown Game state.
    
    Parameters:
    state (dict): A dictionary with keys 'state' (list of integers) and 'target' (integer).
    
    Returns:
    list: A list of dictionaries, each representing a successor state.
    """
    numbers = state['state']
    target = state['target']
    operations = ['+', '-', '*', '/']
    
    def apply_operation(a, b, op):
        if op == '+':
            return a + b
        elif op == '-':
            return a - b
        elif op == '*':
            return a * b
        elif op == '/':
            if b == 0:
                return None
            return a / b
    
    def generate_new_states(numbers):
        if len(numbers) <= 1:
            return []
        
        successors = []
        for i in range(len(numbers)):
            for j in range(i + 1, len(numbers)):
                for op in operations:
                    # Apply operation in both orders for subtraction and division
                    for (a, b) in [(numbers[i], numbers[j]), (numbers[j], numbers[i])]:
                        result = apply_operation(a, b, op)
                        if result is not None:
                            new_numbers = numbers[:i] + numbers[i+1:j] + numbers[j+1:]
                            new_numbers.append(result)
                            successors.append({"state": new_numbers, "target": target})
        return successors
    
    return generate_new_states(numbers)


def is_goal_state_20250512_183704(state):
    return len(state["state"]) == 1 and state["state"][0] == state["target"]

def get_successor_states_20250512_183704(state):
    import itertools
    
    numbers = state["state"]
    target = state["target"]
    successors = []
    
    def apply_operation(a, b, op):
        if op == '+':
            return a + b
        elif op == '-':
            return a - b
        elif op == '*':
            return a * b
        elif op == '/':
            if b != 0:
                return a / b
        return None
    
    # Generate all pairs of indices
    for (i, a), (j, b) in itertools.combinations(enumerate(numbers), 2):
        # Apply each operation in both orders for subtraction and division
        for op in ['+', '-', '*', '/']:
            result = apply_operation(a, b, op)
            if result is not None:
                # Create a new state with the result and the remaining numbers
                new_numbers = numbers[:]
                new_numbers.pop(j)
                new_numbers.pop(i)
                new_numbers.append(result)
                successors.append({"state": new_numbers, "target": target})
            
            if op in ['-', '/']:
                result = apply_operation(b, a, op)
                if result is not None:
                    # Create a new state with the result and the remaining numbers
                    new_numbers = numbers[:]
                    new_numbers.pop(j)
                    new_numbers.pop(i)
                    new_numbers.append(result)
                    successors.append({"state": new_numbers, "target": target})
    
    return successors


def is_goal_state_20250512_183926(state):
    """
    Determines if the given state is a goal state in the Countdown Game.
    
    Parameters:
    state (dict): A dictionary with a list of numbers and a target number.
                  Example: {"state": [21], "target": 21}
    
    Returns:
    bool: True if the state is a goal state, False otherwise.
    """
    return len(state['state']) == 1 and state['state'][0] == state['target']

def get_successor_states_20250512_183926(state):
    """
    Generates a list of successor states for the given state in the Countdown Game.
    
    Parameters:
    state (dict): A dictionary with a list of numbers and a target number.
                  Example: {"state": [6, 6, 2, 1], "target": 24}
    
    Returns:
    list: A list of successor states, where each state is a dictionary with a list of numbers and the same target number.
    """
    from itertools import permutations, combinations

    numbers = state['state']
    target = state['target']
    successor_states = []

    # Generate all pairs of numbers
    for (i, a), (j, b) in combinations(enumerate(numbers), 2):
        remaining_numbers = numbers[:i] + numbers[i+1:j] + numbers[j+1:]
        
        # Possible operations: addition, subtraction, multiplication, division
        operations = [
            (a + b, f"{a} + {b}"),
            (a - b, f"{a} - {b}"),
            (b - a, f"{b} - {a}"),
            (a * b, f"{a} * {b}"),
        ]
        
        if b != 0:
            operations.append((a / b, f"{a} / {b}"))
        if a != 0:
            operations.append((b / a, f"{b} / {a}"))
        
        for result, operation in operations:
            new_state = remaining_numbers + [result]
            successor_states.append({"state": new_state, "target": target})
    
    return successor_states




def is_goal_state_20250512_191957(state):
    return len(state["state"]) == 1 and state["state"][0] == state["target"]


def get_successor_states_20250512_191957(state):
    from itertools import permutations

    numbers = state["state"]
    target = state["target"]
    n = len(numbers)
    
    def generate_expressions(a, b, op):
        # Create a new list with the two numbers used in the operation removed
        new_numbers = numbers.copy()
        new_numbers.remove(a)
        new_numbers.remove(b)
        # Add the result of the operation to the new list
        new_numbers.append(op(a, b))
        return {"state": new_numbers, "target": target}

    def possible_operations(a, b):
        # Generate all possible operations
        operations = [
            (a + b, lambda x, y: x + y),  # Addition
            (a - b, lambda x, y: x - y),  # Subtraction
            (b - a, lambda x, y: y - x),  # Reverse Subtraction
            (a * b, lambda x, y: x * y),  # Multiplication
            (a / b if b != 0 else None, lambda x, y: x / y),  # Division
            (b / a if a != 0 else None, lambda x, y: y / x)  # Reverse Division
        ]
        return [(res, op) for res, op in operations if res is not None]

    successor_states = []
    for (a, b) in permutations(numbers, 2):
        for (result, op) in possible_operations(a, b):
            new_state = generate_expressions(a, b, op)
            if len(new_state["state"]) == n - 1:
                successor_states.append(new_state)

    return successor_states


def is_goal_state_20250512_193400(state):
    """
    Checks if the given state is a goal state in the Countdown Game.
    
    Parameters:
    state (dict): A dictionary with keys 'state' (list of integers) and 'target' (integer).
    
    Returns:
    bool: True if the state is a goal state, False otherwise.
    """
    return len(state['state']) == 1 and state['state'][0] == state['target']


def get_successor_states_20250512_193400(state):
    from itertools import combinations, permutations
    
    """
    Generates a list of successor states for the given state in the Countdown Game.
    
    Parameters:
    state (dict): A dictionary with keys 'state' (list of integers) and 'target' (integer).
    
    Returns:
    list: A list of dictionaries, each representing a successor state.
    """
    numbers = state['state']
    target = state['target']
    
    def apply_operation(a, b, op):
        if op == '+': return a + b
        elif op == '-': return a - b
        elif op == '*': return a * b
        elif op == '/':
            if b != 0:
                return a / b
            return None
    
    def generate_states(nums):
        if len(nums) == 1:
            return [{'state': nums, 'target': target}]
        
        states = []
        for i, j in combinations(range(len(nums)), 2):
            remaining_nums = [nums[k] for k in range(len(nums)) if k not in [i, j]]
            for x, y in permutations([nums[i], nums[j]]):
                for op in ['+', '-', '*', '/']:
                    result = apply_operation(x, y, op)
                    if result is not None:
                        new_state = {'state': remaining_nums + [result], 'target': target}
                        states.append(new_state)
        return states
    
    successors = generate_states(numbers)
    # Ensure each successor state has exactly one less number than the parent state
    valid_successors = [s for s in successors if len(s['state']) == len(numbers) - 1]
    return valid_successors






get_successor_states_func = {
            "20250509_154448":get_successor_states_20250509_154448,
            "20250509_160231":get_successor_states_20250509_160231,
            "20250509_164317":get_successor_states_20250509_164317,
            "20250509_171121":get_successor_states_20250509_171121,
            "20250509_183049":get_successor_states_20250509_183049,
            # Llama
            "20250512_161832": get_successor_states_20250512_161832,
            "20250512_165326": get_successor_states_20250512_165326,
            "20250512_171238": get_successor_states_20250512_171238,
            "20250512_171658": get_successor_states_20250512_171658,
            "20250512_172254": get_successor_states_20250512_172254,
            # Qwen
            "20250512_175943": get_successor_states_20250512_175943,
            "20250512_183704": get_successor_states_20250512_183704,
            "20250512_183926": get_successor_states_20250512_183926,
            "20250512_191957": get_successor_states_20250512_191957,
            "20250512_193400": get_successor_states_20250512_193400,
            }




is_goal_state_func = {
            # DeepSeek
            "20250509_154448":is_goal_state_20250509_154448,
            "20250509_160231":is_goal_state_20250509_160231,
            "20250509_164317":is_goal_state_20250509_164317,
            "20250509_171121":is_goal_state_20250509_171121,
            "20250509_183049":is_goal_state_20250509_183049,
            # Llama
            "20250512_161832": is_goal_state_20250512_161832,
            "20250512_165326": is_goal_state_20250512_165326,
            "20250512_171238": is_goal_state_20250512_171238,
            "20250512_171658": is_goal_state_20250512_171658,
            "20250512_172254": is_goal_state_20250512_172254,
            # Qwen
            "20250512_175943": is_goal_state_20250512_175943,
            "20250512_183704": is_goal_state_20250512_183704,
            "20250512_183926": is_goal_state_20250512_183926,
            "20250512_191957": is_goal_state_20250512_191957,
            "20250512_193400": is_goal_state_20250512_193400,
            }
##################################################################################################################################



def state_representation(state):
    if isinstance(state, dict):
        # return str(state)
        res = ""
        for key, val in state.items():
            if isinstance(val, list):
                val = sorted(val)
            res += str(key) + ":" + str(val)
        return res
        # return str(state)
    return " ".join(sorted(list([str(s) for s in state])))


def reconstruct_plan(s, Closed):
    plan = []
    current = s
    while current is not None:
        plan.append(current)
        c = state_representation(current)
        current = Closed[c]
    return plan[::-1]


def dfs(state, successor_states, is_goal):
    start_time = time.time()
    expansions = 0
    evaluations = 0
    s = state
    Q = [tuple((s, None))]
    Closed = dict()
    while len(Q) > 0:
        # Get the top from the queue
        s, parent = Q[-1][0], Q[-1][1]
        del Q[-1]
        c = state_representation(s)
        if c in Closed:
            continue
        Closed[c] = parent
        
        if is_goal(s):
            plan = reconstruct_plan(s, Closed)
            print(f"Total time: {time.time() - start_time}")
            print("Solved - Found plan")
            print(plan)
            print(f"Number of expanded nodes is {expansions}")
            print(f"Number of evaluated nodes is {evaluations}")
            return plan
        expansions += 1

        successors = successor_states(s)
        for t in successors:
            Q.append(tuple((t,s)))
            evaluations += 1
    print(f"Total time: {time.time() - start_time}")
    print(f"Number of expanded nodes is {expansions}")
    print(f"Number of evaluated nodes is {evaluations}")
    return None


def validate_transition_full(s, t):
    def ms(s):
        res = {}
        for x in s:
            if x not in res:
                res[x] = 0
            res[x] += 1
        return res
    def ms_minus(s, t):
        res = {}
        for x, c in s.items():
            res[x] = max(c - t.get(x, 0), 0)
        return res
    def ms_to_list(s):
        res = []
        for x, c in s.items():
            for i in range(c):
                res.append(x)
        return res

    if len(s) - len(t) != 1:
        print("Invalid transformation: length mismatch")
        return False
        
    mss = ms(s)
    mst = ms(t)
    f_nums = ms_to_list(ms_minus(mss, mst))
    t_nums = ms_to_list(ms_minus(mst, mss))
    if len(t_nums) == 0 and len(f_nums) == 1:
        x = f_nums[0]
        # Check if x op y = y 
        if math.isclose(x, 0, abs_tol = 0.001) or math.isclose(x, 1, abs_tol = 0.001):
            return True
        other = ms_to_list(ms_minus(mss, ms([x])))
        for y in other:
            if math.isclose(x, 2*y, abs_tol = 0.001) or math.isclose(x, y*y, abs_tol = 0.001) or math.isclose(y, 0, abs_tol = 0.001):
                return True
        print("No valid operation found to transform state")
        return False
    assert(len(f_nums) == 2)
    assert(len(t_nums) == 1)
    x, y = f_nums[0], f_nums[1]
    z = t_nums[0]
    if math.isclose(x+y, z, abs_tol = 0.001) or math.isclose(x-y, z, abs_tol = 0.001) or \
        math.isclose(y-x, z, abs_tol = 0.001) or math.isclose(x*y, z, abs_tol = 0.001) or \
            (y != 0 and math.isclose(x / y, z, abs_tol = 0.001)) or \
            (x != 0 and math.isclose(y / x, z, abs_tol = 0.001)):
        return True
    print("No valid operation found to transform state")
    return False

def validate_solution(plan, target):
    if plan is None:
        print("No plan")
        return False
    if not plan or plan[-1]["state"] != [target]:
        print(f'Final state is {plan[-1]["state"]}, not [{target}]')
        return False
        
    for i in range(len(plan) - 1):
        s = plan[i]["state"]
        t = plan[i + 1]["state"]
        valid = validate_transition_full(s, t)
        if not valid:
            print("Input state", s)
            print("Successor state", t)
            return False
        
    return True

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("--state", type=str, required=True)
    parser.add_argument("--target", type=int, required=True)
    parser.add_argument("--code-id", type=str, required=True)

    args = parser.parse_args()

    state = {"state": [int(x) for x in args.state.split(" ")], "target": args.target }

    get_successor_states = get_successor_states_func[args.code_id]
    is_goal_state = is_goal_state_func[args.code_id]

    plan = dfs(state, get_successor_states, is_goal_state)
    if validate_solution(plan, args.target):
        print("Plan is valid")
    else:
        print("Plan is not valid")
