#!<PATH_REMOVED> python3
import sys
import subprocess
import threading
import time
import os
import shutil
import search  
import importlib.util

#import PDDL_tranlation

# import inference
# import re
# import json

def run_script_with_timeout(script_name, args, output_file_path, time_limit=1800):
    cmd = ['python', '-u', script_name] + args
    proc = subprocess.Popen(
        cmd,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        text=True,
        bufsize=1  # line-buffered
    )

    def kill_process(p):
        p.kill()

    timer = threading.Timer(time_limit, kill_process, [proc])
    timer.start()

    error_message = ""
    success = True
    try:
        with open(output_file_path, 'a', encoding='utf-8') as f:
            while True:
                line = proc.stdout.readline()
                if not line and proc.poll() is not None:
                    break
                if line:
                    print(line, end='')  # echo to terminal
                    f.write(line)
                    f.flush()

            err = proc.stderr.read()
            if err:
                print(err, end='')
                f.write(err)
                f.flush()
                error_message += err
    finally:
        timer.cancel()
        proc.stdout.close()
        proc.stderr.close()
        return_code = proc.wait()
        if return_code != 0:
            success = False
            if not error_message:
                error_message = "Non-zero return code or process was killed/timed out."

    return success, error_message

def run_beam_search(problem_name, problem_path, time_limit=60000, extra_args=None):
    if extra_args is None:
        extra_args = []
    cmd = ['python', '-u', 'search.py', problem_name, problem_path]
    cmd.extend(extra_args)

    process = subprocess.Popen(
        cmd,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        text=True,
        bufsize=1
    )

    def kill_process(p):
        print(f"[TIMEOUT] Killed search for {problem_path} due to time limit.")
        p.kill()

    timer = threading.Timer(time_limit, kill_process, [process])
    timer.start()

    try:
        output_file_path = os.path.join(problem_path, 'output.txt')
        with open(output_file_path, 'w', encoding='utf-8') as f:
            while True:
                line = process.stdout.readline()
                if not line and process.poll() is not None:
                    break
                if line:
                    print(line, end='')
                    f.write(line)
                    f.flush()

            # Read any remaining stderr
            error_output = process.stderr.read()
            if error_output:
                print(error_output, end='')
                f.write(error_output)
                f.flush()
    finally:
        timer.cancel()
        process.stdout.close()
        process.stderr.close()
        process.wait()


def validate_plan(problem_path):
    domain_pddl_path = os.path.join(problem_path, 'domain.pddl')
    problem_pddl_path = os.path.join(problem_path, 'problem.pddl')
    plan_pddl_path = os.path.join(problem_path, 'plan.pddl')

    cmd = ['validate.py', '-v', '-t', '0.01', domain_pddl_path, problem_pddl_path, plan_pddl_path]
    try:
        result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
        val_output = result.stdout
        is_valid = ('Plan valid' in val_output)
        return is_valid, val_output
    except Exception as e:
        return False, f"Error running VAL: {e}"

def main():
    if len(sys.argv) < 5:
        print("Usage: python instances.py <domain_name> <initial> <end> <setup_domain> "
              "[--ParamName=Value] [--OtherParam=Value] ...")
        sys.exit(1)

    domain_name = sys.argv[1]
    initial = int(sys.argv[2])
    end = int(sys.argv[3])
    setup_domain = sys.argv[4].lower() == 'true'

    domain_path = f'.<PATH_REMOVED>'
    instances_folder = f'.<PATH_REMOVED>'

    module_path = f'.<PATH_REMOVED>'
    module_name = 'PDDL_tranlation'
    spec = importlib.util.spec_from_file_location(module_name, module_path)
    module = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(module)

    optional_search_args = sys.argv[5:]

    domain_folder = os.path.join(os.getcwd(), domain_name)
    os.makedirs(domain_folder, exist_ok=True)

    if setup_domain:
        domain_output_file = os.path.join(domain_folder, 'output.txt')
        with open(domain_output_file, 'w', encoding='utf-8'):
            pass

        module.setup_domain(domain_path, domain_name)

        script1 = ".<PATH_REMOVED>"
        success1, err1 = run_script_with_timeout(script1, [domain_name], domain_output_file, time_limit=2000)
        if not success1:
            print(f"[ERROR] {script1} failed. Stopping. Error:\n{err1}")
            #sys.exit(1)
        script2 = ".<PATH_REMOVED>"
        success2, err2 = run_script_with_timeout(script2, [domain_name], domain_output_file, time_limit=2000)
        if not success2:
            print(f"[ERROR] {script2} failed. Stopping. Error:\n{err2}")
            #sys.exit(1)
        script3 = ".<PATH_REMOVED>"
        success3, err3 = run_script_with_timeout(script3, [domain_name], domain_output_file, time_limit=2000)
        if not success3:
            print(f"[ERROR] {script3} failed. Stopping. Error:\n{err3}")
            #sys.exit(1)

        print("[INFO] Domain setup scripts completed successfully.")

    count_valid = 0
    total_count = 0

    for instance_id in range(initial, end):
        print(f"Started instance {instance_id}")
        instance_file = os.path.join(instances_folder, f"instance-{instance_id}.pddl")

        if not os.path.exists(instance_file):
            print(f"[WARNING] Instance file {instance_file} does not exist. Skipping.")
            continue

        total_count += 1

       
        instance_subdir_name = f"{domain_name}_instance_{instance_id}"
        problem_path = os.path.join(domain_folder, instance_subdir_name)
        os.makedirs(problem_path, exist_ok=True)

        destination_domain_pddl = os.path.join(problem_path, 'domain.pddl')
        destination_problem_pddl = os.path.join(problem_path, 'problem.pddl')

        shutil.copy(domain_path, destination_domain_pddl)
        shutil.copy(instance_file, destination_problem_pddl)

        module.setup_instance(domain_path, instance_file, domain_name, instance_subdir_name)
        

        run_beam_search(instance_subdir_name, problem_path, time_limit=60000, extra_args=optional_search_args)

        plan_pddl_path = os.path.join(problem_path, 'plan.pddl')
        is_valid, val_output = validate_plan(problem_path)

        if is_valid:
            folder_suffix = "_correct"
            message = "plan is valid!!"
            count_valid += 1
        else:
            if not os.path.exists(plan_pddl_path):
                folder_suffix = "_incomplete"
                message = "no plan produced"
            else:
                folder_suffix = "_incorrect"
                message = "plan is not valid"

        validation_folder = os.path.join(problem_path, folder_suffix)
        os.makedirs(validation_folder, exist_ok=True)

        val_output_path = os.path.join(validation_folder, 'val_output.txt')
        with open(val_output_path, 'w', encoding='utf-8') as f:
            f.write(val_output + "\n" + message)

        if os.path.exists(plan_pddl_path):
            shutil.copy(plan_pddl_path, validation_folder)

        print(message)
        print(f"Finished processing instance {instance_id}")

    if total_count > 0:
        ratio = round(count_valid / total_count, 2) * 100
        print(f"{count_valid} plan(s) valid out of {total_count} total. ({ratio}%)")
    else:
        print("No valid instance files were processed.")

if __name__ == "__main__":
    main()