import tkinter as tk
from tkinter import messagebox, Tk, Label, Spinbox, Button, Text, END, StringVar, OptionMenu
from tkinter.scrolledtext import ScrolledText
import threading
import subprocess as sub
import os
import sys
import shutil
import queue
from datetime import datetime
import re
import ast

# Initialize a thread-safe queue for inter-thread communication
msg_queue = queue.Queue()

# Initialize the main window
mainwin = Tk()
mainwin.title('Codeforces Hack Assistant')

# GUI Layout
# Language Selection for Source Solution
source_lang_var = StringVar(value="C++")  # Default to C++
source_lang_label = Label(mainwin, text="Language:")
source_lang_label.grid(row=0, column=0, padx=(20, 0), pady=(10, 0), sticky='w')
source_lang_menu = OptionMenu(mainwin, source_lang_var, "C++", "Python")
source_lang_menu.grid(row=0, column=1, padx=(0, 20), pady=(10, 0), sticky='w')

# Language Selection for Compare Solution
compare_lang_var = StringVar(value="C++")  # Default to C++
compare_lang_label = Label(mainwin, text="Language:")
compare_lang_label.grid(row=0, column=16, padx=(20, 0), pady=(10, 0), sticky='w')
compare_lang_menu = OptionMenu(mainwin, compare_lang_var, "C++", "Python")
compare_lang_menu.grid(row=0, column=17, padx=(0, 20), pady=(10, 0), sticky='w')

Label(mainwin, text="Your Solution (C++/Python)").grid(row=1, column=0, columnspan=15)
Label(mainwin, text="Solution to Hack (C++/Python)").grid(row=1, column=16, columnspan=15)

source = ScrolledText(mainwin, height=10, width=60)
source.grid(row=2, column=0, padx=20, pady=(0, 50), columnspan=15)

compare = ScrolledText(mainwin, height=10, width=60)
compare.grid(row=2, column=16, padx=20, pady=(0, 50), columnspan=15)

Label(mainwin, text="Test Case Generator (PYTHON)").grid(row=3, column=0, columnspan=15)
generator = ScrolledText(mainwin, height=10, width=60)
generator.grid(row=4, rowspan=3, column=0, padx=20, pady=(0, 50), columnspan=15)

Label(mainwin, text="Number of Threads: ").grid(row=4, column=17, columnspan=5)
spin = Spinbox(mainwin, from_=1, to=16)
spin.grid(row=4, column=22)

var1 = tk.IntVar()
checkbox = tk.Checkbutton(mainwin, text='Use Test Case Instead', variable=var1, onvalue=1, offvalue=0)
checkbox.grid(row=5, column=22)

# Initialize the log text area at startup to prevent AttributeError
tex = Text(mainwin, height=10, width=120)
tex.grid(row=7, padx=20, pady=(0, 20), column=0, columnspan=30)
tex.configure(state='disabled')

# Define a red tag for highlighting errors and mismatches
tex.tag_configure('red', foreground='red')

# Initialize global variables
execute = None
exit_btn = None
continue_executing = True
workers = []
test_num = 1

mutex = threading.Lock()
available = threading.Lock()

# Create 'usable' directory if it doesn't exist
if not os.path.exists("usable"):
    os.mkdir("usable")

def on_closing():
    if messagebox.askokcancel("Quit", "Do you want to quit?"):
        stop_all_workers()
        if os.path.exists("usable"):
            shutil.rmtree("usable")
        mainwin.destroy()

def printf(input_text, end_char="\n"):
    """
    Enqueue messages to be printed in the GUI with timestamps.
    """
    timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    msg_queue.put((f"[{timestamp}] {input_text}", end_char))

def passed():
    """
    Enqueue a message indicating a passed test.
    """
    global test_num
    with mutex:
        msg_queue.put((f"Test Passed: {test_num}", "\n"))
        test_num += 1

def stop_all_workers():
    """
    Signal all worker threads to stop and wait for them to finish.
    """
    global continue_executing, workers, test_num
    continue_executing = False
    current_thread = threading.current_thread()
    for i, worker in enumerate(workers.copy()):
        if worker != current_thread:
            worker.join(timeout=1)
            msg_queue.put((f"Closed Worker: {i + 1}", "\n"))
    workers.clear()
    test_num = 1
    continue_executing = True
    # Re-enable GUI elements through queue
    msg_queue.put(("ENABLE_GUI", ""))

def reset_gui_after_failure():
    """
    Reset GUI elements after a hack failure to allow re-execution.
    """
    # Re-enable GUI elements
    checkbox.configure(state='normal')
    execute.configure(state='normal')
    spin.configure(state='normal')
    source.configure(state='normal')
    compare.configure(state='normal')
    generator.configure(state='normal')
    exit_btn.configure(state='disabled')

def rewrite(path, data):
    """
    Write data to a file at the specified path.
    """
    with open(path, "w") as raw:
        raw.write(data)

def parse_cpp_inputs(cpp_code):
    """
    Parses C++ code to extract input variables and their types.
    """
    # Regex to capture variable declarations (supports basic types)
    var_decl_regex = re.compile(r'\b(int|long_long|long|double|float|string)\s+(\w+);')
    
    # Regex to capture cin >> lines
    cin_regex = re.compile(r'cin\s*>>\s*(.+?);')
    
    # Map to store variable types
    var_types = {}
    
    # Find all variable declarations
    for match in var_decl_regex.finditer(cpp_code):
        var_type = match.group(1)
        var_name = match.group(2)
        var_types[var_name] = var_type
    
    # Find all cin >> lines and extract variables
    inputs = []
    for match in cin_regex.finditer(cpp_code):
        vars_part = match.group(1)
        # Split by '>>' and possible multiple variables
        vars = [v.strip() for v in vars_part.split('>>')]
        inputs.extend(vars)
    
    # Remove duplicates while preserving order
    seen = set()
    inputs = [x for x in inputs if not (x in seen or seen.add(x))]
    
    # Get types for input variables
    input_vars = []
    for var in inputs:
        var_type = var_types.get(var, 'int')  # default to int if type not found
        input_vars.append((var, var_type))
    
    return input_vars

def parse_python_inputs(python_code):
    """
    Parses Python code to extract input variables and their types using AST.
    Assumes input is read using int(input()), float(input()), etc.
    """
    class InputVisitor(ast.NodeVisitor):
        def __init__(self):
            self.inputs = []  # List of tuples: (variable_name, type)
        
        def visit_Assign(self, node):
            # Look for assignments like x = int(input())
            if isinstance(node.value, ast.Call):
                func = node.value.func
                if isinstance(func, ast.Name) and func.id in ['int', 'float', 'str']:
                    # Assuming single target assignment
                    if len(node.targets) == 1 and isinstance(node.targets[0], ast.Name):
                        var_name = node.targets[0].id
                        var_type = func.id
                        self.inputs.append((var_name, var_type))
            self.generic_visit(node)
        
        def visit_AugAssign(self, node):
            # Handle augmented assignments if necessary
            self.generic_visit(node)
    
    try:
        tree = ast.parse(python_code)
        visitor = InputVisitor()
        visitor.visit(tree)
        return visitor.inputs
    except Exception as e:
        return []  # In case of parsing errors

def generate_test_case_generator_cpp(input_vars, test_cases=10):
    """
    Generates Python test case generator code based on C++ input variables and their types.
    """
    generator_code = """import random

def generate_test_input():
    t = random.randint(1, {test_cases})
    print(t)
    for _ in range(t):
""".format(test_cases=test_cases)
    
    for var, var_type in input_vars:
        indent = ' ' * 8
        if var_type == 'int':
            generator_code += f"{indent}{var} = random.randint(1, 10**9)\n"
        elif var_type in ['long_long', 'long']:
            generator_code += f"{indent}{var} = random.randint(1, 10**18)\n"
        elif var_type in ['double', 'float']:
            generator_code += f"{indent}{var} = random.uniform(1.0, 10**9)\n"
        elif var_type == 'string':
            generator_code += f"{indent}{var} = ''.join(random.choices('abcdefghijklmnopqrstuvwxyz', k=10))\n"
        else:
            generator_code += f"{indent}{var} = 0  # Unknown type: {var_type}\n"
        generator_code += f"{indent}print({var})\n"
    
    generator_code += "\n# Example usage:\n# generate_test_input()\n"
    
    return generator_code

def generate_test_case_generator_python(input_vars, test_cases=10):
    """
    Generates Python test case generator code based on Python input variables and their types.
    """
    generator_code = """import random

def generate_test_input():
    t = random.randint(1, {test_cases})
    print(t)
    for _ in range(t):
""".format(test_cases=test_cases)
    
    for var, var_type in input_vars:
        indent = ' ' * 8
        if var_type == 'int':
            generator_code += f"{indent}{var} = random.randint(1, 10**9)\n"
        elif var_type == 'float':
            generator_code += f"{indent}{var} = random.uniform(1.0, 10**9)\n"
        elif var_type == 'str':
            generator_code += f"{indent}{var} = ''.join(random.choices('abcdefghijklmnopqrstuvwxyz', k=10))\n"
        else:
            generator_code += f"{indent}{var} = 0  # Unknown type: {var_type}\n"
        generator_code += f"{indent}print({var})\n"
    
    generator_code += "\n# Example usage:\n# generate_test_input()\n"
    
    return generator_code

def auto_generate_generator():
    """
    Automatically generates the test case generator based on the source solution code.
    Detects whether the source is C++ or Python and parses accordingly.
    Allows further editing after generation.
    """
    source_language = source_lang_var.get()
    source_code = source.get(1.0, END)
    
    if source_language == "C++":
        input_vars = parse_cpp_inputs(source_code)
        if not input_vars:
            printf("No input variables found. Please ensure your C++ code contains input reading via 'cin'.", "")
            return
        generator_code = generate_test_case_generator_cpp(input_vars, test_cases=10)
    elif source_language == "Python":
        input_vars = parse_python_inputs(source_code)
        if not input_vars:
            printf("No input variables found. Please ensure your Python code contains input reading using int(input()), float(input()), or str(input()).", "")
            return
        generator_code = generate_test_case_generator_python(input_vars, test_cases=10)
    else:
        printf("Unsupported source language selected.", "")
        return
    
    # Populate the generator text area with the generated code
    generator.configure(state='normal')
    generator.delete(1.0, END)
    generator.insert(END, generator_code)
    generator.configure(state='normal')  # Keep it editable after auto-generation
    
    # Optionally, write the generator code to the 'usable' directory
    if source_language == "C++":
        rewrite("usable/generator.py", generator_code)
    elif source_language == "Python":
        rewrite("usable/generator.py", generator_code)
    
    printf("Test case generator auto-generated successfully.", "")

def execut(name, input_data, language="C++", num=0):
    """
    Execute a program (Python or C++) and return its output and errors.
    """
    if language == "Python":
        # Execute Python script using the current Python interpreter
        command = [sys.executable, f'{name}.py']
        try:
            p = sub.Popen(command, stdout=sub.PIPE, stderr=sub.PIPE, cwd='usable')
            output, errors = p.communicate(timeout=10)
        except sub.TimeoutExpired:
            p.kill()
            output, errors = p.communicate()
        except FileNotFoundError:
            msg_queue.put(("RED:Python interpreter not found.", ""))
            return "", "Python interpreter not found."
        return output.decode('utf-8'), errors.decode('utf-8')

    elif language == "C++":
        # Prepare input file
        input_file = f"usable/input{num}.txt"
        rewrite(input_file, str(input_data))
    
        # Execute C++ program
        executable = f"./{name}" if os.name != 'nt' else f"{name}.exe"
        command = [executable]
    
        try:
            with open(input_file, 'r') as f:
                p = sub.Popen(command, stdout=sub.PIPE, stderr=sub.PIPE, cwd='usable', stdin=f)
                output, errors = p.communicate(timeout=10)
        except FileNotFoundError:
            msg_queue.put((f"RED:Executable {name} not found in 'usable' directory.", ""))
            return "", "Executable not found."
        except sub.TimeoutExpired:
            p.kill()
            output, errors = p.communicate()
        return output.decode('utf-8'), errors.decode('utf-8')

    else:
        msg_queue.put((f"RED:Unknown language: {language}", ""))
        return "", f"Unknown language: {language}"

def compares(input_text, pp):
    """
    Compare the outputs of the source and hack solutions.
    """
    # Retrieve language selections
    source_language = source_lang_var.get()
    compare_language = compare_lang_var.get()
    
    # Execute Source Solution
    a_output, a_errors = execut(f'source{pp}', input_text, language=source_language)
    # Execute Compare Solution
    b_output, b_errors = execut(f'compare{pp}', input_text, language=compare_language)

    if a_errors.strip():
        msg_queue.put((f"RED:Runtime Error on Source!\nInput:\n{input_text}\n\nError:\n{a_errors}\nExiting Thread..", ""))
        stop_all_workers()
        msg_queue.put(("ENABLE_GUI", ""))
        return False
    if b_errors.strip():
        msg_queue.put((f"RED:Runtime Error on Hack!\nInput:\n{input_text}\n\nError:\n{b_errors}\nExiting Thread..", ""))
        stop_all_workers()
        msg_queue.put(("ENABLE_GUI", ""))
        return False
    if a_output.strip() != b_output.strip():
        mismatch_message = (
            f"RED:Output Mismatch Detected!\n\n"
            f"Input:\n{input_text}\n\n"
            f"Your Solution Output:\n{a_output}\n\n"
            f"Hack Solution Output:\n{b_output}\n\n"
            f"Exiting Thread.."
        )
        msg_queue.put((mismatch_message, ""))
        stop_all_workers()
        msg_queue.put(("ENABLE_GUI", ""))
        return False
    return True

def run(pp, single=False):
    """
    Worker thread function to compile and execute code.
    """
    global continue_executing
    with available:
        if not continue_executing:
            return

        # Retrieve language selections
        source_language = source_lang_var.get()
        compare_language = compare_lang_var.get()

        # Handle Source Language
        if source_language == "C++":
            compile_source = ['clang++', 'source.cpp', '-o', f'source{pp}']
            try:
                p = sub.Popen(compile_source, stdout=sub.PIPE, stderr=sub.PIPE, cwd='usable')
                output, errors = p.communicate()
                errors = errors.decode('utf-8')
                if errors.strip():
                    msg_queue.put(("RED:Source Compilation Error!\nExiting..", ""))
                    stop_all_workers()
                    msg_queue.put(("ENABLE_GUI", ""))
                    return
                # Set executable permissions (Unix-based systems)
                if os.name != 'nt':
                    os.chmod(f'usable/source{pp}', 0o755)
            except FileNotFoundError:
                msg_queue.put(("RED:clang++ not found. Please ensure it is installed and added to PATH.", ""))
                stop_all_workers()
                msg_queue.put(("ENABLE_GUI", ""))
                return
        elif source_language == "Python":
            # Write per-thread source Python file
            source_code = source.get(1.0, END)
            rewrite(f"usable/source{pp}.py", source_code)
        else:
            msg_queue.put((f"RED:Unsupported source language: {source_language}", ""))
            stop_all_workers()
            msg_queue.put(("ENABLE_GUI", ""))
            return

        # Handle Compare Language
        if compare_language == "C++":
            compile_compare = ['clang++', 'compare.cpp', '-o', f'compare{pp}']
            try:
                p = sub.Popen(compile_compare, stdout=sub.PIPE, stderr=sub.PIPE, cwd='usable')
                output, errors = p.communicate()
                errors = errors.decode('utf-8')
                if errors.strip():
                    msg_queue.put(("RED:Solution to Hack Compilation Error!\nExiting..", ""))
                    stop_all_workers()
                    msg_queue.put(("ENABLE_GUI", ""))
                    return
                # Set executable permissions (Unix-based systems)
                if os.name != 'nt':
                    os.chmod(f'usable/compare{pp}', 0o755)
            except FileNotFoundError:
                msg_queue.put(("RED:clang++ not found. Please ensure it is installed and added to PATH.", ""))
                stop_all_workers()
                msg_queue.put(("ENABLE_GUI", ""))
                return
        elif compare_language == "Python":
            # Write per-thread compare Python file
            compare_code = compare.get(1.0, END)
            rewrite(f"usable/compare{pp}.py", compare_code)
        else:
            msg_queue.put((f"RED:Unsupported compare language: {compare_language}", ""))
            stop_all_workers()
            msg_queue.put(("ENABLE_GUI", ""))
            return

        # If not running in single mode, update the generator script
        if not single:
            # Ensure generator script is thread-specific
            generator_code = generator.get(1.0, END)
            rewrite(f"usable/generator{pp}.py", generator_code)

    while continue_executing:
        if single:
            # Use the provided test case instead of generating one
            test_case = generator.get(1.0, END).strip()
            if not test_case:
                msg_queue.put(("RED:No test case provided.\nExiting..", ""))
                stop_all_workers()
                msg_queue.put(("ENABLE_GUI", ""))
                return
            result = compares(test_case, pp)
            if result:
                passed()
            # After a single test case, stop execution to allow user modification
            stop_all_workers()
            return

        # Execute the generator to produce a test case
        generate_output, generate_errors = execut(f'generator{pp}', "", language="Python")
        if generate_errors.strip():
            msg_queue.put((f"RED:Generator Error!\n{generate_errors}\nExiting..", ""))
            stop_all_workers()
            msg_queue.put(("ENABLE_GUI", ""))
            return

        # Compare the outputs of the source and hack solutions
        result = compares(generate_output, pp)
        if result:
            passed()
        else:
            # If mismatch, stop and allow user to edit generator
            return

def printer():
    """
    Start the execution process by compiling code and initiating worker threads.
    """
    global execute, exit_btn

    # Clear previous logs
    tex.configure(state='normal')
    tex.delete(1.0, END)
    tex.configure(state='disabled')
    
    # Write the source and compare code to the 'usable' directory based on selected languages
    source_language = source_lang_var.get()
    compare_language = compare_lang_var.get()

    if source_language == "C++":
        rewrite("usable/source.cpp", source.get(1.0, END))
    elif source_language == "Python":
        # Initialize with source0.py for the first thread
        rewrite("usable/source0.py", source.get(1.0, END))
    else:
        # This should not happen as options are limited to C++ and Python
        printf("Unsupported Source Language Selected.", "")
        return

    if compare_language == "C++":
        rewrite("usable/compare.cpp", compare.get(1.0, END))
    elif compare_language == "Python":
        # Initialize with compare0.py for the first thread
        rewrite("usable/compare0.py", compare.get(1.0, END))
    else:
        # This should not happen as options are limited to C++ and Python
        printf("Unsupported Compare Language Selected.", "")
        return

    # Disable GUI elements during execution
    checkbox.configure(state='disabled')
    execute.configure(state='disabled')
    spin.configure(state='disabled')
    source.configure(state='disabled')
    compare.configure(state='disabled')
    generator.configure(state='disabled')
    exit_btn.configure(state='normal')

    if var1.get() == 1:
        # Single-threaded mode using provided test case
        p = threading.Thread(target=run, args=(str(0), True), name='0', daemon=True)
        p.start()
        workers.append(p)
        printf("Started Thread: 1")
    else:
        # Multi-threaded mode with generated test cases
        for i in range(int(spin.get())):
            p = threading.Thread(target=run, args=(str(i),), name=str(i), daemon=True)
            p.start()
            workers.append(p)
            printf(f"Started Thread: {i + 1}")
    return

def process_queue():
    """
    Periodically check the queue and update the GUI.
    """
    try:
        while True:
            message, end_char = msg_queue.get_nowait()
            if message.startswith("RED:"):
                # Remove the 'RED:' prefix
                clean_message = message[4:]
                tex.configure(state='normal')
                tex.insert(tk.END, clean_message + end_char, 'red')
                tex.see(tk.END)
                tex.configure(state='disabled')
            elif message == "ENABLE_GUI":
                # Re-enable GUI elements
                reset_gui_after_failure()
                printf("Ready for new execution.", "")
            else:
                tex.configure(state='normal')
                tex.insert(tk.END, message + end_char)
                tex.see(tk.END)
                tex.configure(state='disabled')
    except queue.Empty:
        pass
    finally:
        mainwin.after(100, process_queue)  # Check the queue every 100 ms

# Initialize Execute, Stop, and Auto-Generate Generator buttons
execute = Button(mainwin, text="Execute", command=printer)
execute.grid(row=6, column=18, columnspan=5, sticky="EW", pady=(0, 25))

exit_btn = Button(mainwin, text="Stop", command=stop_all_workers)
exit_btn.grid(row=6, column=25, columnspan=3, sticky="EW", pady=(0, 25))
exit_btn.configure(state='disabled')

# Initialize Auto-Generate Generator button
auto_gen_btn = Button(mainwin, text="Auto-Generate Generator", command=auto_generate_generator)
auto_gen_btn.grid(row=3, column=16, columnspan=15, sticky="EW", padx=20, pady=(10, 0))

# Handle window closing event
mainwin.protocol("WM_DELETE_WINDOW", on_closing)

# Start processing the queue
mainwin.after(100, process_queue)

# Start the Tkinter event loop
mainwin.mainloop(). Your task: Currently your solution output does not show anything when comparing to hack solution output, you need to add it, also you listed how many test cases have passed (i.e that same between the two codes)