//! Computed-goto-style instruction dispatch via function-pointer table.
//!
//! This module provides a `const` dispatch table of 256 function pointers,
//! one per JVM opcode. Frequently-executed ("hot") opcodes have dedicated
//! handlers that avoid the overhead of a giant `match` statement. All other
//! opcodes fall back to the existing `dispatch_instruction` match.
//!
//! # Design
//!
//! - `OpcodeHandler` — function pointer type for all handlers  
//! - `DISPATCH_TABLE` — `const` array of 256 handlers, indexed by opcode  
//! - Hot handlers — small `#[inline(always)]` methods for ~20 common opcodes  
//! - `handle_fallback` — thin wrapper around `dispatch_instruction` for the rest
//!
//! # Hot Opcodes
//!
//! The following opcodes have dedicated handlers because they appear in
//! >70 % of typical Java bytecode:
//!
//! - Constants: `nop`, `aconst_null`, `iconst_0`..`iconst_5`, `iconst_m1`
//! - Loads: `iload_0`..`iload_3`, `aload_0`..`aload_3`
//! - Stores: `istore_0`..`istore_3`, `astore_0`..`astore_3`
//! - Stack: `pop`, `dup`
//! - Arithmetic: `iadd`, `isub`, `imul`, `idiv`
//! - Control: `return`, `ireturn`

use crate::class_file::ClassFile;
use crate::error::{JvmError, RuntimeError};
use crate::memory::{StackFrame, Value};

use super::Interpreter;

/// Function-pointer signature for every opcode handler.
///
/// `opcode` is passed even to handlers that don't need it (e.g. `nop`) so
/// that the table is homogeneous and no dynamic dispatch is required.
pub(crate) type OpcodeHandler =
    fn(&mut Interpreter, &ClassFile, &[u8], &mut StackFrame, u8) -> Result<bool, JvmError>;

impl Interpreter {
    // ----- hot handlers (kept tiny so the compiler inlines them) -----

    #[inline(always)]
    fn handle_nop(
        &mut self,
        _class: &ClassFile,
        _code: &[u8],
        _frame: &mut StackFrame,
        _opcode: u8,
    ) -> Result<bool, JvmError> {
        Ok(true)
    }

    #[inline(always)]
    fn handle_aconst_null(
        &mut self,
        _class: &ClassFile,
        _code: &[u8],
        frame: &mut StackFrame,
        _opcode: u8,
    ) -> Result<bool, JvmError> {
        frame.push(Value::Null)?;
        Ok(true)
    }

    #[inline(always)]
    fn handle_iconst_m1(
        &mut self,
        _class: &ClassFile,
        _code: &[u8],
        frame: &mut StackFrame,
        _opcode: u8,
    ) -> Result<bool, JvmError> {
        frame.push(Value::Int(-1))?;
        Ok(true)
    }

    #[inline(always)]
    fn handle_iconst_0(
        &mut self,
        _class: &ClassFile,
        _code: &[u8],
        frame: &mut StackFrame,
        _opcode: u8,
    ) -> Result<bool, JvmError> {
        frame.push(Value::Int(0))?;
        Ok(true)
    }

    #[inline(always)]
    fn handle_iconst_1(
        &mut self,
        _class: &ClassFile,
        _code: &[u8],
        frame: &mut StackFrame,
        _opcode: u8,
    ) -> Result<bool, JvmError> {
        frame.push(Value::Int(1))?;
        Ok(true)
    }

    #[inline(always)]
    fn handle_iconst_2(
        &mut self,
        _class: &ClassFile,
        _code: &[u8],
        frame: &mut StackFrame,
        _opcode: u8,
    ) -> Result<bool, JvmError> {
        frame.push(Value::Int(2))?;
        Ok(true)
    }

    #[inline(always)]
    fn handle_iconst_3(
        &mut self,
        _class: &ClassFile,
        _code: &[u8],
        frame: &mut StackFrame,
        _opcode: u8,
    ) -> Result<bool, JvmError> {
        frame.push(Value::Int(3))?;
        Ok(true)
    }

    #[inline(always)]
    fn handle_iconst_4(
        &mut self,
        _class: &ClassFile,
        _code: &[u8],
        frame: &mut StackFrame,
        _opcode: u8,
    ) -> Result<bool, JvmError> {
        frame.push(Value::Int(4))?;
        Ok(true)
    }

    #[inline(always)]
    fn handle_iconst_5(
        &mut self,
        _class: &ClassFile,
        _code: &[u8],
        frame: &mut StackFrame,
        _opcode: u8,
    ) -> Result<bool, JvmError> {
        frame.push(Value::Int(5))?;
        Ok(true)
    }

    #[inline(always)]
    fn handle_iload_n(
        &mut self,
        _class: &ClassFile,
        _code: &[u8],
        frame: &mut StackFrame,
        opcode: u8,
    ) -> Result<bool, JvmError> {
        let idx = (opcode - 0x1a) as usize;
        let v = frame.load_local(idx)?;
        frame.push(v)?;
        Ok(true)
    }

    #[inline(always)]
    fn handle_aload_n(
        &mut self,
        _class: &ClassFile,
        _code: &[u8],
        frame: &mut StackFrame,
        opcode: u8,
    ) -> Result<bool, JvmError> {
        let idx = (opcode - 0x2a) as usize;
        let v = frame.load_local(idx)?;
        frame.push(v)?;
        Ok(true)
    }

    #[inline(always)]
    fn handle_istore_n(
        &mut self,
        _class: &ClassFile,
        _code: &[u8],
        frame: &mut StackFrame,
        opcode: u8,
    ) -> Result<bool, JvmError> {
        let idx = (opcode - 0x3b) as usize;
        let v = frame.pop()?;
        frame.store_local(idx, v)?;
        Ok(true)
    }

    #[inline(always)]
    fn handle_astore_n(
        &mut self,
        _class: &ClassFile,
        _code: &[u8],
        frame: &mut StackFrame,
        opcode: u8,
    ) -> Result<bool, JvmError> {
        let idx = (opcode - 0x4b) as usize;
        let v = frame.pop()?;
        frame.store_local(idx, v)?;
        Ok(true)
    }

    #[inline(always)]
    fn handle_pop(
        &mut self,
        _class: &ClassFile,
        _code: &[u8],
        frame: &mut StackFrame,
        _opcode: u8,
    ) -> Result<bool, JvmError> {
        frame.pop()?;
        Ok(true)
    }

    #[inline(always)]
    fn handle_dup(
        &mut self,
        _class: &ClassFile,
        _code: &[u8],
        frame: &mut StackFrame,
        _opcode: u8,
    ) -> Result<bool, JvmError> {
        let v = frame.peek()?.clone();
        frame.push(v)?;
        Ok(true)
    }

    #[inline(always)]
    fn handle_swap(
        &mut self,
        _class: &ClassFile,
        _code: &[u8],
        frame: &mut StackFrame,
        _opcode: u8,
    ) -> Result<bool, JvmError> {
        let v1 = frame.pop()?;
        let v2 = frame.pop()?;
        frame.push(v1)?;
        frame.push(v2)?;
        Ok(true)
    }

    #[inline(always)]
    fn handle_iadd(
        &mut self,
        _class: &ClassFile,
        _code: &[u8],
        frame: &mut StackFrame,
        _opcode: u8,
    ) -> Result<bool, JvmError> {
        let b = frame.pop()?.as_int();
        let a = frame.pop()?.as_int();
        frame.push(Value::Int(a.wrapping_add(b)))?;
        Ok(true)
    }

    #[inline(always)]
    fn handle_isub(
        &mut self,
        _class: &ClassFile,
        _code: &[u8],
        frame: &mut StackFrame,
        _opcode: u8,
    ) -> Result<bool, JvmError> {
        let b = frame.pop()?.as_int();
        let a = frame.pop()?.as_int();
        frame.push(Value::Int(a.wrapping_sub(b)))?;
        Ok(true)
    }

    #[inline(always)]
    fn handle_imul(
        &mut self,
        _class: &ClassFile,
        _code: &[u8],
        frame: &mut StackFrame,
        _opcode: u8,
    ) -> Result<bool, JvmError> {
        let b = frame.pop()?.as_int();
        let a = frame.pop()?.as_int();
        frame.push(Value::Int(a.wrapping_mul(b)))?;
        Ok(true)
    }

    #[inline(always)]
    fn handle_idiv(
        &mut self,
        _class: &ClassFile,
        _code: &[u8],
        frame: &mut StackFrame,
        _opcode: u8,
    ) -> Result<bool, JvmError> {
        let b = frame.pop()?.as_int();
        let a = frame.pop()?.as_int();
        if b == 0 {
            return Err(crate::error::to_runtime_error_enum(
                RuntimeError::DivisionByZero,
            ));
        }
        frame.push(Value::Int(a.wrapping_div(b)))?;
        Ok(true)
    }

    #[inline(always)]
    fn handle_return(
        &mut self,
        _class: &ClassFile,
        _code: &[u8],
        _frame: &mut StackFrame,
        _opcode: u8,
    ) -> Result<bool, JvmError> {
        Ok(false) // signal break
    }

    #[inline(always)]
    fn handle_ireturn(
        &mut self,
        _class: &ClassFile,
        _code: &[u8],
        frame: &mut StackFrame,
        _opcode: u8,
    ) -> Result<bool, JvmError> {
        if !frame.stack.is_empty() {
            let _ = frame.pop()?;
        }
        Ok(false) // signal break
    }

    #[inline(always)]
    fn handle_areturn(
        &mut self,
        _class: &ClassFile,
        _code: &[u8],
        frame: &mut StackFrame,
        _opcode: u8,
    ) -> Result<bool, JvmError> {
        if !frame.stack.is_empty() {
            let _ = frame.pop()?;
        }
        Ok(false) // signal break
    }

    // ----- fallback for everything else -----

    #[inline(never)]
    fn handle_fallback(
        &mut self,
        class: &ClassFile,
        code: &[u8],
        frame: &mut StackFrame,
        opcode: u8,
    ) -> Result<bool, JvmError> {
        self.dispatch_instruction(class, code, frame, opcode)
    }
}

/// `const` dispatch table indexed by raw opcode byte.
///
/// Entries for the ~20 hottest opcodes point to dedicated handlers.
/// Every other entry points to `handle_fallback`, which delegates to the
/// existing `match`-based `dispatch_instruction`.
pub(crate) const DISPATCH_TABLE: [OpcodeHandler; 256] = {
    let mut table: [OpcodeHandler; 256] = [Interpreter::handle_fallback; 256];

    // Constants
    table[0x00] = Interpreter::handle_nop;
    table[0x01] = Interpreter::handle_aconst_null;
    table[0x02] = Interpreter::handle_iconst_m1;
    table[0x03] = Interpreter::handle_iconst_0;
    table[0x04] = Interpreter::handle_iconst_1;
    table[0x05] = Interpreter::handle_iconst_2;
    table[0x06] = Interpreter::handle_iconst_3;
    table[0x07] = Interpreter::handle_iconst_4;
    table[0x08] = Interpreter::handle_iconst_5;

    // Loads (iload_0..3)
    table[0x1a] = Interpreter::handle_iload_n;
    table[0x1b] = Interpreter::handle_iload_n;
    table[0x1c] = Interpreter::handle_iload_n;
    table[0x1d] = Interpreter::handle_iload_n;

    // Loads (aload_0..3)
    table[0x2a] = Interpreter::handle_aload_n;
    table[0x2b] = Interpreter::handle_aload_n;
    table[0x2c] = Interpreter::handle_aload_n;
    table[0x2d] = Interpreter::handle_aload_n;

    // Stores (istore_0..3)
    table[0x3b] = Interpreter::handle_istore_n;
    table[0x3c] = Interpreter::handle_istore_n;
    table[0x3d] = Interpreter::handle_istore_n;
    table[0x3e] = Interpreter::handle_istore_n;

    // Stores (astore_0..3)
    table[0x4b] = Interpreter::handle_astore_n;
    table[0x4c] = Interpreter::handle_astore_n;
    table[0x4d] = Interpreter::handle_astore_n;
    table[0x4e] = Interpreter::handle_astore_n;

    // Stack
    table[0x57] = Interpreter::handle_pop;
    table[0x59] = Interpreter::handle_dup;
    table[0x5f] = Interpreter::handle_swap;

    // Integer arithmetic
    table[0x60] = Interpreter::handle_iadd;
    table[0x64] = Interpreter::handle_isub;
    table[0x68] = Interpreter::handle_imul;
    table[0x6c] = Interpreter::handle_idiv;

    // Return opcodes (0xb1, 0xac, 0xb0) are handled inline by the
    // dispatch loop so they can transfer the return value to the
    // caller frame. They fall back to handle_fallback.

    table
};

#[cfg(test)]
mod tests {
    use crate::class_file::ClassFile;
    use crate::interpreter::Interpreter;
    use crate::memory::{StackFrame, Value};

    /// Run a single opcode through the dispatch table and return the
    /// resulting top-of-stack value (or an error).
    fn run_opcode(
        opcode: u8,
        locals: Vec<Value>,
        stack: Vec<Value>,
    ) -> Result<Vec<Value>, super::JvmError> {
        let mut interp = Interpreter::new();
        let class = ClassFile {
            magic: 0xCAFEBABE,
            minor_version: 0,
            major_version: 52,
            constant_pool: vec![],
            access_flags: 0,
            this_class: 0,
            super_class: 0,
            interfaces: vec![],
            fields: vec![],
            methods: vec![],
            attributes: vec![],
        };
        let mut frame = StackFrame::new(10, 10, "test".to_string());
        frame.locals = locals;
        for v in stack {
            frame.push(v).unwrap();
        }
        let code: &[u8] = &[opcode];
        let _ = super::DISPATCH_TABLE[opcode as usize](&mut interp, &class, code, &mut frame, opcode)?;
        Ok(frame.stack)
    }

    #[test]
    fn test_table_nop() {
        let stack = run_opcode(0x00, vec![], vec![]).unwrap();
        assert!(stack.is_empty());
    }

    #[test]
    fn test_table_aconst_null() {
        let stack = run_opcode(0x01, vec![], vec![]).unwrap();
        assert_eq!(stack.len(), 1);
        assert_eq!(stack[0], Value::Null);
    }

    #[test]
    fn test_table_iconst_0() {
        let stack = run_opcode(0x03, vec![], vec![]).unwrap();
        assert_eq!(stack, vec![Value::Int(0)]);
    }

    #[test]
    fn test_table_iconst_5() {
        let stack = run_opcode(0x08, vec![], vec![]).unwrap();
        assert_eq!(stack, vec![Value::Int(5)]);
    }

    #[test]
    fn test_table_iload_0() {
        let stack = run_opcode(0x1a, vec![Value::Int(42)], vec![]).unwrap();
        assert_eq!(stack, vec![Value::Int(42)]);
    }

    #[test]
    fn test_table_aload_0() {
        let stack = run_opcode(0x2a, vec![Value::Reference(7)], vec![]).unwrap();
        assert_eq!(stack, vec![Value::Reference(7)]);
    }

    #[test]
    fn test_table_istore_0() {
        let stack = run_opcode(0x3b, vec![Value::Int(0); 2], vec![Value::Int(99)]).unwrap();
        assert!(stack.is_empty());
    }

    #[test]
    fn test_table_pop() {
        let stack = run_opcode(0x57, vec![], vec![Value::Int(1), Value::Int(2)]).unwrap();
        assert_eq!(stack, vec![Value::Int(1)]);
    }

    #[test]
    fn test_table_dup() {
        let stack = run_opcode(0x59, vec![], vec![Value::Int(42)]).unwrap();
        assert_eq!(stack, vec![Value::Int(42), Value::Int(42)]);
    }

    #[test]
    fn test_table_swap() {
        let stack = run_opcode(0x5f, vec![], vec![Value::Int(1), Value::Int(2)]).unwrap();
        assert_eq!(stack, vec![Value::Int(2), Value::Int(1)]);
    }

    #[test]
    fn test_table_iadd() {
        let stack = run_opcode(0x60, vec![], vec![Value::Int(3), Value::Int(4)]).unwrap();
        assert_eq!(stack, vec![Value::Int(7)]);
    }

    #[test]
    fn test_table_isub() {
        let stack = run_opcode(0x64, vec![], vec![Value::Int(10), Value::Int(3)]).unwrap();
        assert_eq!(stack, vec![Value::Int(7)]);
    }

    #[test]
    fn test_table_imul() {
        let stack = run_opcode(0x68, vec![], vec![Value::Int(6), Value::Int(7)]).unwrap();
        assert_eq!(stack, vec![Value::Int(42)]);
    }

    #[test]
    fn test_table_idiv() {
        let stack = run_opcode(0x6c, vec![], vec![Value::Int(20), Value::Int(4)]).unwrap();
        assert_eq!(stack, vec![Value::Int(5)]);
    }

    #[test]
    fn test_table_idiv_by_zero() {
        let result = run_opcode(0x6c, vec![], vec![Value::Int(5), Value::Int(0)]);
        assert!(result.is_err());
    }

    #[test]
    fn test_table_return_breaks() {
        let mut interp = Interpreter::new();
        let class = ClassFile {
            magic: 0xCAFEBABE,
            minor_version: 0,
            major_version: 52,
            constant_pool: vec![],
            access_flags: 0,
            this_class: 0,
            super_class: 0,
            interfaces: vec![],
            fields: vec![],
            methods: vec![],
            attributes: vec![],
        };
        let mut frame = StackFrame::new(2, 2, "test".to_string());
        let code: &[u8] = &[0xb1];
        let cont = super::DISPATCH_TABLE[0xb1 as usize](&mut interp, &class, code, &mut frame, 0xb1).unwrap();
        assert!(!cont, "return should signal break");
    }
}
