//! O3 Execute: single-instruction execution for the out-of-order backend.
//!
//! This module provides `execute_one()` which executes a single issued
//! instruction. It directly calls the shared hardware units (Alu, Fpu, BRU)
//! and handles all instruction types: ALU, FP, branch, jump, CSR,
//! MRET/SRET, WFI, SFENCE.VMA, ECALL, FENCE.I, and trap propagation.
//!
//! This is independent from the in-order execute — both call the same
//! hardware units but are structured differently.

use crate::common::error::{ExceptionStage, Trap};
use crate::core::Cpu;
use crate::core::pipeline::latches::{ExMem1Entry, RenameIssueEntry};
use crate::core::pipeline::rob::{CsrUpdate, Rob};
use crate::core::pipeline::signals::{AluOp, CsrOp, OpASrc, OpBSrc};
use crate::core::units::alu::Alu;
use crate::core::units::bru::BranchPredictor;
use crate::core::units::fpu::Fpu;
use crate::isa::abi;
use crate::isa::privileged::opcodes as sys_ops;
use crate::isa::rv64i::{funct3, opcodes};

const FUNCT3_SHIFT: u32 = 12;
const FUNCT3_MASK: u32 = 0x7;
const JALR_ALIGNMENT_MASK: u64 = !1;

/// Execute a single instruction for the O3 backend.
///
/// Returns `(ExMem1Entry, needs_flush)`. When `needs_flush` is true,
/// the engine must flush younger instructions (misprediction, CSR,
/// MRET/SRET, FENCE.I, etc.).
pub fn execute_one(cpu: &mut Cpu, id: RenameIssueEntry, rob: &mut Rob) -> (ExMem1Entry, bool) {
    // Propagate traps from earlier stages
    if let Some(trap) = id.trap.clone() {
        if cpu.trace {
            eprintln!("EX  pc={:#x} # TRAP: {:?}", id.pc, trap);
        }
        rob.fault(
            id.rob_tag,
            trap,
            id.exception_stage.unwrap_or(ExceptionStage::Execute),
        );
        let result = ExMem1Entry {
            rob_tag: id.rob_tag,
            pc: id.pc,
            inst: id.inst,
            inst_size: id.inst_size,
            rd: id.rd,
            alu: 0,
            store_data: 0,
            ctrl: id.ctrl,
            trap: None,
            exception_stage: None,
            rd_phys: id.rd_phys,
            fp_flags: 0,
        };
        return (result, true);
    }

    if cpu.trace {
        eprintln!("EX  pc={:#x} rob_tag={}", id.pc, id.rob_tag.0);
    }

    let fwd_a = id.rv1;
    let fwd_b = id.rv2;
    let fwd_c = id.rv3;
    let store_data = fwd_b;

    let op_a = match id.ctrl.a_src {
        OpASrc::Reg1 => fwd_a,
        OpASrc::Pc => id.pc,
        OpASrc::Zero => 0,
    };
    let op_b = match id.ctrl.b_src {
        OpBSrc::Reg2 => fwd_b,
        OpBSrc::Imm => id.imm as u64,
        OpBSrc::Zero => 0,
    };
    let op_c = fwd_c;

    // FENCE.I: flush the pipeline so younger instructions are squashed.
    // The I-cache flush is deferred to COMMIT time (after store drain)
    // to ensure that all prior stores are visible in RAM before the
    // I-cache refills with the new data.
    if id.ctrl.is_fence_i {
        cpu.pc = id.pc.wrapping_add(id.inst_size);
        cpu.redirect_pending = true;

        let result = ExMem1Entry {
            rob_tag: id.rob_tag,
            pc: id.pc,
            inst: id.inst,
            inst_size: id.inst_size,
            rd: id.rd,
            alu: 0,
            store_data: 0,
            ctrl: id.ctrl,
            trap: None,
            exception_stage: None,
            rd_phys: id.rd_phys,
            fp_flags: 0,
        };
        return (result, true);
    }

    // System instructions
    if id.ctrl.is_system {
        return execute_system(cpu, id, rob, fwd_a, store_data);
    }

    // When mstatus.FS == OFF, all FP instructions trap as illegal.
    {
        let fs = (cpu.csrs.mstatus & crate::core::arch::csr::MSTATUS_FS) >> 13;
        let is_fp = id.ctrl.fp_reg_write || id.ctrl.rs1_fp || id.ctrl.rs2_fp || id.ctrl.rs3_fp;
        if fs == 0 && is_fp {
            rob.fault(
                id.rob_tag,
                Trap::IllegalInstruction(id.inst),
                ExceptionStage::Execute,
            );
            let result = ExMem1Entry {
                rob_tag: id.rob_tag,
                pc: id.pc,
                inst: id.inst,
                inst_size: id.inst_size,
                rd: id.rd,
                alu: 0,
                store_data: 0,
                ctrl: id.ctrl,
                trap: None,
                exception_stage: None,
                rd_phys: id.rd_phys,
                fp_flags: 0,
            };
            return (result, true);
        }
    }

    // ALU / FPU execution
    let (alu_out, fp_flags) = compute_alu(id.ctrl.alu, op_a, op_b, op_c, id.ctrl.is_rv32);

    let mut needs_flush = false;

    // Branch resolution
    if id.ctrl.branch {
        let taken = match (id.inst >> FUNCT3_SHIFT) & FUNCT3_MASK {
            funct3::BEQ => op_a == op_b,
            funct3::BNE => op_a != op_b,
            funct3::BLT => (op_a as i64) < (op_b as i64),
            funct3::BGE => (op_a as i64) >= (op_b as i64),
            funct3::BLTU => op_a < op_b,
            funct3::BGEU => op_a >= op_b,
            _ => false,
        };
        let actual_target = id.pc.wrapping_add(id.imm as u64);
        let fallthrough = id.pc.wrapping_add(id.inst_size);

        let predicted_target = if id.pred_taken {
            id.pred_target
        } else {
            fallthrough
        };
        let actual_next_pc = if taken { actual_target } else { fallthrough };

        let mispredicted = predicted_target != actual_next_pc;

        cpu.branch_predictor.repair_history(id.ghr_snapshot);
        cpu.branch_predictor.restore_ras(id.ras_snapshot);
        // Defer branch predictor update to commit time to avoid
        // polluting tables with speculative/wrong-path data.
        rob.set_bp_update(
            id.rob_tag,
            id.pc,
            taken,
            if taken { Some(actual_target) } else { None },
        );

        if mispredicted {
            cpu.stats.branch_mispredictions += 1;
            cpu.pc = actual_next_pc;
            cpu.redirect_pending = true;
            needs_flush = true;
        } else {
            cpu.stats.branch_predictions += 1;
        }
    }

    // Jump resolution
    if id.ctrl.jump {
        use crate::common::constants::OPCODE_MASK;
        let is_jalr = (id.inst & OPCODE_MASK) == opcodes::OP_JALR;
        let is_call = (id.inst & OPCODE_MASK) == opcodes::OP_JAL && id.rd == abi::REG_RA;
        let is_ret = is_jalr && id.rd == abi::REG_ZERO && id.rs1 == abi::REG_RA;

        let actual_target = if is_jalr {
            (fwd_a.wrapping_add(id.imm as u64)) & JALR_ALIGNMENT_MASK
        } else {
            id.pc.wrapping_add(id.imm as u64)
        };

        let predicted_target = if id.pred_taken {
            id.pred_target
        } else {
            id.pc.wrapping_add(id.inst_size)
        };

        if actual_target != predicted_target {
            cpu.stats.branch_mispredictions += 1;
            cpu.pc = actual_target;
            cpu.redirect_pending = true;
            needs_flush = true;
        } else {
            cpu.stats.branch_predictions += 1;
        }

        if is_call {
            cpu.branch_predictor
                .on_call(id.pc, id.pc.wrapping_add(id.inst_size), actual_target);
        } else if is_ret {
            cpu.branch_predictor.on_return();
        }
    }

    let result = ExMem1Entry {
        rob_tag: id.rob_tag,
        pc: id.pc,
        inst: id.inst,
        inst_size: id.inst_size,
        rd: id.rd,
        alu: alu_out,
        store_data,
        ctrl: id.ctrl,
        trap: None,
        exception_stage: None,
        rd_phys: id.rd_phys,
        fp_flags,
    };

    (result, needs_flush)
}

/// Handle system instructions (MRET, SRET, WFI, SFENCE.VMA, ECALL, CSR).
fn execute_system(
    cpu: &mut Cpu,
    id: RenameIssueEntry,
    rob: &mut Rob,
    fwd_a: u64,
    store_data: u64,
) -> (ExMem1Entry, bool) {
    let make_result =
        |alu: u64, ctrl: crate::core::pipeline::signals::ControlSignals| ExMem1Entry {
            rob_tag: id.rob_tag,
            pc: id.pc,
            inst: id.inst,
            inst_size: id.inst_size,
            rd: id.rd,
            alu,
            store_data: 0,
            ctrl,
            trap: None,
            exception_stage: None,
            rd_phys: id.rd_phys,
            fp_flags: 0,
        };

    // MRET
    if id.ctrl.is_mret {
        return (make_result(0, id.ctrl), true);
    }

    // SRET
    if id.ctrl.is_sret {
        let tsr = (cpu.csrs.mstatus >> 22) & 1;
        if cpu.privilege == crate::core::arch::mode::PrivilegeMode::Supervisor && tsr != 0 {
            rob.fault(
                id.rob_tag,
                Trap::IllegalInstruction(id.inst),
                ExceptionStage::Execute,
            );
            return (make_result(0, id.ctrl), true);
        }
        return (make_result(0, id.ctrl), true);
    }

    // WFI
    if id.inst == sys_ops::WFI {
        let tw = (cpu.csrs.mstatus >> 21) & 1;
        if cpu.privilege == crate::core::arch::mode::PrivilegeMode::User
            || (cpu.privilege == crate::core::arch::mode::PrivilegeMode::Supervisor && tw != 0)
        {
            rob.fault(
                id.rob_tag,
                Trap::IllegalInstruction(id.inst),
                ExceptionStage::Execute,
            );
        } else if cpu.csrs.mie != 0 || cpu.csrs.mip != 0 {
            cpu.wfi_waiting = true;
            cpu.wfi_pc = id.pc.wrapping_add(id.inst_size);
        } else {
            cpu.pc = id.pc.wrapping_add(id.inst_size);
            cpu.redirect_pending = true;
        }
        return (make_result(0, id.ctrl), true);
    }

    // SFENCE.VMA
    if id.ctrl.is_sfence_vma {
        let tvm = (cpu.csrs.mstatus >> 20) & 1;
        if cpu.privilege == crate::core::arch::mode::PrivilegeMode::Supervisor && tvm != 0 {
            rob.fault(
                id.rob_tag,
                Trap::IllegalInstruction(id.inst),
                ExceptionStage::Execute,
            );
            return (
                ExMem1Entry {
                    rob_tag: id.rob_tag,
                    pc: id.pc,
                    inst: id.inst,
                    inst_size: id.inst_size,
                    rd: id.rd,
                    alu: 0,
                    store_data,
                    ctrl: id.ctrl,
                    trap: None,
                    exception_stage: None,
                    rd_phys: id.rd_phys,
                    fp_flags: 0,
                },
                true,
            );
        }

        cpu.clear_reservation();
        sfence_vma_flush(cpu, id.rs1, id.rs2, fwd_a, id.rv2);

        cpu.pc = id.pc.wrapping_add(id.inst_size);
        cpu.redirect_pending = true;

        return (
            ExMem1Entry {
                rob_tag: id.rob_tag,
                pc: id.pc,
                inst: id.inst,
                inst_size: id.inst_size,
                rd: id.rd,
                alu: 0,
                store_data,
                ctrl: id.ctrl,
                trap: None,
                exception_stage: None,
                rd_phys: id.rd_phys,
                fp_flags: 0,
            },
            true,
        );
    }

    // ECALL
    if id.inst == sys_ops::ECALL {
        use crate::core::arch::mode::PrivilegeMode;
        let trap = match cpu.privilege {
            PrivilegeMode::User => Trap::EnvironmentCallFromUMode,
            PrivilegeMode::Supervisor => Trap::EnvironmentCallFromSMode,
            PrivilegeMode::Machine => Trap::EnvironmentCallFromMMode,
        };
        rob.fault(id.rob_tag, trap, ExceptionStage::Execute);
        return (make_result(0, id.ctrl), true);
    }

    // CSR operations
    if id.ctrl.csr_op != CsrOp::None {
        return execute_csr(cpu, id, rob, fwd_a, store_data);
    }

    // Unknown system instruction — pass through
    (make_result(0, id.ctrl), true)
}

/// Handle CSR operations.
fn execute_csr(
    cpu: &mut Cpu,
    id: RenameIssueEntry,
    rob: &mut Rob,
    fwd_a: u64,
    store_data: u64,
) -> (ExMem1Entry, bool) {
    // SATP access check in S-mode with TVM=1
    if id.ctrl.csr_addr == crate::core::arch::csr::SATP
        && cpu.privilege == crate::core::arch::mode::PrivilegeMode::Supervisor
        && ((cpu.csrs.mstatus >> 20) & 1) != 0
    {
        rob.fault(
            id.rob_tag,
            Trap::IllegalInstruction(id.inst),
            ExceptionStage::Execute,
        );
        return (
            ExMem1Entry {
                rob_tag: id.rob_tag,
                pc: id.pc,
                inst: id.inst,
                inst_size: id.inst_size,
                rd: id.rd,
                alu: 0,
                store_data: 0,
                ctrl: id.ctrl,
                trap: None,
                exception_stage: None,
                rd_phys: id.rd_phys,
                fp_flags: 0,
            },
            true,
        );
    }

    // Counter-enable check for CYCLE/TIME/INSTRET (and their high-word aliases)
    {
        use crate::core::arch::csr as csr_addrs;
        use crate::core::arch::mode::PrivilegeMode;
        let addr = id.ctrl.csr_addr;
        let counter_bit = match addr {
            csr_addrs::CYCLE => Some(0),   // CY bit
            csr_addrs::TIME => Some(1),    // TM bit
            csr_addrs::INSTRET => Some(2), // IR bit
            _ => None,
        };
        if let Some(bit) = counter_bit {
            let mask = 1u64 << bit;
            let denied = match cpu.privilege {
                PrivilegeMode::Supervisor => (cpu.csrs.mcounteren & mask) == 0,
                PrivilegeMode::User => {
                    (cpu.csrs.mcounteren & mask) == 0 || (cpu.csrs.scounteren & mask) == 0
                }
                PrivilegeMode::Machine => false,
            };
            if denied {
                rob.fault(
                    id.rob_tag,
                    Trap::IllegalInstruction(id.inst),
                    ExceptionStage::Execute,
                );
                return (
                    ExMem1Entry {
                        rob_tag: id.rob_tag,
                        pc: id.pc,
                        inst: id.inst,
                        inst_size: id.inst_size,
                        rd: id.rd,
                        alu: 0,
                        store_data: 0,
                        ctrl: id.ctrl,
                        trap: None,
                        exception_stage: None,
                        rd_phys: id.rd_phys,
                        fp_flags: 0,
                    },
                    true,
                );
            }
        }
    }

    // Privilege check
    let csr_priv = (id.ctrl.csr_addr >> 8) & 3;
    if (cpu.privilege.to_u8() as u32) < csr_priv {
        rob.fault(
            id.rob_tag,
            Trap::IllegalInstruction(id.inst),
            ExceptionStage::Execute,
        );
        return (
            ExMem1Entry {
                rob_tag: id.rob_tag,
                pc: id.pc,
                inst: id.inst,
                inst_size: id.inst_size,
                rd: id.rd,
                alu: 0,
                store_data: 0,
                ctrl: id.ctrl,
                trap: None,
                exception_stage: None,
                rd_phys: id.rd_phys,
                fp_flags: 0,
            },
            true,
        );
    }

    // Read-only check
    let read_only = (id.ctrl.csr_addr >> 10) & 3 == 3;
    if read_only {
        let would_write = match id.ctrl.csr_op {
            CsrOp::Rw | CsrOp::Rwi => true,
            CsrOp::Rs | CsrOp::Rc => id.rs1 != 0,
            CsrOp::Rsi | CsrOp::Rci => (id.rs1 & 0x1f) != 0,
            CsrOp::None => false,
        };
        if would_write {
            rob.fault(
                id.rob_tag,
                Trap::IllegalInstruction(id.inst),
                ExceptionStage::Execute,
            );
            return (
                ExMem1Entry {
                    rob_tag: id.rob_tag,
                    pc: id.pc,
                    inst: id.inst,
                    inst_size: id.inst_size,
                    rd: id.rd,
                    alu: 0,
                    store_data: 0,
                    ctrl: id.ctrl,
                    trap: None,
                    exception_stage: None,
                    rd_phys: id.rd_phys,
                    fp_flags: 0,
                },
                true,
            );
        }
    }

    // fp_flags are deferred to commit, but a CSR read of fflags/fcsr must
    // see the accumulated flags from all older (completed) instructions.
    // CSR instructions are serializing, so all older entries are complete.
    {
        use crate::core::arch::csr as csr_addrs;
        if id.ctrl.csr_addr == csr_addrs::FFLAGS
            || id.ctrl.csr_addr == csr_addrs::FCSR
            || id.ctrl.csr_addr == csr_addrs::FRM
        {
            let acc = rob.drain_fp_flags_before(id.rob_tag);
            cpu.csrs.fflags |= acc as u64;
        }
    }
    let old = cpu.csr_read(id.ctrl.csr_addr);
    let src = match id.ctrl.csr_op {
        CsrOp::Rwi | CsrOp::Rsi | CsrOp::Rci => (id.rs1 as u64) & 0x1f,
        _ => fwd_a,
    };
    let new = match id.ctrl.csr_op {
        CsrOp::Rw | CsrOp::Rwi => src,
        CsrOp::Rs | CsrOp::Rsi => old | src,
        CsrOp::Rc | CsrOp::Rci => old & !src,
        CsrOp::None => old,
    };

    rob.set_csr_update(
        id.rob_tag,
        CsrUpdate {
            addr: id.ctrl.csr_addr,
            old_val: old,
            new_val: new,
            applied: false,
        },
    );

    cpu.pc = id.pc.wrapping_add(id.inst_size);
    cpu.redirect_pending = true;

    (
        ExMem1Entry {
            rob_tag: id.rob_tag,
            pc: id.pc,
            inst: id.inst,
            inst_size: id.inst_size,
            rd: id.rd,
            alu: old, // result = old CSR value for rd
            store_data,
            ctrl: id.ctrl,
            trap: None,
            exception_stage: None,
            rd_phys: id.rd_phys,
            fp_flags: 0,
        },
        true,
    )
}

/// Performs selective SFENCE.VMA TLB/cache flushing based on rs1 and rs2.
///
/// * rs1_idx == 0, rs2_idx == 0: flush all TLB entries
/// * rs1_idx != 0, rs2_idx == 0: flush entries matching the virtual address in rs1
/// * rs1_idx == 0, rs2_idx != 0: flush non-global entries matching the ASID in rs2
/// * rs1_idx != 0, rs2_idx != 0: flush the entry matching both vaddr and ASID
fn sfence_vma_flush(cpu: &mut Cpu, rs1_idx: usize, rs2_idx: usize, rs1_val: u64, rs2_val: u64) {
    use crate::common::constants::{PAGE_SHIFT, VPN_MASK};
    match (rs1_idx != 0, rs2_idx != 0) {
        (false, false) => {
            // Flush all
            cpu.mmu.dtlb.flush();
            cpu.mmu.itlb.flush();
            cpu.mmu.l2_tlb.flush();
            cpu.l1_d_cache.flush();
            cpu.l1_i_cache.invalidate_all();
        }
        (true, false) => {
            // Flush by virtual address only
            let vpn = (rs1_val >> PAGE_SHIFT) & VPN_MASK;
            cpu.mmu.dtlb.flush_vaddr(vpn);
            cpu.mmu.itlb.flush_vaddr(vpn);
            cpu.mmu.l2_tlb.flush_vaddr(vpn);
        }
        (false, true) => {
            // Flush by ASID only (non-global entries)
            let asid = rs2_val as u16;
            cpu.mmu.dtlb.flush_asid(asid);
            cpu.mmu.itlb.flush_asid(asid);
            cpu.mmu.l2_tlb.flush_asid(asid);
        }
        (true, true) => {
            // Flush by both virtual address and ASID
            let vpn = (rs1_val >> PAGE_SHIFT) & VPN_MASK;
            let asid = rs2_val as u16;
            cpu.mmu.dtlb.flush_vaddr_asid(vpn, asid);
            cpu.mmu.itlb.flush_vaddr_asid(vpn, asid);
            cpu.mmu.l2_tlb.flush_vaddr_asid(vpn, asid);
        }
    }
}

/// Compute ALU/FPU result and return (result, fp_flags).
fn compute_alu(alu_op: AluOp, op_a: u64, op_b: u64, op_c: u64, is_rv32: bool) -> (u64, u8) {
    // FP conversions and moves that need special handling
    match alu_op {
        AluOp::FCvtSW
        | AluOp::FCvtSL
        | AluOp::FCvtSWU
        | AluOp::FCvtSLU
        | AluOp::FCvtSD
        | AluOp::FCvtDS
        | AluOp::FMvToF => {
            let val = match alu_op {
                AluOp::FCvtSW => {
                    if is_rv32 {
                        Fpu::box_f32((op_a as i32) as f32)
                    } else {
                        ((op_a as i32) as f64).to_bits()
                    }
                }
                AluOp::FCvtSWU => {
                    if is_rv32 {
                        Fpu::box_f32((op_a as u32) as f32)
                    } else {
                        ((op_a as u32) as f64).to_bits()
                    }
                }
                AluOp::FCvtSL => {
                    if is_rv32 {
                        Fpu::box_f32((op_a as i64) as f32)
                    } else {
                        ((op_a as i64) as f64).to_bits()
                    }
                }
                AluOp::FCvtSLU => {
                    if is_rv32 {
                        Fpu::box_f32(op_a as f32)
                    } else {
                        (op_a as f64).to_bits()
                    }
                }
                AluOp::FCvtSD => {
                    use crate::core::units::fpu::nan_handling::box_f32_canon;
                    let val_d = f64::from_bits(op_a);
                    let val_s = val_d as f32;
                    box_f32_canon(val_s)
                }
                AluOp::FCvtDS => {
                    let val_s = f32::from_bits(op_a as u32);
                    let val_d = val_s as f64;
                    val_d.to_bits()
                }
                AluOp::FMvToF => {
                    if is_rv32 {
                        Fpu::box_f32(f32::from_bits(op_a as u32))
                    } else {
                        op_a
                    }
                }
                _ => 0,
            };
            return (val, 0);
        }
        _ => {}
    }

    let is_fp_op = matches!(
        alu_op,
        AluOp::FAdd
            | AluOp::FSub
            | AluOp::FMul
            | AluOp::FDiv
            | AluOp::FSqrt
            | AluOp::FMin
            | AluOp::FMax
            | AluOp::FMAdd
            | AluOp::FMSub
            | AluOp::FNMAdd
            | AluOp::FNMSub
            | AluOp::FSgnJ
            | AluOp::FSgnJN
            | AluOp::FSgnJX
            | AluOp::FEq
            | AluOp::FLt
            | AluOp::FLe
            | AluOp::FClass
            | AluOp::FCvtWS
            | AluOp::FCvtWUS
            | AluOp::FCvtLS
            | AluOp::FCvtLUS
            | AluOp::FMvToX
    );

    if is_fp_op {
        let (result, fp_flags) = Fpu::execute_full(alu_op, op_a, op_b, op_c, is_rv32);
        (result, fp_flags.bits())
    } else {
        (Alu::execute(alu_op, op_a, op_b, op_c, is_rv32), 0)
    }
}
