"""sim_controller.py - Verilator simulation controller.

Handles Verilator compilation and execution of RTL testbenches.
"""

import subprocess
import os
import time


class SimController:
    """Controls Verilator compilation and simulation."""

    def __init__(self, config: dict):
        """Initialize with test framework configuration.

        Args:
            config: The loaded config.json dict.
        """
        self.config = config
        self.verilator_path = config.get('verilator', {}).get('path', 'verilator')
        self.compile_args = config.get('verilator', {}).get('compile_args', [])

    def compile(self, top_module: str, rtl_files: list[str], tb_cpp: str,
                work_dir: str, inc_dirs: list[str] = None) -> str:
        """Compile RTL with Verilator and return path to executable.

        Args:
            top_module: Name of the top-level Verilog module.
            rtl_files: List of Verilog source file paths.
            tb_cpp: Path to C++ testbench file.
            work_dir: Working directory for compilation output (-Mdir).
            inc_dirs: List of include directories for `include directives.

        Returns:
            Path to the compiled executable.

        Raises:
            RuntimeError: If Verilator is not found or compilation fails.
        """
        os.makedirs(work_dir, exist_ok=True)

        # Build verilator command
        cmd = [self.verilator_path]
        cmd.extend(self.compile_args)
        cmd.extend(['--top-module', top_module])
        # No explicit -std= flag; let Verilator use its default (C++14+)
        if inc_dirs:
            for d in inc_dirs:
                cmd.append(f'+incdir+{d}')
        cmd.extend(rtl_files)
        cmd.append(tb_cpp)
        cmd.extend(['-Mdir', work_dir])

        try:
            result = subprocess.run(
                cmd,
                capture_output=True,
                text=True,
                timeout=120,
                cwd=os.getcwd()
            )
        except subprocess.TimeoutExpired:
            raise RuntimeError("Verilator compilation timed out.")
        except FileNotFoundError:
            raise RuntimeError(
                f"Verilator not found at '{self.verilator_path}'."
            )

        if result.returncode != 0:
            tail = result.stderr[-800:] if result.stderr else '(no stderr)'
            raise RuntimeError(f"Verilator compilation failed:\n{tail}")

        # Executable is at <work_dir>/V<top_module>
        executable = os.path.join(work_dir, f'V{top_module}')
        if not os.path.exists(executable):
            raise RuntimeError(
                f"Compilation succeeded but executable not found: {executable}"
            )
        return executable

    def run(self, executable: str, vector_file: str,
            timeout_s: int = 30) -> dict:
        """Run the compiled simulation executable.

        Args:
            executable: Path to the compiled executable.
            vector_file: Path to the hex vector file.
            timeout_s: Timeout in seconds.

        Returns:
            dict with keys: success, results, elapsed, stderr.
        """
        if not os.path.exists(executable):
            return {
                'success': False,
                'results': [],
                'elapsed': 0.0,
                'stderr': f'Executable not found: {executable}'
            }

        start_time = time.time()
        try:
            result = subprocess.run(
                [executable, f'+VECTOR_FILE={vector_file}'],
                capture_output=True,
                text=True,
                timeout=timeout_s
            )
            elapsed = time.time() - start_time

            # Parse stdout for "RESULT: XXX" lines
            results = []
            for line in result.stdout.splitlines():
                line = line.strip()
                if line.startswith('RESULT:'):
                    hex_val = line.split(':', 1)[1].strip()
                    results.append(hex_val)

            success = result.returncode == 0 and len(results) > 0
            return {
                'success': success,
                'results': results,
                'elapsed': elapsed,
                'stderr': result.stderr
            }
        except subprocess.TimeoutExpired:
            elapsed = time.time() - start_time
            return {
                'success': False,
                'results': [],
                'elapsed': elapsed,
                'stderr': 'Simulation timed out.'
            }
