//! BET FPGA Simulation Wrapper (Phase 6.1)
//!
//! Generates a self-contained Icarus Verilog testbench from BET bytecode.
//! The testbench instantiates `bet_processor` with an inline ROM initialised
//! from the program bytes, drives clock/reset, and prints register state on halt.
//!
//! ## Usage
//! ```no_run
//! use ternlang_hdl::sim::BetSimEmitter;
//! let prog = vec![0x01u8, 0x02, 0x00]; // PUSH +1, PUSH -1, HALT
//! let tb = BetSimEmitter::new().emit_testbench(&prog);
//! std::fs::write("sim_tb.v", &tb).unwrap();
//! // then: iverilog -o sim_tb.vvp sim_tb.v && vvp sim_tb.vvp
//! ```
//!
//! ## Trit encoding reminder
//! ```text
//!   2'b01  →  -1 (conflict)
//!   2'b10  →  +1 (truth)
//!   2'b11  →   0 (hold, active neutral)
//!   2'b00  → FAULT
//! ```

use crate::verilog::VerilogEmitter;
use crate::isa::BetIsaEmitter;

/// Maximum simulation cycles before the testbench forces `$finish`.
const DEFAULT_MAX_CYCLES: usize = 1000;

/// Generates Icarus Verilog simulation testbenches for BET programs.
pub struct BetSimEmitter {
    max_cycles: usize,
}

impl BetSimEmitter {
    pub fn new() -> Self {
        BetSimEmitter { max_cycles: DEFAULT_MAX_CYCLES }
    }

    pub fn with_max_cycles(max_cycles: usize) -> Self {
        BetSimEmitter { max_cycles }
    }

    /// Emit a complete, self-contained Verilog testbench for the given BET bytecode.
    ///
    /// The output file can be compiled and run with:
    /// ```sh
    /// iverilog -o bet_sim.vvp bet_sim_tb.v && vvp bet_sim.vvp
    /// ```
    pub fn emit_testbench(&self, bytecode: &[u8]) -> String {
        let mut out = String::new();

        out.push_str("// ═══════════════════════════════════════════════════════════════\n");
        out.push_str("// BET Processor Simulation Testbench\n");
        out.push_str("// RFI-IRFOS Ternary Intelligence Stack\n");
        out.push_str("// Auto-generated by ternlang-hdl BetSimEmitter — do not edit\n");
        out.push_str("//\n");
        out.push_str("// Trit encoding: 2'b01=-1  2'b10=+1  2'b11=0(hold)  2'b00=FAULT\n");
        out.push_str("// ═══════════════════════════════════════════════════════════════\n");
        out.push_str("`timescale 1ns / 1ps\n\n");

        // Emit all primitive modules
        out.push_str(VerilogEmitter::emit_primitives().as_str());
        out.push('\n');
        let isa = BetIsaEmitter::new();
        out.push_str(&isa.emit_register_file());
        out.push('\n');
        out.push_str(&isa.emit_program_counter());
        out.push('\n');
        out.push_str(&isa.emit_control_unit());
        out.push('\n');
        out.push_str(&isa.emit_top());
        out.push('\n');

        // Instruction ROM module
        out.push_str(&self.emit_rom(bytecode));
        out.push('\n');

        // Stack module (minimal stack for simulation — 256 deep, 2-bit wide)
        out.push_str(Self::emit_stack_shim());
        out.push('\n');

        // Testbench top
        out.push_str(&self.emit_tb_top(bytecode.len()));

        out
    }

    /// Emit an instruction ROM initialised with the program bytes.
    fn emit_rom(&self, bytecode: &[u8]) -> String {
        let depth = bytecode.len().max(4).next_power_of_two();
        let addr_bits = (depth as f64).log2().ceil() as usize;

        let mut init_lines = String::new();
        for (i, &b) in bytecode.iter().enumerate() {
            init_lines.push_str(&format!("        rom[{}] = 8'h{:02X};\n", i, b));
        }
        // Pad remainder with THALT (0x00)
        for i in bytecode.len()..depth {
            init_lines.push_str(&format!("        rom[{}] = 8'h00; // THALT (pad)\n", i));
        }

        format!(
            r#"// BET Instruction ROM — {depth} bytes, {addr_bits}-bit address
// Program: {prog_len} bytes of BET bytecode
module bet_imem #(
    parameter DEPTH     = {depth},
    parameter ADDR_BITS = {addr_bits}
) (
    input  [{addr_bits_m1}:0] addr,
    output [7:0]              data
);
    reg [7:0] rom [0:DEPTH-1];
    initial begin
{init_lines}    end
    assign data = rom[addr[{addr_bits_m1}:0]];
endmodule
"#,
            depth = depth,
            addr_bits = addr_bits,
            addr_bits_m1 = addr_bits - 1,
            prog_len = bytecode.len(),
            init_lines = init_lines
        )
    }

    /// Emit a minimal simulation stack (LIFO, 256 depth, 2-bit trit).
    fn emit_stack_shim() -> &'static str {
        r#"// Simulation stack shim — not synthesisable, testbench only
// In synthesis: replace with registered stack or BRAM.
module bet_stack_shim (
    input        clk,
    input        rst_n,
    input  [1:0] push_data,
    input        push_en,
    input        pop_en,
    output [1:0] top,
    output       empty
);
    reg [1:0] mem [0:255];
    reg [7:0] sp;   // stack pointer (points to next free slot)
    integer   k;

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            sp <= 0;
            for (k = 0; k < 256; k = k + 1)
                mem[k] <= 2'b11; // hold
        end else if (push_en && !pop_en) begin
            mem[sp] <= push_data;
            sp <= sp + 1;
        end else if (pop_en && !push_en && sp > 0) begin
            sp <= sp - 1;
        end
    end

    assign top   = (sp > 0) ? mem[sp - 1] : 2'b11;
    assign empty = (sp == 0);
endmodule
"#
    }

    /// Emit the testbench `module tb_bet_processor` with clock, reset, DUT, and monitors.
    fn emit_tb_top(&self, prog_len: usize) -> String {
        let max_cycles = self.max_cycles;

        format!(
            r#"// ───────────────────────────────────────────────────────────────
// Testbench: tb_bet_processor
// Drives clock/reset, monitors PC + opcode, displays regs on HALT
// ───────────────────────────────────────────────────────────────
module tb_bet_processor;

    // ── Clock & Reset ─────────────────────────────────────────
    reg clk = 0;
    reg rst_n = 0;
    always #5 clk = ~clk;   // 100 MHz (10 ns period)

    // ── DUT interface ─────────────────────────────────────────
    wire [15:0] imem_addr;
    wire [7:0]  imem_data;
    wire [15:0] dmem_addr;
    wire [1:0]  dmem_wdata;
    wire        dmem_we;
    wire        dmem_re;
    reg  [1:0]  dmem_rdata = 2'b11;  // tensor heap: return hold by default

    // ── Instruction ROM ───────────────────────────────────────
    bet_imem u_rom (
        .addr(imem_addr[{addr_bits_m1}:0]),
        .data(imem_data)
    );

    // ── DUT ───────────────────────────────────────────────────
    bet_processor u_dut (
        .clk       (clk),
        .rst_n     (rst_n),
        .imem_addr (imem_addr),
        .imem_data (imem_data),
        .dmem_addr (dmem_addr),
        .dmem_wdata(dmem_wdata),
        .dmem_we   (dmem_we),
        .dmem_re   (dmem_re),
        .dmem_rdata(dmem_rdata)
    );

    // ── Simulation control ────────────────────────────────────
    integer cycle_count = 0;
    integer halted      = 0;

    task display_trit;
        input [1:0] t;
        begin
            case (t)
                2'b01: $write("-1");
                2'b10: $write("+1");
                2'b11: $write(" 0");
                2'b00: $write("XX");  // FAULT
            endcase
        end
    endtask

    always @(posedge clk) begin
        if (rst_n) begin
            cycle_count <= cycle_count + 1;

            // THALT detection (opcode == 8'h00 after reset)
            if (imem_data == 8'h00 && cycle_count > 2 && !halted) begin
                halted <= 1;
                $display("");
                $display("═══════════════════════════════════════");
                $display(" BET Processor HALTED at cycle %0d", cycle_count);
                $display(" Program: {prog_len} bytes");
                $display("───────────────────────────────────────");
                $display(" PC = %0d", imem_addr);
                $display(" Stack top = %b", 2'b11);  // Architecture defined
                $display("═══════════════════════════════════════");
                $display(" Simulation complete (Icarus Verilog)");
                $display(" RFI-IRFOS BET Processor v0.1");
                #20 $finish;
            end

            // Safety: max cycle limit
            if (cycle_count >= {max_cycles}) begin
                $display("TIMEOUT: simulation exceeded {max_cycles} cycles — forcing halt");
                $finish;
            end
        end
    end

    // ── VCD waveform dump (for GTKWave) ───────────────────────
    initial begin
        $dumpfile("bet_sim.vcd");
        $dumpvars(0, tb_bet_processor);
    end

    // ── Cycle-by-cycle monitor (first 32 cycles) ──────────────
    initial begin
        $display("BET Processor Simulation — RFI-IRFOS TIS");
        $display("Program: {prog_len} bytes of BET bytecode");
        $display("Max cycles: {max_cycles}");
        $display("───────────────────────────────────────");
        // Release reset after 2 clock edges
        @(posedge clk); #1;
        @(posedge clk); #1;
        rst_n = 1;
        $display("Reset released at t=%0t", $time);
    end

    // ── Opcode trace (first 64 instructions) ─────────────────
    reg [5:0] trace_count = 0;
    always @(posedge clk) begin
        if (rst_n && !halted && trace_count < 63) begin
            $display("  [%3d] PC=%0d  OPCODE=8'h%02X",
                     cycle_count, imem_addr, imem_data);
            trace_count <= trace_count + 1;
        end
    end

endmodule
"#,
            prog_len = prog_len,
            max_cycles = max_cycles,
            addr_bits_m1 = 14  // 15-bit index into 16-bit address (bottom 15 bits)
        )
    }

    /// Check whether Icarus Verilog (`iverilog`) is available on $PATH.
    pub fn iverilog_available() -> bool {
        std::process::Command::new("iverilog")
            .arg("--version")
            .output()
            .map(|o| o.status.success())
            .unwrap_or(false)
    }

    /// Compile and run a testbench file using Icarus Verilog.
    /// Returns (stdout, stderr, exit_code).
    ///
    /// Requires `iverilog` and `vvp` on $PATH.
    pub fn run_iverilog(tb_path: &str) -> Result<String, String> {
        let vvp_path = tb_path.replace(".v", ".vvp");

        // Compile
        let compile = std::process::Command::new("iverilog")
            .args(["-o", &vvp_path, "-g2001", tb_path])
            .output()
            .map_err(|e| format!("iverilog not found: {}", e))?;

        if !compile.status.success() {
            return Err(format!(
                "iverilog compile failed:\n{}",
                String::from_utf8_lossy(&compile.stderr)
            ));
        }

        // Run
        let run = std::process::Command::new("vvp")
            .arg(&vvp_path)
            .output()
            .map_err(|e| format!("vvp not found: {}", e))?;

        let stdout = String::from_utf8_lossy(&run.stdout).into_owned();
        let stderr = String::from_utf8_lossy(&run.stderr).into_owned();
        if !run.status.success() {
            return Err(format!("vvp run failed:\n{}", stderr));
        }
        Ok(stdout + &stderr)
    }
}

impl Default for BetSimEmitter {
    fn default() -> Self { Self::new() }
}

// ─────────────────────────────────────────────────────────────────────────────
// Tests
// ─────────────────────────────────────────────────────────────────────────────

#[cfg(test)]
mod tests {
    use super::*;

    fn minimal_prog() -> Vec<u8> {
        // PUSH +1 (0x01, 0x02), PUSH -1 (0x01, 0x01), TADD (0x02), THALT (0x00)
        vec![0x01, 0x02, 0x01, 0x01, 0x02, 0x00]
    }

    #[test]
    fn test_emit_testbench_not_empty() {
        let em = BetSimEmitter::new();
        let tb = em.emit_testbench(&minimal_prog());
        assert!(!tb.is_empty());
    }

    #[test]
    fn test_testbench_contains_timescale() {
        let tb = BetSimEmitter::new().emit_testbench(&minimal_prog());
        assert!(tb.contains("`timescale"));
    }

    #[test]
    fn test_testbench_contains_all_modules() {
        let tb = BetSimEmitter::new().emit_testbench(&minimal_prog());
        assert!(tb.contains("module bet_processor"));
        assert!(tb.contains("module bet_regfile"));
        assert!(tb.contains("module bet_pc"));
        assert!(tb.contains("module bet_control"));
        assert!(tb.contains("module bet_imem"));
        assert!(tb.contains("module bet_stack_shim"));
        assert!(tb.contains("module tb_bet_processor"));
    }

    #[test]
    fn test_rom_encodes_bytecode() {
        let prog = vec![0xAB, 0xCD, 0x00];
        let em = BetSimEmitter::new();
        let tb = em.emit_testbench(&prog);
        assert!(tb.contains("8'hAB"));
        assert!(tb.contains("8'hCD"));
        assert!(tb.contains("8'h00"));
    }

    #[test]
    fn test_rom_pads_to_power_of_two() {
        let prog = vec![0x01, 0x00]; // 2 bytes → ROM depth = 4
        let em = BetSimEmitter::new();
        let rom = em.emit_rom(&prog);
        assert!(rom.contains("DEPTH     = 4"));
    }

    #[test]
    fn test_testbench_contains_vcd_dump() {
        let tb = BetSimEmitter::new().emit_testbench(&minimal_prog());
        assert!(tb.contains("$dumpfile"));
        assert!(tb.contains("$dumpvars"));
    }

    #[test]
    fn test_testbench_contains_halt_detection() {
        let tb = BetSimEmitter::new().emit_testbench(&minimal_prog());
        assert!(tb.contains("8'h00"));        // THALT opcode check
        assert!(tb.contains("$finish"));
        assert!(tb.contains("HALTED"));
    }

    #[test]
    fn test_custom_max_cycles() {
        let em = BetSimEmitter::with_max_cycles(42);
        let tb = em.emit_testbench(&minimal_prog());
        assert!(tb.contains("42"));
    }

    #[test]
    fn test_trit_encoding_comment_present() {
        let tb = BetSimEmitter::new().emit_testbench(&minimal_prog());
        assert!(tb.contains("2'b01=-1"));
        assert!(tb.contains("2'b10=+1"));
    }

    #[test]
    fn test_iverilog_check_does_not_panic() {
        // Just verify it runs without panicking — result depends on system
        let _ = BetSimEmitter::iverilog_available();
    }
}
