//! Instruction dispatch - handles individual bytecode opcodes.

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

use super::utils;
use super::Interpreter;

impl Interpreter {
    /// Resolve ldc constant pool entry to Value (for ldc, ldc_w, ldc2_w)
    fn resolve_ldc(&mut self, class: &ClassFile, index: usize) -> Result<Value, JvmError> {
        let entry = class.constant_pool.get(index).ok_or_else(|| {
            to_runtime_error_enum(RuntimeError::IllegalArgument(format!(
                "Constant pool index {} out of bounds",
                index
            )))
        })?;
        match entry {
            ConstantPoolEntry::ConstantInteger { bytes } => Ok(Value::Int(*bytes)),
            ConstantPoolEntry::ConstantFloat { bytes } => Ok(Value::Float(*bytes)),
            ConstantPoolEntry::ConstantString { string_index } => {
                let s = class.get_string(*string_index).unwrap_or_default();
                let addr = self.memory.heap.allocate_string(s);
                Ok(Value::Reference(addr))
            }
            _ => Err(to_runtime_error_enum(RuntimeError::IllegalArgument(
                "ldc: invalid constant type".to_string(),
            ))),
        }
    }

    fn resolve_ldc2_w(&self, class: &ClassFile, index: usize) -> Result<Value, JvmError> {
        let entry = class.constant_pool.get(index).ok_or_else(|| {
            to_runtime_error_enum(RuntimeError::IllegalArgument(format!(
                "Constant pool index {} out of bounds",
                index
            )))
        })?;
        match entry {
            ConstantPoolEntry::ConstantLong { bytes } => Ok(Value::Long(*bytes)),
            ConstantPoolEntry::ConstantDouble { bytes } => Ok(Value::Double(*bytes)),
            _ => Err(to_runtime_error_enum(RuntimeError::IllegalArgument(
                "ldc2_w: expected Long or Double".to_string(),
            ))),
        }
    }

    fn resolve_class(&mut self, class: &ClassFile, index: usize) -> Result<String, JvmError> {
        let entry = class.constant_pool.get(index).ok_or_else(|| {
            to_runtime_error_enum(RuntimeError::IllegalArgument(format!(
                "Constant pool index {} out of bounds",
                index
            )))
        })?;
        match entry {
            ConstantPoolEntry::ConstantClass { name_index } => {
                let class_name = class.get_string(*name_index).unwrap_or_default();
                // Ensure class is loaded
                if !self.class_loader.is_class_loaded(&class_name) {
                    let _ = self.class_loader.load_class(&class_name);
                }
                Ok(class_name)
            }
            _ => Err(to_runtime_error_enum(RuntimeError::IllegalArgument(
                "Expected Class constant".to_string(),
            ))),
        }
    }

    /// Ensure java/lang/System.out is initialized (synthetic PrintStream)
    pub(crate) fn ensure_system_out(&mut self) {
        let fields = self
            .memory
            .static_fields
            .entry("java/lang/System".to_string())
            .or_insert_with(std::collections::HashMap::new);
        if !fields.contains_key("out") {
            let addr = self.memory.heap.allocate("java/io/PrintStream".to_string());
            fields.insert("out".to_string(), Value::Reference(addr));
        }
    }
    /// Dispatch a single bytecode instruction. Returns false to break (return).
    #[inline(always)]
    pub(crate) fn dispatch_instruction(
        &mut self,
        class: &ClassFile,
        code: &[u8],
        frame: &mut StackFrame,
        opcode: u8,
    ) -> Result<bool, JvmError> {
        match opcode {
            // Constants
            0x00 => {} // nop
            0x01 => frame.push(Value::Null)?,
            0x02 => frame.push(Value::Int(-1))?,
            0x03 => frame.push(Value::Int(0))?,
            0x04 => frame.push(Value::Int(1))?,
            0x05 => frame.push(Value::Int(2))?,
            0x06 => frame.push(Value::Int(3))?,
            0x07 => frame.push(Value::Int(4))?,
            0x08 => frame.push(Value::Int(5))?,
            0x10 => {
                // bipush
                if frame.pc < code.len() {
                    let byte_val = code[frame.pc] as i8 as i32;
                    frame.pc += 1;
                    frame.push(Value::Int(byte_val))?;
                }
            }
            0x11 => {
                // sipush
                if frame.pc + 1 < code.len() {
                    let short_val = utils::read_i16(code, frame.pc) as i32;
                    frame.pc += 2;
                    frame.push(Value::Int(short_val))?;
                }
            }
            0x12 => {
                // ldc
                if frame.pc < code.len() {
                    let idx = code[frame.pc] as usize;
                    frame.pc += 1;
                    let val = self.resolve_ldc(class, idx)?;
                    frame.push(val)?;
                }
            }
            0x13 => {
                // ldc_w
                if frame.pc + 1 < code.len() {
                    let idx = utils::read_u16(code, frame.pc) as usize;
                    frame.pc += 2;
                    let val = self.resolve_ldc(class, idx)?;
                    frame.push(val)?;
                }
            }
            0x14 => {
                // ldc2_w
                if frame.pc + 1 < code.len() {
                    let idx = utils::read_u16(code, frame.pc) as usize;
                    frame.pc += 2;
                    let val = self.resolve_ldc2_w(class, idx)?;
                    frame.push(val)?;
                }
            }
            // Loads
            0x1a..=0x1d => {
                let idx = (opcode - 0x1a) as usize;
                let v = frame.load_local(idx)?;
                frame.push(v)?;
            }
            0x15 => {
                // iload
                if frame.pc < code.len() {
                    let idx = code[frame.pc] as usize;
                    frame.pc += 1;
                    let v = frame.load_local(idx)?;
                    frame.push(v)?;
                }
            }
            // Stores
            0x3b..=0x3e => {
                let idx = (opcode - 0x3b) as usize;
                let v = frame.pop()?;
                frame.store_local(idx, v)?;
            }
            0x36 => {
                // istore
                if frame.pc < code.len() {
                    let idx = code[frame.pc] as usize;
                    frame.pc += 1;
                    let v = frame.pop()?;
                    frame.store_local(idx, v)?;
                }
            }
            // aload variants
            0x19 => {
                // aload
                if frame.pc < code.len() {
                    let idx = code[frame.pc] as usize;
                    frame.pc += 1;
                    let v = frame.load_local(idx)?;
                    frame.push(v)?;
                }
            }
            0x2a..=0x2d => {
                let idx = (opcode - 0x2a) as usize;
                let v = frame.load_local(idx)?;
                frame.push(v)?;
            }
            0x3a => {
                // astore
                if frame.pc < code.len() {
                    let idx = code[frame.pc] as usize;
                    frame.pc += 1;
                    let v = frame.pop()?;
                    frame.store_local(idx, v)?;
                }
            }
            0x4b..=0x4e => {
                let idx = (opcode - 0x4b) as usize;
                let v = frame.pop()?;
                frame.store_local(idx, v)?;
            }
            // Stack manipulation
            0x57 => {
                frame.pop()?;
            }
            0x58 => {
                // pop2
                let v1 = frame.pop()?;
                if !v1.is_category_2() {
                    frame.pop()?;
                }
            }
            0x59 => {
                let v = frame.peek()?.clone();
                frame.push(v)?;
            }
            0x5a => {
                let v1 = frame.pop()?;
                let v2 = frame.pop()?;
                frame.push(v1.clone())?;
                frame.push(v2)?;
                frame.push(v1)?;
            }
            0x5b => {
                // dup_x2
                let v1 = frame.pop()?;
                let v2 = frame.pop()?;
                if v2.is_category_2() {
                    // Form 2: ..., v2, v1 -> ..., v1, v2, v1 (v2 is cat 2)
                    frame.push(v1.clone())?;
                    frame.push(v2)?;
                    frame.push(v1)?;
                } else {
                    // Form 1: ..., v3, v2, v1 -> ..., v1, v3, v2, v1 (v2 is cat 1)
                    let v3 = frame.pop()?;
                    frame.push(v1.clone())?;
                    frame.push(v3)?;
                    frame.push(v2)?;
                    frame.push(v1)?;
                }
            }
            0x5c => {
                // dup2
                let v1 = frame.pop()?;
                if v1.is_category_2() {
                    // Form 2: ..., v -> ..., v, v
                    frame.push(v1.clone())?;
                    frame.push(v1)?;
                } else {
                    // Form 1: ..., v2, v1 -> ..., v2, v1, v2, v1
                    let v2 = frame.pop()?;
                    frame.push(v2.clone())?;
                    frame.push(v1.clone())?;
                    frame.push(v2)?;
                    frame.push(v1)?;
                }
            }
            0x5d => {
                // dup2_x1
                let v1 = frame.pop()?;
                if v1.is_category_2() {
                    // Form 2: ..., v2, v1 -> ..., v1, v2, v1 (v1 is cat 2)
                    let v2 = frame.pop()?;
                    frame.push(v1.clone())?;
                    frame.push(v2)?;
                    frame.push(v1)?;
                } else {
                    // Form 1: ..., v3, v2, v1 -> ..., v2, v1, v3, v2, v1 (v1 is cat 1)
                    let v2 = frame.pop()?;
                    let v3 = frame.pop()?;
                    frame.push(v2.clone())?;
                    frame.push(v1.clone())?;
                    frame.push(v3)?;
                    frame.push(v2)?;
                    frame.push(v1)?;
                }
            }
            0x5e => {
                // dup2_x2
                let v1 = frame.pop()?;
                if v1.is_category_2() {
                    // v1 is cat 2 (2 words)
                    let v2 = frame.pop()?;
                    if v2.is_category_2() {
                        // Form 4: ..., v2, v1 -> ..., v1, v2, v1 (v2 is cat 2)
                        frame.push(v1.clone())?;
                        frame.push(v2)?;
                        frame.push(v1)?;
                    } else {
                        // v2 is cat 1. Need another cat 1.
                        let v3 = frame.pop()?;
                        // Form 2: ..., v3, v2, v1 -> ..., v1, v3, v2, v1
                        frame.push(v1.clone())?;
                        frame.push(v3)?;
                        frame.push(v2)?;
                        frame.push(v1)?;
                    }
                } else {
                    // v1 is cat 1. v2 MUST be cat 1.
                    let v2 = frame.pop()?;
                    let v3 = frame.pop()?;
                    if v3.is_category_2() {
                        // Form 3: ..., v3, v2, v1 -> ..., v2, v1, v3, v2, v1
                        frame.push(v2.clone())?;
                        frame.push(v1.clone())?;
                        frame.push(v3)?;
                        frame.push(v2)?;
                        frame.push(v1)?;
                    } else {
                        // v3 is cat 1. Need another cat 1.
                        let v4 = frame.pop()?;
                        // Form 1: ..., v4, v3, v2, v1 -> ..., v2, v1, v4, v3, v2, v1
                        frame.push(v2.clone())?;
                        frame.push(v1.clone())?;
                        frame.push(v4)?;
                        frame.push(v3)?;
                        frame.push(v2)?;
                        frame.push(v1)?;
                    }
                }
            }
            0x5f => {
                let v1 = frame.pop()?;
                let v2 = frame.pop()?;
                frame.push(v1)?;
                frame.push(v2)?;
            }
            0x84 => {
                // iinc
                if frame.pc + 2 <= code.len() {
                    let idx = code[frame.pc] as usize;
                    let delta = code[frame.pc + 1] as i8 as i32;
                    frame.pc += 2;
                    let v = frame.load_local(idx)?.as_int();
                    frame.store_local(idx, Value::Int(v.wrapping_add(delta)))?;
                }
            }
            // Integer arithmetic
            0x60 => {
                let b = frame.pop()?.as_int();
                let a = frame.pop()?.as_int();
                frame.push(Value::Int(a.wrapping_add(b)))?;
            }
            0x61 => {
                // ladd
                let b = frame.pop()?.as_long();
                let a = frame.pop()?.as_long();
                frame.push(Value::Long(a.wrapping_add(b)))?;
            }
            0x62 => {
                // fadd
                let b = frame.pop()?.as_float();
                let a = frame.pop()?.as_float();
                frame.push(Value::Float(a + b))?;
            }
            0x63 => {
                // dadd
                let b = frame.pop()?.as_double();
                let a = frame.pop()?.as_double();
                frame.push(Value::Double(a + b))?;
            }
            0x64 => {
                let b = frame.pop()?.as_int();
                let a = frame.pop()?.as_int();
                frame.push(Value::Int(a.wrapping_sub(b)))?;
            }
            0x65 => {
                // lsub
                let b = frame.pop()?.as_long();
                let a = frame.pop()?.as_long();
                frame.push(Value::Long(a.wrapping_sub(b)))?;
            }
            0x66 => {
                // fsub
                let b = frame.pop()?.as_float();
                let a = frame.pop()?.as_float();
                frame.push(Value::Float(a - b))?;
            }
            0x67 => {
                // dsub
                let b = frame.pop()?.as_double();
                let a = frame.pop()?.as_double();
                frame.push(Value::Double(a - b))?;
            }
            0x68 => {
                let b = frame.pop()?.as_int();
                let a = frame.pop()?.as_int();
                frame.push(Value::Int(a.wrapping_mul(b)))?;
            }
            0x69 => {
                // lmul
                let b = frame.pop()?.as_long();
                let a = frame.pop()?.as_long();
                frame.push(Value::Long(a.wrapping_mul(b)))?;
            }
            0x6a => {
                // fmul
                let b = frame.pop()?.as_float();
                let a = frame.pop()?.as_float();
                frame.push(Value::Float(a * b))?;
            }
            0x6b => {
                // dmul
                let b = frame.pop()?.as_double();
                let a = frame.pop()?.as_double();
                frame.push(Value::Double(a * b))?;
            }
            0x6c => {
                let b = frame.pop()?.as_int();
                let a = frame.pop()?.as_int();
                let result = if b == 0 {
                    return Err(to_runtime_error_enum(RuntimeError::DivisionByZero));
                } else {
                    a.wrapping_div(b)
                };
                frame.push(Value::Int(result))?;
            }
            0x6d => {
                // ldiv
                let b = frame.pop()?.as_long();
                let a = frame.pop()?.as_long();
                if b == 0 {
                    return Err(to_runtime_error_enum(RuntimeError::DivisionByZero));
                }
                frame.push(Value::Long(a.wrapping_div(b)))?;
            }
            0x6e => {
                // fdiv
                let b = frame.pop()?.as_float();
                let a = frame.pop()?.as_float();
                frame.push(Value::Float(a / b))?;
            }
            0x6f => {
                // ddiv
                let b = frame.pop()?.as_double();
                let a = frame.pop()?.as_double();
                frame.push(Value::Double(a / b))?;
            }
            0x70 => {
                let b = frame.pop()?.as_int();
                let a = frame.pop()?.as_int();
                let result = if b == 0 {
                    return Err(to_runtime_error_enum(RuntimeError::DivisionByZero));
                } else {
                    a.wrapping_rem(b)
                };
                frame.push(Value::Int(result))?;
            }
            0x71 => {
                // lrem
                let b = frame.pop()?.as_long();
                let a = frame.pop()?.as_long();
                if b == 0 {
                    return Err(to_runtime_error_enum(RuntimeError::DivisionByZero));
                }
                frame.push(Value::Long(a % b))?;
            }
            0x72 => {
                // frem
                let b = frame.pop()?.as_float();
                let a = frame.pop()?.as_float();
                frame.push(Value::Float(a % b))?;
            }
            0x73 => {
                // drem
                let b = frame.pop()?.as_double();
                let a = frame.pop()?.as_double();
                frame.push(Value::Double(a % b))?;
            }
            0x74 => {
                // ineg
                let v = frame.pop()?.as_int();
                frame.push(Value::Int(-v))?;
            }
            0x75 => {
                // lneg
                let v = frame.pop()?.as_long();
                frame.push(Value::Long(-v))?;
            }
            0x76 => {
                // fneg
                let v = frame.pop()?.as_float();
                frame.push(Value::Float(-v))?;
            }
            0x77 => {
                // dneg
                let v = frame.pop()?.as_double();
                frame.push(Value::Double(-v))?;
            }
            // Shifts
            0x78 => {
                // ishl
                let s = frame.pop()?.as_int() & 0x1f;
                let v = frame.pop()?.as_int();
                frame.push(Value::Int(v << s))?;
            }
            0x79 => {
                // lshl
                let s = frame.pop()?.as_int() & 0x3f;
                let v = frame.pop()?.as_long();
                frame.push(Value::Long(v << s))?;
            }
            0x7a => {
                // ishr
                let s = frame.pop()?.as_int() & 0x1f;
                let v = frame.pop()?.as_int();
                frame.push(Value::Int(v >> s))?;
            }
            0x7b => {
                // lshr
                let s = frame.pop()?.as_int() & 0x3f;
                let v = frame.pop()?.as_long();
                frame.push(Value::Long(v >> s))?;
            }
            0x7c => {
                // iushr
                let s = frame.pop()?.as_int() & 0x1f;
                let v = frame.pop()?.as_int() as u32;
                frame.push(Value::Int((v >> s) as i32))?;
            }
            0x7d => {
                // lushr
                let s = frame.pop()?.as_int() & 0x3f;
                let v = frame.pop()?.as_long() as u64;
                frame.push(Value::Long((v >> s) as i64))?;
            }
            // Logic
            0x7e => {
                // iand
                let v2 = frame.pop()?.as_int();
                let v1 = frame.pop()?.as_int();
                frame.push(Value::Int(v1 & v2))?;
            }
            0x7f => {
                // land
                let v2 = frame.pop()?.as_long();
                let v1 = frame.pop()?.as_long();
                frame.push(Value::Long(v1 & v2))?;
            }
            0x80 => {
                // ior
                let v2 = frame.pop()?.as_int();
                let v1 = frame.pop()?.as_int();
                frame.push(Value::Int(v1 | v2))?;
            }
            0x81 => {
                // lor
                let v2 = frame.pop()?.as_long();
                let v1 = frame.pop()?.as_long();
                frame.push(Value::Long(v1 | v2))?;
            }
            0x82 => {
                // ixor
                let v2 = frame.pop()?.as_int();
                let v1 = frame.pop()?.as_int();
                frame.push(Value::Int(v1 ^ v2))?;
            }
            0x83 => {
                // lxor
                let v2 = frame.pop()?.as_long();
                let v1 = frame.pop()?.as_long();
                frame.push(Value::Long(v1 ^ v2))?;
            }
            // Conversions
            0x85 => {
                // i2l
                let v = frame.pop()?.as_int();
                frame.push(Value::Long(v as i64))?;
            }
            0x86 => {
                // i2f
                let v = frame.pop()?.as_int();
                frame.push(Value::Float(v as f32))?;
            }
            0x87 => {
                // i2d
                let v = frame.pop()?.as_int();
                frame.push(Value::Double(v as f64))?;
            }
            0x88 => {
                // l2i
                let v = frame.pop()?.as_long();
                frame.push(Value::Int(v as i32))?;
            }
            0x89 => {
                // l2f
                let v = frame.pop()?.as_long();
                frame.push(Value::Float(v as f32))?;
            }
            0x8a => {
                // l2d
                let v = frame.pop()?.as_long();
                frame.push(Value::Double(v as f64))?;
            }
            0x8b => {
                // f2i
                let v = frame.pop()?.as_float();
                frame.push(Value::Int(v as i32))?;
            }
            0x8c => {
                // f2l
                let v = frame.pop()?.as_float();
                frame.push(Value::Long(v as i64))?;
            }
            0x8d => {
                // f2d
                let v = frame.pop()?.as_float();
                frame.push(Value::Double(v as f64))?;
            }
            0x8e => {
                // d2i
                let v = frame.pop()?.as_double();
                frame.push(Value::Int(v as i32))?;
            }
            0x8f => {
                // d2l
                let v = frame.pop()?.as_double();
                frame.push(Value::Long(v as i64))?;
            }
            0x90 => {
                // d2f
                let v = frame.pop()?.as_double();
                frame.push(Value::Float(v as f32))?;
            }
            0x91 => {
                // i2b
                let v = frame.pop()?.as_int();
                frame.push(Value::Int(v as i8 as i32))?;
            }
            0x92 => {
                // i2c
                let v = frame.pop()?.as_int();
                frame.push(Value::Int(v as u16 as i32))?;
            }
            0x93 => {
                // i2s
                let v = frame.pop()?.as_int();
                frame.push(Value::Int(v as i16 as i32))?;
            }
            // Comparisons
            0x94 => {
                // lcmp
                let v2 = frame.pop()?.as_long();
                let v1 = frame.pop()?.as_long();
                let res = if v1 > v2 {
                    1
                } else if v1 == v2 {
                    0
                } else {
                    -1
                };
                frame.push(Value::Int(res))?;
            }
            0x95 => {
                // fcmpl
                let v2 = frame.pop()?.as_float();
                let v1 = frame.pop()?.as_float();
                if v1.is_nan() || v2.is_nan() {
                    frame.push(Value::Int(-1))?;
                } else if v1 > v2 {
                    frame.push(Value::Int(1))?;
                } else if v1 == v2 {
                    frame.push(Value::Int(0))?;
                } else {
                    frame.push(Value::Int(-1))?;
                }
            }
            0x96 => {
                // fcmpg
                let v2 = frame.pop()?.as_float();
                let v1 = frame.pop()?.as_float();
                if v1.is_nan() || v2.is_nan() {
                    frame.push(Value::Int(1))?;
                } else if v1 > v2 {
                    frame.push(Value::Int(1))?;
                } else if v1 == v2 {
                    frame.push(Value::Int(0))?;
                } else {
                    frame.push(Value::Int(-1))?;
                }
            }
            0x97 => {
                // dcmpl
                let v2 = frame.pop()?.as_double();
                let v1 = frame.pop()?.as_double();
                if v1.is_nan() || v2.is_nan() {
                    frame.push(Value::Int(-1))?;
                } else if v1 > v2 {
                    frame.push(Value::Int(1))?;
                } else if v1 == v2 {
                    frame.push(Value::Int(0))?;
                } else {
                    frame.push(Value::Int(-1))?;
                }
            }
            0x98 => {
                // dcmpg
                let v2 = frame.pop()?.as_double();
                let v1 = frame.pop()?.as_double();
                if v1.is_nan() || v2.is_nan() {
                    frame.push(Value::Int(1))?;
                } else if v1 > v2 {
                    frame.push(Value::Int(1))?;
                } else if v1 == v2 {
                    frame.push(Value::Int(0))?;
                } else {
                    frame.push(Value::Int(-1))?;
                }
            }
            // Control flow
            0x99 => {
                // ifeq
                if frame.pc + 1 < code.len() {
                    let v = frame.pop()?.as_int();
                    let offset = utils::read_i16(code, frame.pc) as i32;
                    frame.pc += 2;
                    if v == 0 {
                        frame.pc = (frame.pc as i32 + offset - 3) as usize;
                    }
                }
            }
            0x9a => {
                // ifne
                if frame.pc + 1 < code.len() {
                    let v = frame.pop()?.as_int();
                    let offset = utils::read_i16(code, frame.pc) as i32;
                    frame.pc += 2;
                    if v != 0 {
                        frame.pc = (frame.pc as i32 + offset - 3) as usize;
                    }
                }
            }
            0x9b => {
                // iflt
                if frame.pc + 1 < code.len() {
                    let v = frame.pop()?.as_int();
                    let offset = utils::read_i16(code, frame.pc) as i32;
                    frame.pc += 2;
                    if v < 0 {
                        frame.pc = (frame.pc as i32 + offset - 3) as usize;
                    }
                }
            }
            0x9c => {
                // ifge
                if frame.pc + 1 < code.len() {
                    let v = frame.pop()?.as_int();
                    let offset = utils::read_i16(code, frame.pc) as i32;
                    frame.pc += 2;
                    if v >= 0 {
                        frame.pc = (frame.pc as i32 + offset - 3) as usize;
                    }
                }
            }
            0x9d => {
                // ifgt
                if frame.pc + 1 < code.len() {
                    let v = frame.pop()?.as_int();
                    let offset = utils::read_i16(code, frame.pc) as i32;
                    frame.pc += 2;
                    if v > 0 {
                        frame.pc = (frame.pc as i32 + offset - 3) as usize;
                    }
                }
            }
            0x9e => {
                // ifle
                if frame.pc + 1 < code.len() {
                    let v = frame.pop()?.as_int();
                    let offset = utils::read_i16(code, frame.pc) as i32;
                    frame.pc += 2;
                    if v <= 0 {
                        frame.pc = (frame.pc as i32 + offset - 3) as usize;
                    }
                }
            }
            0x9f => {
                // if_icmpeq
                if frame.pc + 1 < code.len() {
                    let v2 = frame.pop()?.as_int();
                    let v1 = frame.pop()?.as_int();
                    let offset = utils::read_i16(code, frame.pc) as i32;
                    frame.pc += 2;
                    if v1 == v2 {
                        frame.pc = (frame.pc as i32 + offset - 3) as usize;
                    }
                }
            }
            0xa0 => {
                // if_icmpne
                if frame.pc + 1 < code.len() {
                    let v2 = frame.pop()?.as_int();
                    let v1 = frame.pop()?.as_int();
                    let offset = utils::read_i16(code, frame.pc) as i32;
                    frame.pc += 2;
                    if v1 != v2 {
                        frame.pc = (frame.pc as i32 + offset - 3) as usize;
                    }
                }
            }
            0xa1 => {
                // if_icmplt
                if frame.pc + 1 < code.len() {
                    let v2 = frame.pop()?.as_int();
                    let v1 = frame.pop()?.as_int();
                    let offset = utils::read_i16(code, frame.pc) as i32;
                    frame.pc += 2;
                    if v1 < v2 {
                        frame.pc = (frame.pc as i32 + offset - 3) as usize;
                    }
                }
            }
            0xa2 => {
                // if_icmpge
                if frame.pc + 1 < code.len() {
                    let v2 = frame.pop()?.as_int();
                    let v1 = frame.pop()?.as_int();
                    let offset = utils::read_i16(code, frame.pc) as i32;
                    frame.pc += 2;
                    if v1 >= v2 {
                        frame.pc = (frame.pc as i32 + offset - 3) as usize;
                    }
                }
            }
            0xa3 => {
                // if_icmpgt
                if frame.pc + 1 < code.len() {
                    let v2 = frame.pop()?.as_int();
                    let v1 = frame.pop()?.as_int();
                    let offset = utils::read_i16(code, frame.pc) as i32;
                    frame.pc += 2;
                    if v1 > v2 {
                        frame.pc = (frame.pc as i32 + offset - 3) as usize;
                    }
                }
            }
            0xa4 => {
                // if_icmple
                if frame.pc + 1 < code.len() {
                    let v2 = frame.pop()?.as_int();
                    let v1 = frame.pop()?.as_int();
                    let offset = utils::read_i16(code, frame.pc) as i32;
                    frame.pc += 2;
                    if v1 <= v2 {
                        frame.pc = (frame.pc as i32 + offset - 3) as usize;
                    }
                }
            }
            0xa7 => {
                // goto
                if frame.pc + 1 < code.len() {
                    let offset = utils::read_i16(code, frame.pc) as i32;
                    frame.pc = (frame.pc as i32 - 1 + offset) as usize;
                }
            }
            0xa8 => {
                // jsr
                if frame.pc + 1 < code.len() {
                    let offset = utils::read_i16(code, frame.pc) as i32;
                    let next_pc = frame.pc + 2;
                    frame.push(Value::ReturnAddress(next_pc as u32))?;
                    frame.pc = (frame.pc as i32 - 1 + offset) as usize;
                }
            }
            0xa9 => {
                // ret
                if frame.pc < code.len() {
                    let index = code[frame.pc] as usize;
                    frame.pc += 1;
                    let val = frame.load_local(index)?;
                    if let Value::ReturnAddress(pc) = val {
                        frame.pc = pc as usize;
                    } else {
                        return Err(to_runtime_error_enum(RuntimeError::UnsupportedOperation(
                            "ret: expected ReturnAddress".to_string(),
                        )));
                    }
                }
            }
            0xaa => {
                // tableswitch
                let opcode_addr = frame.pc - 1;
                // padding aligns defaultbyte1 to 4-byte boundary from start of method
                // defaultbyte1 is at (opcode_addr + 1 + padding)
                // (opcode_addr + 1 + padding) % 4 == 0
                // frame.pc is currently opcode_addr + 1
                let padding = (4 - (frame.pc % 4)) % 4;
                frame.pc += padding;

                if frame.pc + 12 <= code.len() {
                    let default_offset = utils::read_i32(code, frame.pc);
                    frame.pc += 4;
                    let low = utils::read_i32(code, frame.pc);
                    frame.pc += 4;
                    let high = utils::read_i32(code, frame.pc);
                    frame.pc += 4;

                    let index = frame.pop()?.as_int();
                    if index < low || index > high {
                        frame.pc = (opcode_addr as i32 + default_offset) as usize;
                    } else {
                        let offset_idx = (index - low) as usize;
                        if frame.pc + offset_idx * 4 + 4 <= code.len() {
                            let offset = utils::read_i32(code, frame.pc + offset_idx * 4);
                            frame.pc = (opcode_addr as i32 + offset) as usize;
                        } else {
                            return Err(to_runtime_error_enum(
                                RuntimeError::ArrayIndexOutOfBounds(frame.pc, code.len()),
                            ));
                        }
                    }
                }
            }
            0xab => {
                // lookupswitch
                let opcode_addr = frame.pc - 1;
                let padding = (4 - (frame.pc % 4)) % 4;
                frame.pc += padding;

                if frame.pc + 8 <= code.len() {
                    let default_offset = utils::read_i32(code, frame.pc);
                    frame.pc += 4;
                    let npairs = utils::read_i32(code, frame.pc);
                    frame.pc += 4;

                    let key = frame.pop()?.as_int();
                    let mut found = false;
                    for _ in 0..npairs {
                        if frame.pc + 8 <= code.len() {
                            let match_key = utils::read_i32(code, frame.pc);
                            frame.pc += 4;
                            let offset = utils::read_i32(code, frame.pc);
                            frame.pc += 4;
                            if key == match_key {
                                frame.pc = (opcode_addr as i32 + offset) as usize;
                                found = true;
                                break;
                            }
                        } else {
                            return Err(to_runtime_error_enum(
                                RuntimeError::ArrayIndexOutOfBounds(frame.pc, code.len()),
                            ));
                        }
                    }
                    if !found {
                        frame.pc = (opcode_addr as i32 + default_offset) as usize;
                    }
                }
            }
            0xa5 => {
                // if_acmpeq
                if frame.pc + 1 < code.len() {
                    let v2 = frame.pop()?;
                    let v1 = frame.pop()?;
                    let offset = utils::read_i16(code, frame.pc) as i32;
                    frame.pc += 2;

                    let eq = match (&v1, &v2) {
                        (Value::Reference(r1), Value::Reference(r2)) => r1 == r2,
                        (Value::Null, Value::Null) => true,
                        (Value::Reference(_), Value::Null) => false,
                        (Value::Null, Value::Reference(_)) => false,
                        (Value::ArrayRef(r1), Value::ArrayRef(r2)) => r1 == r2,
                        // Arrays are objects too
                        (Value::Reference(r1), Value::ArrayRef(r2)) => *r1 == *r2,
                        (Value::ArrayRef(r1), Value::Reference(r2)) => *r1 == *r2,
                        _ => false, // Different types or invalid comparison
                    };

                    if eq {
                        frame.pc = (frame.pc as i32 + offset - 3) as usize;
                    }
                }
            }
            0xa6 => {
                // if_acmpne
                if frame.pc + 1 < code.len() {
                    let v2 = frame.pop()?;
                    let v1 = frame.pop()?;
                    let offset = utils::read_i16(code, frame.pc) as i32;
                    frame.pc += 2;

                    let eq = match (&v1, &v2) {
                        (Value::Reference(r1), Value::Reference(r2)) => r1 == r2,
                        (Value::Null, Value::Null) => true,
                        (Value::Reference(_), Value::Null) => false,
                        (Value::Null, Value::Reference(_)) => false,
                        (Value::ArrayRef(r1), Value::ArrayRef(r2)) => r1 == r2,
                        (Value::Reference(r1), Value::ArrayRef(r2)) => *r1 == *r2,
                        (Value::ArrayRef(r1), Value::Reference(r2)) => *r1 == *r2,
                        _ => false,
                    };

                    if !eq {
                        frame.pc = (frame.pc as i32 + offset - 3) as usize;
                    }
                }
            }
            0xc0 => {
                // checkcast
                if frame.pc + 2 <= code.len() {
                    let index = utils::read_u16(code, frame.pc) as usize;
                    frame.pc += 2;
                    let target_class = self.resolve_class(class, index)?;

                    if let Ok(val) = frame.peek() {
                        if !matches!(val, Value::Null) {
                            let obj_class = self
                                .get_value_class(val)
                                .map_err(|e| to_runtime_error_enum(e))?;
                            if !self.is_assignable_from(&obj_class, &target_class) {
                                return Err(to_runtime_error_enum(
                                    RuntimeError::ClassCastException(obj_class, target_class),
                                ));
                            }
                        }
                    }
                }
            }
            0xc1 => {
                // instanceof
                if frame.pc + 2 <= code.len() {
                    let index = utils::read_u16(code, frame.pc) as usize;
                    frame.pc += 2;
                    let target_class = self.resolve_class(class, index)?;

                    let val = frame.pop()?;
                    let result = if matches!(val, Value::Null) {
                        0
                    } else {
                        let obj_class = self
                            .get_value_class(&val)
                            .map_err(|e| to_runtime_error_enum(e))?;
                        if self.is_assignable_from(&obj_class, &target_class) {
                            1
                        } else {
                            0
                        }
                    };
                    frame.push(Value::Int(result))?;
                }
            }
            0xc5 => {
                // multianewarray
                if frame.pc + 3 <= code.len() {
                    let index = utils::read_u16(code, frame.pc) as usize;
                    frame.pc += 2;
                    let dimensions = code[frame.pc];
                    frame.pc += 1;

                    if dimensions < 1 {
                        return Err(to_runtime_error_enum(RuntimeError::IllegalArgument(
                            "Dimensions < 1".to_string(),
                        )));
                    }

                    let target_class = self.resolve_class(class, index)?;

                    let mut counts = Vec::with_capacity(dimensions as usize);
                    for _ in 0..dimensions {
                        counts.push(frame.pop()?.as_int());
                    }
                    counts.reverse();

                    let array_ref = self
                        .allocate_multi_array(&target_class, &counts)
                        .map_err(|e| to_runtime_error_enum(e))?;

                    frame.push(Value::ArrayRef(array_ref))?;
                }
            }
            0xc6 => {
                // ifnull
                if frame.pc + 1 < code.len() {
                    let v = frame.pop()?;
                    let offset = utils::read_i16(code, frame.pc) as i32;
                    frame.pc += 2;
                    let is_null = matches!(v, Value::Null);
                    if is_null {
                        frame.pc = (frame.pc as i32 + offset - 3) as usize;
                    }
                }
            }
            0xc7 => {
                // ifnonnull
                if frame.pc + 1 < code.len() {
                    let v = frame.pop()?;
                    let offset = utils::read_i16(code, frame.pc) as i32;
                    frame.pc += 2;
                    let is_null = matches!(v, Value::Null);
                    if !is_null {
                        frame.pc = (frame.pc as i32 + offset - 3) as usize;
                    }
                }
            }
            0xc8 => {
                // goto_w
                if frame.pc + 3 < code.len() {
                    let offset = utils::read_i32(code, frame.pc);
                    // goto_w is 5 bytes: opcode + 4 bytes offset
                    // frame.pc points to first byte of offset
                    // target = opcode_addr + offset = (frame.pc - 1) + offset
                    frame.pc = (frame.pc as i32 - 1 + offset) as usize;
                }
            }
            0xc9 => {
                // jsr_w
                if frame.pc + 3 < code.len() {
                    let offset = utils::read_i32(code, frame.pc);
                    // jsr_w is 5 bytes. Next instruction is at opcode_addr + 5 = frame.pc + 4
                    let next_pc = frame.pc + 4;
                    frame.push(Value::ReturnAddress(next_pc as u32))?;
                    frame.pc = (frame.pc as i32 - 1 + offset) as usize;
                }
            }
            0xb1 => return Ok(false), // return
            0xb6 => {
                if frame.pc + 1 < code.len() {
                    let index = utils::read_u16(code, frame.pc) as usize;
                    frame.pc += 2;
                    self.invoke_virtual(class, frame, index)?;
                }
            }
            0xb8 => {
                if frame.pc + 1 < code.len() {
                    let index = utils::read_u16(code, frame.pc) as usize;
                    frame.pc += 2;
                    self.invoke_static(class, frame, index)?;
                }
            }
            0xba => {
                if frame.pc + 2 < code.len() {
                    let index = utils::read_u16(code, frame.pc) as usize;
                    frame.pc += 2;
                    let _bootstrap = utils::read_u16(code, frame.pc);
                    frame.pc += 2;
                    self.handle_invokedynamic(class, frame, index)?;
                }
            }
            0xb2 => {
                if frame.pc + 1 < code.len() {
                    let index = utils::read_u16(code, frame.pc) as usize;
                    frame.pc += 2;
                    self.get_static(class, frame, index)?;
                }
            }
            0xb3 => {
                if frame.pc + 1 < code.len() {
                    let index = utils::read_u16(code, frame.pc) as usize;
                    frame.pc += 2;
                    self.put_static(class, frame, index)?;
                }
            }
            0xb4 => {
                if frame.pc + 1 < code.len() {
                    let index = utils::read_u16(code, frame.pc) as usize;
                    frame.pc += 2;
                    self.get_field(class, frame, index)?;
                }
            }
            0xb5 => {
                if frame.pc + 1 < code.len() {
                    let index = utils::read_u16(code, frame.pc) as usize;
                    frame.pc += 2;
                    self.put_field(class, frame, index)?;
                }
            }
            0xb7 => {
                if frame.pc + 1 < code.len() {
                    let index = utils::read_u16(code, frame.pc) as usize;
                    frame.pc += 2;
                    self.invoke_special(class, frame, index)?;
                }
            }
            0xbb => {
                if frame.pc + 1 < code.len() {
                    let index = utils::read_u16(code, frame.pc) as usize;
                    frame.pc += 2;
                    self.do_new(class, frame, index)?;
                }
            }
            0xbc => {
                if frame.pc < code.len() {
                    let atype = code[frame.pc];
                    frame.pc += 1;
                    let len = frame.pop()?.as_int();
                    if len < 0 {
                        return Err(to_runtime_error_enum(
                            RuntimeError::NegativeArraySizeException(len),
                        ));
                    }
                    let arr = match atype {
                        4 => HeapArray::BooleanArray(vec![false; len as usize]),
                        5 => HeapArray::CharArray(vec![0u16; len as usize]),
                        6 => HeapArray::FloatArray(vec![0.0; len as usize]),
                        7 => HeapArray::DoubleArray(vec![0.0; len as usize]),
                        8 => HeapArray::ByteArray(vec![0; len as usize]),
                        9 => HeapArray::ShortArray(vec![0; len as usize]),
                        10 => HeapArray::IntArray(vec![0; len as usize]),
                        11 => HeapArray::LongArray(vec![0; len as usize]),
                        _ => {
                            return Err(to_runtime_error_enum(RuntimeError::IllegalArgument(
                                format!("Unknown array type: {}", atype),
                            )));
                        }
                    };
                    let addr = self.memory.heap.allocate_array(arr);
                    frame.push(Value::ArrayRef(addr))?;
                }
            }
            0xbd => {
                if frame.pc + 1 < code.len() {
                    let index = utils::read_u16(code, frame.pc) as usize;
                    frame.pc += 2;
                    self.do_anewarray(class, frame, index)?;
                }
            }
            0xbe => {
                let arr_ref = frame
                    .pop()?
                    .as_reference()
                    .ok_or_else(|| to_runtime_error_enum(RuntimeError::NullPointerException))?;
                let len = self
                    .memory
                    .heap
                    .array_length(arr_ref)
                    .map_err(|e| to_runtime_error_enum(RuntimeError::from(e)))?;
                frame.push(Value::Int(len as i32))?;
            }
            0x4f => {
                // iastore
                let val = frame.pop()?.as_int();
                let idx = frame.pop()?.as_int();
                let arr_ref = frame
                    .pop()?
                    .as_reference()
                    .ok_or_else(|| to_runtime_error_enum(RuntimeError::NullPointerException))?;
                self.memory
                    .heap
                    .array_set(arr_ref, idx as usize, Value::Int(val))
                    .map_err(|e| to_runtime_error_enum(RuntimeError::from(e)))?;
            }
            0x50 => {
                // lastore
                let val = frame.pop()?.as_long();
                let idx = frame.pop()?.as_int();
                let arr_ref = frame
                    .pop()?
                    .as_reference()
                    .ok_or_else(|| to_runtime_error_enum(RuntimeError::NullPointerException))?;
                self.memory
                    .heap
                    .array_set(arr_ref, idx as usize, Value::Long(val))
                    .map_err(|e| to_runtime_error_enum(RuntimeError::from(e)))?;
            }
            0x51 => {
                // fastore
                let val = frame.pop()?.as_float();
                let idx = frame.pop()?.as_int();
                let arr_ref = frame
                    .pop()?
                    .as_reference()
                    .ok_or_else(|| to_runtime_error_enum(RuntimeError::NullPointerException))?;
                self.memory
                    .heap
                    .array_set(arr_ref, idx as usize, Value::Float(val))
                    .map_err(|e| to_runtime_error_enum(RuntimeError::from(e)))?;
            }
            0x52 => {
                // dastore
                let val = frame.pop()?.as_double();
                let idx = frame.pop()?.as_int();
                let arr_ref = frame
                    .pop()?
                    .as_reference()
                    .ok_or_else(|| to_runtime_error_enum(RuntimeError::NullPointerException))?;
                self.memory
                    .heap
                    .array_set(arr_ref, idx as usize, Value::Double(val))
                    .map_err(|e| to_runtime_error_enum(RuntimeError::from(e)))?;
            }
            0x53 => {
                // aastore
                let val = frame.pop()?;
                let idx = frame.pop()?.as_int();
                let arr_ref = frame
                    .pop()?
                    .as_reference()
                    .ok_or_else(|| to_runtime_error_enum(RuntimeError::NullPointerException))?;
                let ref_val = match &val {
                    Value::Null => Value::Reference(0),
                    _ => val,
                };
                self.memory
                    .heap
                    .array_set(arr_ref, idx as usize, ref_val)
                    .map_err(|e| to_runtime_error_enum(RuntimeError::from(e)))?;
            }
            0x54 => {
                // bastore
                let val = frame.pop()?.as_int();
                let idx = frame.pop()?.as_int();
                let arr_ref = frame
                    .pop()?
                    .as_reference()
                    .ok_or_else(|| to_runtime_error_enum(RuntimeError::NullPointerException))?;
                self.memory
                    .heap
                    .array_set(arr_ref, idx as usize, Value::Int(val))
                    .map_err(|e| to_runtime_error_enum(RuntimeError::from(e)))?;
            }
            0x55 => {
                // castore
                let val = frame.pop()?.as_int();
                let idx = frame.pop()?.as_int();
                let arr_ref = frame
                    .pop()?
                    .as_reference()
                    .ok_or_else(|| to_runtime_error_enum(RuntimeError::NullPointerException))?;
                self.memory
                    .heap
                    .array_set(arr_ref, idx as usize, Value::Int(val))
                    .map_err(|e| to_runtime_error_enum(RuntimeError::from(e)))?;
            }
            0x56 => {
                // sastore
                let val = frame.pop()?.as_int();
                let idx = frame.pop()?.as_int();
                let arr_ref = frame
                    .pop()?
                    .as_reference()
                    .ok_or_else(|| to_runtime_error_enum(RuntimeError::NullPointerException))?;
                self.memory
                    .heap
                    .array_set(arr_ref, idx as usize, Value::Int(val))
                    .map_err(|e| to_runtime_error_enum(RuntimeError::from(e)))?;
            }
            0x2e => {
                // iaload
                let idx = frame.pop()?.as_int();
                let arr_ref = frame
                    .pop()?
                    .as_reference()
                    .ok_or_else(|| to_runtime_error_enum(RuntimeError::NullPointerException))?;
                let val = self
                    .memory
                    .heap
                    .array_get(arr_ref, idx as usize)
                    .map_err(|e| to_runtime_error_enum(RuntimeError::from(e)))?;
                frame.push(val)?;
            }
            0x2f => {
                // laload
                let idx = frame.pop()?.as_int();
                let arr_ref = frame
                    .pop()?
                    .as_reference()
                    .ok_or_else(|| to_runtime_error_enum(RuntimeError::NullPointerException))?;
                let val = self
                    .memory
                    .heap
                    .array_get(arr_ref, idx as usize)
                    .map_err(|e| to_runtime_error_enum(RuntimeError::from(e)))?;
                frame.push(val)?;
            }
            0x30 => {
                // faload
                let idx = frame.pop()?.as_int();
                let arr_ref = frame
                    .pop()?
                    .as_reference()
                    .ok_or_else(|| to_runtime_error_enum(RuntimeError::NullPointerException))?;
                let val = self
                    .memory
                    .heap
                    .array_get(arr_ref, idx as usize)
                    .map_err(|e| to_runtime_error_enum(RuntimeError::from(e)))?;
                frame.push(val)?;
            }
            0x31 => {
                // daload
                let idx = frame.pop()?.as_int();
                let arr_ref = frame
                    .pop()?
                    .as_reference()
                    .ok_or_else(|| to_runtime_error_enum(RuntimeError::NullPointerException))?;
                let val = self
                    .memory
                    .heap
                    .array_get(arr_ref, idx as usize)
                    .map_err(|e| to_runtime_error_enum(RuntimeError::from(e)))?;
                frame.push(val)?;
            }
            0x32 => {
                // aaload
                let idx = frame.pop()?.as_int();
                let arr_ref = frame
                    .pop()?
                    .as_reference()
                    .ok_or_else(|| to_runtime_error_enum(RuntimeError::NullPointerException))?;
                let val = self
                    .memory
                    .heap
                    .array_get(arr_ref, idx as usize)
                    .map_err(|e| to_runtime_error_enum(RuntimeError::from(e)))?;
                frame.push(val)?;
            }
            0x33 => {
                // baload
                let idx = frame.pop()?.as_int();
                let arr_ref = frame
                    .pop()?
                    .as_reference()
                    .ok_or_else(|| to_runtime_error_enum(RuntimeError::NullPointerException))?;
                let val = self
                    .memory
                    .heap
                    .array_get(arr_ref, idx as usize)
                    .map_err(|e| to_runtime_error_enum(RuntimeError::from(e)))?;
                frame.push(val)?;
            }
            0x34 => {
                // caload
                let idx = frame.pop()?.as_int();
                let arr_ref = frame
                    .pop()?
                    .as_reference()
                    .ok_or_else(|| to_runtime_error_enum(RuntimeError::NullPointerException))?;
                let val = self
                    .memory
                    .heap
                    .array_get(arr_ref, idx as usize)
                    .map_err(|e| to_runtime_error_enum(RuntimeError::from(e)))?;
                frame.push(val)?;
            }
            0x35 => {
                // saload
                let idx = frame.pop()?.as_int();
                let arr_ref = frame
                    .pop()?
                    .as_reference()
                    .ok_or_else(|| to_runtime_error_enum(RuntimeError::NullPointerException))?;
                let val = self
                    .memory
                    .heap
                    .array_get(arr_ref, idx as usize)
                    .map_err(|e| to_runtime_error_enum(RuntimeError::from(e)))?;
                frame.push(val)?;
            }
            0xc2 => {
                let obj_ref = frame
                    .pop()?
                    .as_reference()
                    .ok_or_else(|| to_runtime_error_enum(RuntimeError::NullPointerException))?;
                self.memory
                    .heap
                    .monitor_enter(obj_ref, self.current_thread_id)
                    .map_err(|e| to_runtime_error_enum(RuntimeError::from(e)))?;
            }
            0xc3 => {
                let obj_ref = frame
                    .pop()?
                    .as_reference()
                    .ok_or_else(|| to_runtime_error_enum(RuntimeError::NullPointerException))?;
                self.memory
                    .heap
                    .monitor_exit(obj_ref, self.current_thread_id)
                    .map_err(|e| to_runtime_error_enum(RuntimeError::from(e)))?;
            }
            _ => {
                // Skip pc for opcodes that take operands (stub - no-op execution)
                if opcode >= 0x12 && opcode <= 0x14 {
                    frame.pc += if opcode == 0x14 { 2 } else { 1 };
                } else if opcode >= 0x16 && opcode <= 0x19 {
                    frame.pc += 1;
                } else if opcode >= 0x36 && opcode <= 0x3a {
                    frame.pc += 1;
                } else if opcode >= 0x84 && opcode <= 0x84 {
                    frame.pc += 2; // iinc
                } else if opcode >= 0xb2 && opcode <= 0xb5 {
                    frame.pc += 2;
                }
            }
        }
        Ok(true)
    }
}
