//! Hand-rolled JVM `.class` file reader, tailored to the subset we need
//! for BD-J label extraction (Deluxe / dbp / similar frameworks).
//!
//! Spec: JVMS §4 (class file format) and §6 (bytecode). We implement the
//! minimum to expose: constant pool, methods, the `Code` attribute, and
//! a non-allocating bytecode iterator.
//!
//! No external deps beyond `std`. No `unsafe`. No panics on malformed
//! input — every parse fault is a typed [`Error`]. Shared infrastructure
//! for any label parser that needs structured access to .class files
//! inside a `/BDMV/JAR/<x>.jar`.

// Foundation module — public API is staged for `labels::deluxe` (which
// will exercise the bytecode walker) and `labels::dbp`'s refactor onto
// the constant-pool iterator. The dead-code allow comes off as those
// callers land. Tests below cover the API in isolation.
#![allow(dead_code)]

const CLASS_MAGIC: u32 = 0xCAFEBABE;

// ---------------------------------------------------------------------------
// Error type
// ---------------------------------------------------------------------------

#[derive(Debug)]
pub enum Error {
    UnexpectedEof { needed: &'static str },
    BadMagic(u32),
    BadCpTag { index: u16, tag: u8 },
    BadUtf8 { index: u16 },
    BadCodeAttribute,
    BadInstruction { pc: usize, opcode: u8 },
}

// No Display/std::error::Error impl: this is a crate-internal, typed error
// used only for `match`/`?` within the label parsers (callers discard it via
// `let Ok(_) = ... else continue`). Per the library's zero-English rule there
// is no user-facing text; the variant fields carry the structured detail.

pub type Result<T> = std::result::Result<T, Error>;

// ---------------------------------------------------------------------------
// Constant pool
// ---------------------------------------------------------------------------

#[derive(Debug, Clone)]
pub enum CpInfo {
    /// Index 0 is unused per spec; the slot after Long/Double is also unused.
    Empty,
    Utf8(String),
    Integer(i32),
    Float(f32),
    Long(i64),
    Double(f64),
    Class {
        name_index: u16,
    },
    String {
        string_index: u16,
    },
    Fieldref {
        class_index: u16,
        name_and_type_index: u16,
    },
    Methodref {
        class_index: u16,
        name_and_type_index: u16,
    },
    InterfaceMethodref {
        class_index: u16,
        name_and_type_index: u16,
    },
    NameAndType {
        name_index: u16,
        descriptor_index: u16,
    },
    MethodHandle {
        reference_kind: u8,
        reference_index: u16,
    },
    MethodType {
        descriptor_index: u16,
    },
    Dynamic {
        bootstrap_method_attr_index: u16,
        name_and_type_index: u16,
    },
    InvokeDynamic {
        bootstrap_method_attr_index: u16,
        name_and_type_index: u16,
    },
    Module {
        name_index: u16,
    },
    Package {
        name_index: u16,
    },
}

pub struct ConstantPool {
    entries: Vec<CpInfo>,
}

impl ConstantPool {
    /// Test-only constructor — build a constant pool directly from a
    /// vector of entries. Real callers go through `ClassFile::parse`
    /// which builds this from class-file bytes. Used by parser unit
    /// tests (e.g. `labels::deluxe`) that need to exercise bytecode
    /// walkers against synthetic class fixtures without hand-rolling
    /// valid .class byte buffers.
    ///
    /// Caller is responsible for: prepending a `CpInfo::Empty` at
    /// index 0 (the spec-reserved slot), and inserting a `CpInfo::Empty`
    /// after each Long/Double entry (the 2-slot quirk).
    #[cfg(test)]
    pub(crate) fn from_entries(entries: Vec<CpInfo>) -> Self {
        ConstantPool { entries }
    }

    #[inline]
    pub fn get(&self, index: u16) -> Option<&CpInfo> {
        self.entries.get(index as usize)
    }

    /// Resolve `index` to its UTF-8 string content. Returns None unless
    /// the entry is `CpInfo::Utf8`.
    pub fn utf8(&self, index: u16) -> Option<&str> {
        match self.get(index)? {
            CpInfo::Utf8(s) => Some(s.as_str()),
            _ => None,
        }
    }

    /// Resolve a `CONSTANT_Class` entry to the class's binary name.
    pub fn class_name(&self, index: u16) -> Option<&str> {
        match self.get(index)? {
            CpInfo::Class { name_index } => self.utf8(*name_index),
            _ => None,
        }
    }

    /// Resolve a `CONSTANT_String` entry to its underlying UTF-8.
    pub fn string(&self, index: u16) -> Option<&str> {
        match self.get(index)? {
            CpInfo::String { string_index } => self.utf8(*string_index),
            _ => None,
        }
    }

    pub fn integer(&self, index: u16) -> Option<i32> {
        match self.get(index)? {
            CpInfo::Integer(v) => Some(*v),
            _ => None,
        }
    }

    /// For `ldc` / `ldc_w` operands: resolve a constant-pool index to a
    /// best-effort string. Supports Utf8, String, Integer, Class.
    pub fn load_constant_display(&self, index: u16) -> Option<String> {
        Some(match self.get(index)? {
            CpInfo::Utf8(s) => format!("utf8:{:?}", s),
            CpInfo::String { string_index } => {
                format!("str:{:?}", self.utf8(*string_index).unwrap_or("<?>"))
            }
            CpInfo::Integer(i) => format!("int:{}", i),
            CpInfo::Float(v) => format!("float:{}", v),
            CpInfo::Long(v) => format!("long:{}", v),
            CpInfo::Double(v) => format!("double:{}", v),
            CpInfo::Class { name_index } => {
                format!("class:{:?}", self.utf8(*name_index).unwrap_or("<?>"))
            }
            _ => return None,
        })
    }

    /// Resolve a `CONSTANT_Fieldref` / `Methodref` / `InterfaceMethodref`
    /// to (owning_class_name, member_name, descriptor).
    pub fn member_ref(&self, index: u16) -> Option<MemberRef<'_>> {
        let (class_index, nt_index) = match self.get(index)? {
            CpInfo::Fieldref {
                class_index,
                name_and_type_index,
            }
            | CpInfo::Methodref {
                class_index,
                name_and_type_index,
            }
            | CpInfo::InterfaceMethodref {
                class_index,
                name_and_type_index,
            } => (*class_index, *name_and_type_index),
            _ => return None,
        };
        let class_name = self.class_name(class_index)?;
        let (name, descriptor) = match self.get(nt_index)? {
            CpInfo::NameAndType {
                name_index,
                descriptor_index,
            } => (self.utf8(*name_index)?, self.utf8(*descriptor_index)?),
            _ => return None,
        };
        Some(MemberRef {
            class_name,
            name,
            descriptor,
        })
    }

    #[inline]
    pub fn len(&self) -> usize {
        self.entries.len()
    }

    #[inline]
    pub fn is_empty(&self) -> bool {
        self.entries.is_empty()
    }

    pub fn iter(&self) -> impl Iterator<Item = (u16, &CpInfo)> {
        self.entries.iter().enumerate().map(|(i, e)| (i as u16, e))
    }
}

#[derive(Debug, Clone, Copy)]
pub struct MemberRef<'a> {
    pub class_name: &'a str,
    pub name: &'a str,
    pub descriptor: &'a str,
}

// ---------------------------------------------------------------------------
// ClassFile + Member + Attribute
// ---------------------------------------------------------------------------

pub struct ClassFile {
    pub minor_version: u16,
    pub major_version: u16,
    pub constant_pool: ConstantPool,
    pub access_flags: u16,
    pub this_class: u16,
    pub super_class: u16,
    pub interfaces: Vec<u16>,
    pub fields: Vec<Member>,
    pub methods: Vec<Member>,
    pub attributes: Vec<Attribute>,
}

pub struct Member {
    pub access_flags: u16,
    pub name_index: u16,
    pub descriptor_index: u16,
    pub attributes: Vec<Attribute>,
}

pub struct Attribute {
    pub name_index: u16,
    pub info: Vec<u8>,
}

impl ClassFile {
    pub fn parse(bytes: &[u8]) -> Result<Self> {
        let mut r = Reader::new(bytes);
        let magic = r.u32("magic")?;
        if magic != CLASS_MAGIC {
            return Err(Error::BadMagic(magic));
        }
        let minor_version = r.u16("minor_version")?;
        let major_version = r.u16("major_version")?;
        let constant_pool = read_constant_pool(&mut r)?;
        let access_flags = r.u16("access_flags")?;
        let this_class = r.u16("this_class")?;
        let super_class = r.u16("super_class")?;
        let interfaces_count = r.u16("interfaces_count")? as usize;
        let mut interfaces = Vec::with_capacity(interfaces_count);
        for _ in 0..interfaces_count {
            interfaces.push(r.u16("interface")?);
        }
        let fields = read_members(&mut r)?;
        let methods = read_members(&mut r)?;
        let attributes = read_attributes(&mut r)?;
        Ok(ClassFile {
            minor_version,
            major_version,
            constant_pool,
            access_flags,
            this_class,
            super_class,
            interfaces,
            fields,
            methods,
            attributes,
        })
    }

    pub fn this_class_name(&self) -> Option<&str> {
        self.constant_pool.class_name(self.this_class)
    }

    pub fn super_class_name(&self) -> Option<&str> {
        self.constant_pool.class_name(self.super_class)
    }

    /// Convenience: name of a `Member` belonging to this class.
    pub fn member_name<'a>(&'a self, m: &Member) -> Option<&'a str> {
        self.constant_pool.utf8(m.name_index)
    }

    pub fn member_descriptor<'a>(&'a self, m: &Member) -> Option<&'a str> {
        self.constant_pool.utf8(m.descriptor_index)
    }
}

impl Member {
    /// Locate the `Code` attribute on this member (only methods have one).
    /// Returns the parsed [`CodeAttribute`] for direct bytecode iteration.
    pub fn code<'a>(&'a self, pool: &'a ConstantPool) -> Option<CodeAttribute<'a>> {
        for attr in &self.attributes {
            if pool.utf8(attr.name_index) == Some("Code") {
                return parse_code_attribute(&attr.info).ok();
            }
        }
        None
    }
}

pub struct CodeAttribute<'a> {
    pub max_stack: u16,
    pub max_locals: u16,
    pub code: &'a [u8],
}

impl<'a> CodeAttribute<'a> {
    /// Iterate instructions in this method's bytecode. The iterator
    /// stops at the first malformed instruction, which is the safe
    /// behavior for label extraction (we read straight-line `<clinit>`).
    pub fn instructions(&self) -> Instructions<'a> {
        Instructions {
            code: self.code,
            pos: 0,
        }
    }
}

fn parse_code_attribute(info: &[u8]) -> Result<CodeAttribute<'_>> {
    if info.len() < 8 {
        return Err(Error::BadCodeAttribute);
    }
    let mut r = Reader::new(info);
    let max_stack = r.u16("max_stack")?;
    let max_locals = r.u16("max_locals")?;
    let code_length = r.u32("code_length")? as usize;
    let code = r.slice(code_length, "code bytes")?;
    Ok(CodeAttribute {
        max_stack,
        max_locals,
        code,
    })
}

// ---------------------------------------------------------------------------
// Constant pool reader
// ---------------------------------------------------------------------------

fn read_constant_pool(r: &mut Reader<'_>) -> Result<ConstantPool> {
    let count = r.u16("constant_pool_count")? as usize;
    let mut entries: Vec<CpInfo> = Vec::with_capacity(count);
    entries.push(CpInfo::Empty); // index 0 unused per spec
    let mut i = 1usize;
    while i < count {
        let tag = r.u8("cp tag")?;
        let entry = match tag {
            1 => {
                // CONSTANT_Utf8
                let length = r.u16("utf8 length")? as usize;
                let bytes = r.slice(length, "utf8 bytes")?;
                let s =
                    decode_modified_utf8(bytes).map_err(|_| Error::BadUtf8 { index: i as u16 })?;
                CpInfo::Utf8(s)
            }
            3 => CpInfo::Integer(r.i32("integer")?),
            4 => CpInfo::Float(f32::from_bits(r.u32("float")?)),
            5 => CpInfo::Long(r.i64("long")?),
            6 => CpInfo::Double(f64::from_bits(r.u64("double")?)),
            7 => CpInfo::Class {
                name_index: r.u16("class name_index")?,
            },
            8 => CpInfo::String {
                string_index: r.u16("string_index")?,
            },
            9 => CpInfo::Fieldref {
                class_index: r.u16("fieldref class")?,
                name_and_type_index: r.u16("fieldref nat")?,
            },
            10 => CpInfo::Methodref {
                class_index: r.u16("methodref class")?,
                name_and_type_index: r.u16("methodref nat")?,
            },
            11 => CpInfo::InterfaceMethodref {
                class_index: r.u16("imethodref class")?,
                name_and_type_index: r.u16("imethodref nat")?,
            },
            12 => CpInfo::NameAndType {
                name_index: r.u16("nat name")?,
                descriptor_index: r.u16("nat descriptor")?,
            },
            15 => CpInfo::MethodHandle {
                reference_kind: r.u8("mh kind")?,
                reference_index: r.u16("mh index")?,
            },
            16 => CpInfo::MethodType {
                descriptor_index: r.u16("mt descriptor")?,
            },
            17 => CpInfo::Dynamic {
                bootstrap_method_attr_index: r.u16("dynamic bootstrap")?,
                name_and_type_index: r.u16("dynamic nat")?,
            },
            18 => CpInfo::InvokeDynamic {
                bootstrap_method_attr_index: r.u16("invokedynamic bootstrap")?,
                name_and_type_index: r.u16("invokedynamic nat")?,
            },
            19 => CpInfo::Module {
                name_index: r.u16("module name")?,
            },
            20 => CpInfo::Package {
                name_index: r.u16("package name")?,
            },
            other => {
                return Err(Error::BadCpTag {
                    index: i as u16,
                    tag: other,
                });
            }
        };
        let is_long_or_double = matches!(entry, CpInfo::Long(_) | CpInfo::Double(_));
        entries.push(entry);
        i += 1;
        if is_long_or_double {
            // JVMS §4.4.5: Long and Double occupy TWO slots; the slot
            // immediately following must be skipped.
            entries.push(CpInfo::Empty);
            i += 1;
        }
    }
    Ok(ConstantPool { entries })
}

/// Decode JVM "modified UTF-8" (JVMS §4.4.7). Practically identical to
/// standard UTF-8 for the BMP-printable subset we see in label strings,
/// but with two notable deviations:
/// - U+0000 is encoded as the two-byte sequence 0xC0 0x80, not as 0x00.
/// - Supplementary characters (U+10000..) are encoded as a UTF-16
///   surrogate pair, each surrogate emitted as 3-byte modified UTF-8.
///
/// For label data (mostly ASCII / Latin-1 / CJK in BMP), the simple
/// implementation here covers everything we'll encounter. We tolerate
/// the 0xC0 0x80 → U+0000 case explicitly; supplementary characters
/// would need surrogate-pair stitching, but no label-relevant string
/// uses them.
fn decode_modified_utf8(bytes: &[u8]) -> std::result::Result<String, ()> {
    let mut out = String::with_capacity(bytes.len());
    let mut i = 0;
    while i < bytes.len() {
        let b0 = bytes[i];
        if b0 == 0 {
            // Spec disallows raw 0x00 in modified UTF-8; reject.
            return Err(());
        }
        if b0 < 0x80 {
            out.push(b0 as char);
            i += 1;
        } else if (b0 & 0xE0) == 0xC0 {
            // 2-byte sequence
            if i + 1 >= bytes.len() {
                return Err(());
            }
            let b1 = bytes[i + 1];
            if (b1 & 0xC0) != 0x80 {
                return Err(());
            }
            let cp = (((b0 & 0x1F) as u32) << 6) | ((b1 & 0x3F) as u32);
            // Modified UTF-8 special: 0xC0 0x80 → U+0000.
            if let Some(c) = char::from_u32(cp) {
                out.push(c);
            } else {
                return Err(());
            }
            i += 2;
        } else if (b0 & 0xF0) == 0xE0 {
            // 3-byte sequence (BMP only in modified UTF-8)
            if i + 2 >= bytes.len() {
                return Err(());
            }
            let b1 = bytes[i + 1];
            let b2 = bytes[i + 2];
            if (b1 & 0xC0) != 0x80 || (b2 & 0xC0) != 0x80 {
                return Err(());
            }
            let cp =
                (((b0 & 0x0F) as u32) << 12) | (((b1 & 0x3F) as u32) << 6) | ((b2 & 0x3F) as u32);
            // Lone surrogates are valid in modified UTF-8 but invalid
            // chars in Rust. For label data we'd never see one; treat
            // as replacement char rather than error to stay robust.
            match char::from_u32(cp) {
                Some(c) => out.push(c),
                None => out.push('\u{FFFD}'),
            }
            i += 3;
        } else {
            // 4-byte or higher: not valid in modified UTF-8.
            return Err(());
        }
    }
    Ok(out)
}

// ---------------------------------------------------------------------------
// Fields, methods, attributes
// ---------------------------------------------------------------------------

fn read_members(r: &mut Reader<'_>) -> Result<Vec<Member>> {
    let count = r.u16("members_count")? as usize;
    let mut out = Vec::with_capacity(count);
    for _ in 0..count {
        let access_flags = r.u16("member access")?;
        let name_index = r.u16("member name")?;
        let descriptor_index = r.u16("member descriptor")?;
        let attributes = read_attributes(r)?;
        out.push(Member {
            access_flags,
            name_index,
            descriptor_index,
            attributes,
        });
    }
    Ok(out)
}

fn read_attributes(r: &mut Reader<'_>) -> Result<Vec<Attribute>> {
    let count = r.u16("attributes_count")? as usize;
    let mut out = Vec::with_capacity(count);
    for _ in 0..count {
        let name_index = r.u16("attribute name")?;
        let length = r.u32("attribute length")? as usize;
        let info = r.slice(length, "attribute info")?.to_vec();
        out.push(Attribute { name_index, info });
    }
    Ok(out)
}

// ---------------------------------------------------------------------------
// Bytecode iterator
// ---------------------------------------------------------------------------

pub struct Instructions<'a> {
    code: &'a [u8],
    pos: usize,
}

#[derive(Debug, Clone, Copy)]
pub struct Instruction<'a> {
    pub pc: usize,
    pub opcode: u8,
    pub operands: &'a [u8],
}

impl Instruction<'_> {
    /// Mnemonic for this opcode (e.g. "ldc", "invokespecial").
    pub fn name(&self) -> &'static str {
        opcode_name(self.opcode)
    }

    /// Operand as a single u8 (e.g. ldc cp index, bipush value).
    pub fn operand_u8(&self) -> Option<u8> {
        self.operands.first().copied()
    }

    /// Operand as a big-endian u16 (e.g. ldc_w/new/getstatic cp index,
    /// branch offset for if*/goto).
    pub fn operand_u16(&self) -> Option<u16> {
        if self.operands.len() >= 2 {
            Some(u16::from_be_bytes([self.operands[0], self.operands[1]]))
        } else {
            None
        }
    }

    /// For instructions whose operand is a constant-pool index — ldc,
    /// ldc_w, ldc2_w, new, getstatic, putstatic, getfield, putfield,
    /// invokevirtual, invokespecial, invokestatic, invokeinterface,
    /// invokedynamic, checkcast, instanceof, anewarray, multianewarray,
    /// ldc with cp index in operands[0] — return the index. Returns
    /// None for opcodes whose operand is not a CP index.
    pub fn cp_index(&self) -> Option<u16> {
        match self.opcode {
            // ldc: 1-byte cp index, zero-extended
            LDC => self.operand_u8().map(u16::from),
            LDC_W | LDC2_W | NEW | GETSTATIC | PUTSTATIC | GETFIELD | PUTFIELD | INVOKEVIRTUAL
            | INVOKESPECIAL | INVOKESTATIC | INVOKEINTERFACE | INVOKEDYNAMIC | CHECKCAST
            | INSTANCEOF | ANEWARRAY | MULTIANEWARRAY => self.operand_u16(),
            _ => None,
        }
    }
}

impl<'a> Iterator for Instructions<'a> {
    type Item = Instruction<'a>;

    fn next(&mut self) -> Option<Self::Item> {
        if self.pos >= self.code.len() {
            return None;
        }
        let pc = self.pos;
        let opcode = self.code[pc];
        let size = instruction_size(self.code, pc)?;
        if pc + size > self.code.len() {
            return None;
        }
        let operands = &self.code[pc + 1..pc + size];
        self.pos = pc + size;
        Some(Instruction {
            pc,
            opcode,
            operands,
        })
    }
}

/// Total size of the instruction at `pc` (opcode + operands). Returns
/// None on malformed input. Handles all JVMS §6 opcodes including
/// the variable-length `tableswitch`, `lookupswitch`, and `wide`.
fn instruction_size(code: &[u8], pc: usize) -> Option<usize> {
    let op = *code.get(pc)?;
    // Fixed-size opcodes use a precomputed table; the few variable-size
    // ones get special cases below.
    if let Some(sz) = FIXED_SIZE[op as usize] {
        return Some(sz as usize);
    }
    match op {
        TABLESWITCH => {
            // 1 opcode byte + 0..3 padding bytes (align to 4-byte boundary
            // from start of method) + 4 default + 4 low + 4 high + 4*(high-low+1)
            let padded_start = (pc + 1 + 3) & !3;
            if padded_start + 12 > code.len() {
                return None;
            }
            let default_offset_pos = padded_start;
            let low = i32::from_be_bytes(
                code[default_offset_pos + 4..default_offset_pos + 8]
                    .try_into()
                    .ok()?,
            );
            let high = i32::from_be_bytes(
                code[default_offset_pos + 8..default_offset_pos + 12]
                    .try_into()
                    .ok()?,
            );
            if high < low {
                return None;
            }
            // `high - low + 1` can overflow i32 for adversarial bytecode
            // (e.g. low=i32::MIN/high=0, or low=0/high=i32::MAX), so widen
            // to i64 before adding. The product and final sum are saturating
            // so they cannot overflow usize on a 32-bit target either.
            let entries = (high as i64 - low as i64 + 1) as u64;
            let table_bytes = entries.saturating_mul(4);
            let base = (padded_start - pc + 12) as u64;
            usize::try_from(base.saturating_add(table_bytes)).ok()
        }
        LOOKUPSWITCH => {
            let padded_start = (pc + 1 + 3) & !3;
            if padded_start + 8 > code.len() {
                return None;
            }
            let npairs =
                i32::from_be_bytes(code[padded_start + 4..padded_start + 8].try_into().ok()?);
            if npairs < 0 {
                return None;
            }
            // Saturating product/sum so an attacker-supplied npairs cannot
            // overflow usize on a 32-bit target.
            let pair_bytes = (npairs as u64).saturating_mul(8);
            let base = (padded_start - pc + 8) as u64;
            usize::try_from(base.saturating_add(pair_bytes)).ok()
        }
        WIDE => {
            // `wide` prefixes one of: iload/lload/fload/dload/aload/
            // istore/lstore/fstore/dstore/astore/ret  → 4 total bytes
            // or `iinc`                                → 6 total bytes
            let next = *code.get(pc + 1)?;
            match next {
                IINC => Some(6),
                ILOAD | LLOAD | FLOAD | DLOAD | ALOAD | ISTORE | LSTORE | FSTORE | DSTORE
                | ASTORE | RET => Some(4),
                _ => None,
            }
        }
        _ => None, // unknown opcode → halt iteration
    }
}

// ---------------------------------------------------------------------------
// Opcode table
// ---------------------------------------------------------------------------

// Named opcode constants for the ones we walk in the parser.
#[allow(dead_code)]
pub const NOP: u8 = 0x00;
pub const ACONST_NULL: u8 = 0x01;
pub const ICONST_M1: u8 = 0x02;
pub const ICONST_0: u8 = 0x03;
pub const ICONST_1: u8 = 0x04;
pub const ICONST_2: u8 = 0x05;
pub const ICONST_3: u8 = 0x06;
pub const ICONST_4: u8 = 0x07;
pub const ICONST_5: u8 = 0x08;
pub const BIPUSH: u8 = 0x10;
pub const SIPUSH: u8 = 0x11;
pub const LDC: u8 = 0x12;
pub const LDC_W: u8 = 0x13;
pub const LDC2_W: u8 = 0x14;
pub const ILOAD: u8 = 0x15;
pub const LLOAD: u8 = 0x16;
pub const FLOAD: u8 = 0x17;
pub const DLOAD: u8 = 0x18;
pub const ALOAD: u8 = 0x19;
pub const ISTORE: u8 = 0x36;
pub const LSTORE: u8 = 0x37;
pub const FSTORE: u8 = 0x38;
pub const DSTORE: u8 = 0x39;
pub const ASTORE: u8 = 0x3A;
pub const AASTORE: u8 = 0x53;
pub const IINC: u8 = 0x84;
pub const RET: u8 = 0xA9;
pub const TABLESWITCH: u8 = 0xAA;
pub const LOOKUPSWITCH: u8 = 0xAB;
pub const GETSTATIC: u8 = 0xB2;
pub const PUTSTATIC: u8 = 0xB3;
pub const GETFIELD: u8 = 0xB4;
pub const PUTFIELD: u8 = 0xB5;
pub const INVOKEVIRTUAL: u8 = 0xB6;
pub const INVOKESPECIAL: u8 = 0xB7;
pub const INVOKESTATIC: u8 = 0xB8;
pub const INVOKEINTERFACE: u8 = 0xB9;
pub const INVOKEDYNAMIC: u8 = 0xBA;
pub const NEW: u8 = 0xBB;
pub const NEWARRAY: u8 = 0xBC;
pub const ANEWARRAY: u8 = 0xBD;
pub const CHECKCAST: u8 = 0xC0;
pub const INSTANCEOF: u8 = 0xC1;
pub const WIDE: u8 = 0xC4;
pub const MULTIANEWARRAY: u8 = 0xC5;

/// Fixed instruction sizes (opcode + operand bytes). `None` means the
/// opcode is either variable-length (see `instruction_size`) or
/// unallocated / reserved.
const FIXED_SIZE: [Option<u8>; 256] = {
    let mut t = [None; 256];
    // 0x00..0x0F: constant stack ops
    let one = Some(1u8);
    let mut i = 0x00u16;
    while i <= 0x0F {
        t[i as usize] = one;
        i += 1;
    }
    t[0x10] = Some(2); // bipush
    t[0x11] = Some(3); // sipush
    t[0x12] = Some(2); // ldc
    t[0x13] = Some(3); // ldc_w
    t[0x14] = Some(3); // ldc2_w
    // 0x15..0x19: iload/lload/fload/dload/aload  (each 2 bytes)
    let two = Some(2u8);
    let mut i = 0x15u16;
    while i <= 0x19 {
        t[i as usize] = two;
        i += 1;
    }
    // 0x1A..0x35: iload_0..aload_3 + iaload..saload (1 byte each)
    let mut i = 0x1Au16;
    while i <= 0x35 {
        t[i as usize] = one;
        i += 1;
    }
    // 0x36..0x3A: istore/lstore/fstore/dstore/astore (2 bytes)
    let mut i = 0x36u16;
    while i <= 0x3A {
        t[i as usize] = two;
        i += 1;
    }
    // 0x3B..0x83: istore_0..astore_3 + array stores + stack + math (1 byte)
    let mut i = 0x3Bu16;
    while i <= 0x83 {
        t[i as usize] = one;
        i += 1;
    }
    t[0x84] = Some(3); // iinc
    // 0x85..0x98: type conversions + comparisons (1 byte)
    let mut i = 0x85u16;
    while i <= 0x98 {
        t[i as usize] = one;
        i += 1;
    }
    // 0x99..0xA6: ifeq..if_acmpne (3 bytes — 2-byte branch offset)
    let three = Some(3u8);
    let mut i = 0x99u16;
    while i <= 0xA6 {
        t[i as usize] = three;
        i += 1;
    }
    t[0xA7] = Some(3); // goto
    t[0xA8] = Some(3); // jsr
    t[0xA9] = Some(2); // ret
    // 0xAA, 0xAB: tableswitch / lookupswitch — variable, handled separately
    // 0xAC..0xB1: returns (1 byte)
    let mut i = 0xACu16;
    while i <= 0xB1 {
        t[i as usize] = one;
        i += 1;
    }
    // 0xB2..0xB8: getstatic/putstatic/getfield/putfield + invokes (3 bytes)
    let mut i = 0xB2u16;
    while i <= 0xB8 {
        t[i as usize] = three;
        i += 1;
    }
    t[0xB9] = Some(5); // invokeinterface: 2-byte cp + 1-byte count + 1-byte 0
    t[0xBA] = Some(5); // invokedynamic: 2-byte cp + 2 bytes 0
    t[0xBB] = Some(3); // new
    t[0xBC] = Some(2); // newarray
    t[0xBD] = Some(3); // anewarray
    t[0xBE] = Some(1); // arraylength
    t[0xBF] = Some(1); // athrow
    t[0xC0] = Some(3); // checkcast
    t[0xC1] = Some(3); // instanceof
    t[0xC2] = Some(1); // monitorenter
    t[0xC3] = Some(1); // monitorexit
    // 0xC4: wide — variable, handled separately
    t[0xC5] = Some(4); // multianewarray: 2-byte cp + 1 byte dimensions
    t[0xC6] = Some(3); // ifnull
    t[0xC7] = Some(3); // ifnonnull
    t[0xC8] = Some(5); // goto_w
    t[0xC9] = Some(5); // jsr_w
    // 0xCA: breakpoint (reserved, 1 byte)
    t[0xCA] = Some(1);
    // 0xFE / 0xFF: impdep1 / impdep2 (reserved, 1 byte)
    t[0xFE] = Some(1);
    t[0xFF] = Some(1);
    t
};

fn opcode_name(op: u8) -> &'static str {
    match op {
        0x00 => "nop",
        0x01 => "aconst_null",
        0x02 => "iconst_m1",
        0x03 => "iconst_0",
        0x04 => "iconst_1",
        0x05 => "iconst_2",
        0x06 => "iconst_3",
        0x07 => "iconst_4",
        0x08 => "iconst_5",
        0x09 => "lconst_0",
        0x0A => "lconst_1",
        0x0B => "fconst_0",
        0x0C => "fconst_1",
        0x0D => "fconst_2",
        0x0E => "dconst_0",
        0x0F => "dconst_1",
        0x10 => "bipush",
        0x11 => "sipush",
        0x12 => "ldc",
        0x13 => "ldc_w",
        0x14 => "ldc2_w",
        0x15 => "iload",
        0x16 => "lload",
        0x17 => "fload",
        0x18 => "dload",
        0x19 => "aload",
        0x36 => "istore",
        0x37 => "lstore",
        0x38 => "fstore",
        0x39 => "dstore",
        0x3A => "astore",
        0x53 => "aastore",
        0x57 => "pop",
        0x58 => "pop2",
        0x59 => "dup",
        0x84 => "iinc",
        0xA7 => "goto",
        0xAA => "tableswitch",
        0xAB => "lookupswitch",
        0xAC => "ireturn",
        0xAD => "lreturn",
        0xAE => "freturn",
        0xAF => "dreturn",
        0xB0 => "areturn",
        0xB1 => "return",
        0xB2 => "getstatic",
        0xB3 => "putstatic",
        0xB4 => "getfield",
        0xB5 => "putfield",
        0xB6 => "invokevirtual",
        0xB7 => "invokespecial",
        0xB8 => "invokestatic",
        0xB9 => "invokeinterface",
        0xBA => "invokedynamic",
        0xBB => "new",
        0xBC => "newarray",
        0xBD => "anewarray",
        0xBE => "arraylength",
        0xBF => "athrow",
        0xC0 => "checkcast",
        0xC1 => "instanceof",
        0xC4 => "wide",
        0xC5 => "multianewarray",
        _ => "?",
    }
}

// ---------------------------------------------------------------------------
// Internal cursor reader
// ---------------------------------------------------------------------------

struct Reader<'a> {
    data: &'a [u8],
    pos: usize,
}

impl<'a> Reader<'a> {
    fn new(data: &'a [u8]) -> Self {
        Reader { data, pos: 0 }
    }

    fn u8(&mut self, needed: &'static str) -> Result<u8> {
        let b = *self
            .data
            .get(self.pos)
            .ok_or(Error::UnexpectedEof { needed })?;
        self.pos += 1;
        Ok(b)
    }

    fn u16(&mut self, needed: &'static str) -> Result<u16> {
        if self.pos + 2 > self.data.len() {
            return Err(Error::UnexpectedEof { needed });
        }
        let v = u16::from_be_bytes([self.data[self.pos], self.data[self.pos + 1]]);
        self.pos += 2;
        Ok(v)
    }

    fn u32(&mut self, needed: &'static str) -> Result<u32> {
        if self.pos + 4 > self.data.len() {
            return Err(Error::UnexpectedEof { needed });
        }
        let v = u32::from_be_bytes([
            self.data[self.pos],
            self.data[self.pos + 1],
            self.data[self.pos + 2],
            self.data[self.pos + 3],
        ]);
        self.pos += 4;
        Ok(v)
    }

    fn i32(&mut self, needed: &'static str) -> Result<i32> {
        self.u32(needed).map(|v| v as i32)
    }

    fn u64(&mut self, needed: &'static str) -> Result<u64> {
        if self.pos + 8 > self.data.len() {
            return Err(Error::UnexpectedEof { needed });
        }
        let v = u64::from_be_bytes([
            self.data[self.pos],
            self.data[self.pos + 1],
            self.data[self.pos + 2],
            self.data[self.pos + 3],
            self.data[self.pos + 4],
            self.data[self.pos + 5],
            self.data[self.pos + 6],
            self.data[self.pos + 7],
        ]);
        self.pos += 8;
        Ok(v)
    }

    fn i64(&mut self, needed: &'static str) -> Result<i64> {
        self.u64(needed).map(|v| v as i64)
    }

    fn slice(&mut self, n: usize, needed: &'static str) -> Result<&'a [u8]> {
        if self.pos + n > self.data.len() {
            return Err(Error::UnexpectedEof { needed });
        }
        let s = &self.data[self.pos..self.pos + n];
        self.pos += n;
        Ok(s)
    }
}

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

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

    #[test]
    fn rejects_non_class_bytes() {
        match ClassFile::parse(b"\x00\x01\x02\x03DEAD") {
            Err(Error::BadMagic(_)) | Err(Error::UnexpectedEof { .. }) => {}
            Err(other) => panic!("expected magic/eof error, got {:?}", other),
            Ok(_) => panic!("expected error, got Ok"),
        }
    }

    #[test]
    fn modified_utf8_basic_ascii() {
        let s = decode_modified_utf8(b"English").unwrap();
        assert_eq!(s, "English");
    }

    #[test]
    fn modified_utf8_null_encoding() {
        // 0xC0 0x80 in modified UTF-8 encodes U+0000.
        let s = decode_modified_utf8(&[0xC0, 0x80]).unwrap();
        assert_eq!(s, "\u{0000}");
    }

    #[test]
    fn modified_utf8_rejects_raw_zero() {
        assert!(decode_modified_utf8(&[0x00]).is_err());
    }

    #[test]
    fn modified_utf8_two_byte() {
        // U+00E9 'é' in the standard 2-byte modified-UTF-8 encoding
        // (0xC3 0xA9), exercising the decoder's 2-byte branch.
        let s = decode_modified_utf8(&[0xC3, 0xA9]).unwrap();
        assert_eq!(s, "é");
    }

    #[test]
    fn instruction_size_fixed_opcodes() {
        // bipush is 2 bytes, sipush is 3, getstatic is 3.
        assert_eq!(instruction_size(&[BIPUSH, 0x05], 0), Some(2));
        assert_eq!(instruction_size(&[SIPUSH, 0x00, 0x05], 0), Some(3));
        assert_eq!(instruction_size(&[GETSTATIC, 0x00, 0x01], 0), Some(3));
        assert_eq!(instruction_size(&[INVOKEINTERFACE, 0, 1, 2, 0], 0), Some(5));
        assert_eq!(instruction_size(&[NEW, 0, 1], 0), Some(3));
    }

    #[test]
    fn instruction_size_tableswitch_padding() {
        // tableswitch at pc=0: pad to 4-byte boundary from pc+1, so 3 pad bytes.
        // default(4) + low(4) + high(4) + 1 entry (low=0 high=0, so high-low+1=1)
        // total = 1 (opcode) + 3 (pad) + 12 + 4 = 20
        let mut code = vec![TABLESWITCH];
        code.extend_from_slice(&[0, 0, 0]); // padding
        code.extend_from_slice(&[0, 0, 0, 0]); // default offset
        code.extend_from_slice(&[0, 0, 0, 0]); // low = 0
        code.extend_from_slice(&[0, 0, 0, 0]); // high = 0
        code.extend_from_slice(&[0, 0, 0, 0]); // 1 jump entry
        assert_eq!(instruction_size(&code, 0), Some(20));
    }

    #[test]
    fn instruction_size_lookupswitch() {
        // pc=0: pad 3, default(4), npairs(4)=2, 2 pairs (8 bytes each) = 16
        // total = 1 + 3 + 8 + 16 = 28
        let mut code = vec![LOOKUPSWITCH];
        code.extend_from_slice(&[0, 0, 0]); // padding
        code.extend_from_slice(&[0, 0, 0, 0]); // default
        code.extend_from_slice(&[0, 0, 0, 2]); // npairs = 2
        code.extend_from_slice(&[0; 16]); // 2 pairs
        assert_eq!(instruction_size(&code, 0), Some(28));
    }

    #[test]
    fn instruction_size_tableswitch_overflow_does_not_panic() {
        // Adversarial low/high spanning the full i32 range. `high - low + 1`
        // overflows i32; the widened i64 count then saturates the byte
        // products. Must return a value (possibly None on a 32-bit usize)
        // without panicking.
        for (low, high) in [
            (i32::MIN, 0i32),
            (0i32, i32::MAX),
            (i32::MIN, i32::MAX),
            (-1i32, i32::MAX),
        ] {
            let mut code = vec![TABLESWITCH];
            code.extend_from_slice(&[0, 0, 0]); // padding
            code.extend_from_slice(&[0, 0, 0, 0]); // default offset
            code.extend_from_slice(&low.to_be_bytes());
            code.extend_from_slice(&high.to_be_bytes());
            // No need to supply the (enormous) jump table; size computation
            // must not read it.
            let _ = instruction_size(&code, 0);
        }
    }

    #[test]
    fn instruction_size_lookupswitch_overflow_does_not_panic() {
        // Maximal npairs; `npairs * 8` must saturate rather than overflow.
        let mut code = vec![LOOKUPSWITCH];
        code.extend_from_slice(&[0, 0, 0]); // padding
        code.extend_from_slice(&[0, 0, 0, 0]); // default
        code.extend_from_slice(&i32::MAX.to_be_bytes()); // npairs = i32::MAX
        let _ = instruction_size(&code, 0);
    }

    #[test]
    fn instruction_size_wide() {
        // wide iload: 4 bytes. wide iinc: 6 bytes.
        assert_eq!(instruction_size(&[WIDE, ILOAD, 0, 1], 0), Some(4));
        assert_eq!(instruction_size(&[WIDE, IINC, 0, 1, 0, 5], 0), Some(6));
    }

    #[test]
    fn instructions_iter_walks_simple_code() {
        // ldc #1; aastore; return
        let code = vec![LDC, 0x01, AASTORE, 0xB1];
        let attr = CodeAttribute {
            max_stack: 1,
            max_locals: 0,
            code: &code,
        };
        let names: Vec<_> = attr.instructions().map(|i| i.name()).collect();
        assert_eq!(names, vec!["ldc", "aastore", "return"]);
    }

    #[test]
    fn instructions_iter_stops_on_truncated() {
        // ldc claims 2 bytes but only 1 byte present after — iterator stops.
        let code = vec![LDC];
        let attr = CodeAttribute {
            max_stack: 1,
            max_locals: 0,
            code: &code,
        };
        let count = attr.instructions().count();
        assert_eq!(count, 0);
    }

    #[test]
    fn cp_index_extraction() {
        let i = Instruction {
            pc: 0,
            opcode: LDC,
            operands: &[0x42],
        };
        assert_eq!(i.cp_index(), Some(0x42));

        let i = Instruction {
            pc: 0,
            opcode: LDC_W,
            operands: &[0x01, 0x23],
        };
        assert_eq!(i.cp_index(), Some(0x0123));

        let i = Instruction {
            pc: 0,
            opcode: NEW,
            operands: &[0x00, 0x10],
        };
        assert_eq!(i.cp_index(), Some(0x0010));

        let i = Instruction {
            pc: 0,
            opcode: AASTORE,
            operands: &[],
        };
        assert_eq!(i.cp_index(), None);
    }

    // ── Robustness smoke tests ──────────────────────────────────────────────
    //
    // ClassFile::parse must NEVER panic on adversarial input, only
    // return Err. These tests feed a battery of malformed byte
    // sequences and assert Err results — they're the lightweight
    // alternative to a full cargo-fuzz target (which would need
    // nightly + separate crate). If we adopt cargo-fuzz later, these
    // tests stay as deterministic regression cases.

    /// Tiny pseudo-random byte generator — deterministic + reproducible
    /// without needing a `rand` dep. xorshift64*; good enough for
    /// generating adversarial byte payloads.
    fn xorshift(state: &mut u64) -> u64 {
        let mut x = *state;
        x ^= x << 13;
        x ^= x >> 7;
        x ^= x << 17;
        *state = x;
        x
    }

    #[test]
    fn parse_rejects_empty_input() {
        assert!(ClassFile::parse(&[]).is_err());
    }

    #[test]
    fn parse_rejects_short_magic() {
        for n in 0..4 {
            let buf = vec![0u8; n];
            assert!(ClassFile::parse(&buf).is_err());
        }
    }

    #[test]
    fn parse_rejects_wrong_magic() {
        let buf = vec![0xDE, 0xAD, 0xBE, 0xEF, 0, 0, 0, 0];
        match ClassFile::parse(&buf) {
            Err(Error::BadMagic(0xDEADBEEF)) => {}
            Err(other) => panic!("expected BadMagic, got {:?}", other),
            Ok(_) => panic!("expected BadMagic error, got Ok"),
        }
    }

    #[test]
    fn parse_rejects_truncated_after_magic() {
        // CAFEBABE + 1 byte = not enough for minor_version (u16).
        let buf = vec![0xCA, 0xFE, 0xBA, 0xBE, 0x00];
        assert!(ClassFile::parse(&buf).is_err());
    }

    #[test]
    fn parse_rejects_bad_cp_tag() {
        // CAFEBABE + minor/major(0,0,0,52) + cp_count=2 + tag=99 (unknown).
        let buf = vec![
            0xCA, 0xFE, 0xBA, 0xBE, // magic
            0x00, 0x00, // minor
            0x00, 0x34, // major
            0x00, 0x02, // cp_count = 2 (one entry)
            99,   // unknown tag
        ];
        match ClassFile::parse(&buf) {
            Err(_) => {} // BadCpTag, BadMagic, or any other malformed-input err
            Ok(_) => panic!("expected error on unknown CP tag"),
        }
    }

    #[test]
    fn parse_rejects_truncated_utf8() {
        // CAFEBABE + minor/major + cp_count=2 + tag=1 (Utf8) + length=10 + 3 bytes (< 10).
        let buf = vec![
            0xCA, 0xFE, 0xBA, 0xBE, 0x00, 0x00, 0x00, 0x34, 0x00, 0x02, // cp_count=2
            1,    // Utf8 tag
            0x00, 10, // length=10
            b'h', b'i', b'!', // only 3 bytes (truncated)
        ];
        assert!(ClassFile::parse(&buf).is_err());
    }

    #[test]
    fn parse_does_not_panic_on_random_bytes() {
        // 200 deterministic-pseudo-random byte buffers of varying
        // lengths. The contract: never panic, only return Err (or in
        // the vanishingly unlikely case of a coincidentally-valid
        // buffer, Ok — we don't assert one or the other).
        let mut state: u64 = 0xDEADBEEF_DEADBEEF;
        for _ in 0..200 {
            let len = (xorshift(&mut state) % 256) as usize;
            let mut buf = Vec::with_capacity(len);
            for _ in 0..len {
                buf.push((xorshift(&mut state) & 0xFF) as u8);
            }
            // No panic. Result doesn't matter — Err is expected for
            // 99%+ of inputs.
            let _ = ClassFile::parse(&buf);
        }
    }

    #[test]
    fn parse_does_not_panic_on_valid_magic_random_tail() {
        // 100 buffers that start with the magic + plausible
        // minor/major but have garbage afterwards. These are the
        // most adversarial — they pass the magic check and then
        // exercise every other parser path.
        let mut state: u64 = 0xCAFEBABE_DEADBEEF;
        for _ in 0..100 {
            let mut buf = vec![0xCA, 0xFE, 0xBA, 0xBE, 0x00, 0x00, 0x00, 0x34];
            let tail_len = (xorshift(&mut state) % 512) as usize;
            for _ in 0..tail_len {
                buf.push((xorshift(&mut state) & 0xFF) as u8);
            }
            let _ = ClassFile::parse(&buf);
        }
    }

    #[test]
    fn instructions_never_panic_on_random_code() {
        // Bytecode iterator must not panic on any byte sequence.
        let mut state: u64 = 0x12345678_87654321;
        for _ in 0..200 {
            let len = (xorshift(&mut state) % 256) as usize;
            let mut code = Vec::with_capacity(len);
            for _ in 0..len {
                code.push((xorshift(&mut state) & 0xFF) as u8);
            }
            let attr = CodeAttribute {
                max_stack: 0,
                max_locals: 0,
                code: &code,
            };
            // Bounded — iterator stops on truncated/unknown opcodes.
            let _: Vec<_> = attr.instructions().collect();
        }
    }

    #[test]
    fn instruction_size_never_panics() {
        // Cover every opcode byte 0..=255 with various code-buffer
        // shapes. instruction_size returns Option but must not panic.
        for op in 0u8..=255 {
            for tail_len in [0usize, 1, 2, 3, 7, 16, 32] {
                let mut buf = vec![op];
                for i in 0..tail_len {
                    buf.push((i as u8).wrapping_mul(31));
                }
                let _ = instruction_size(&buf, 0);
            }
        }
    }

    #[test]
    fn modified_utf8_never_panics_on_random_bytes() {
        let mut state: u64 = 0xABCDEF12_34567890;
        for _ in 0..500 {
            let len = (xorshift(&mut state) % 64) as usize;
            let mut buf = Vec::with_capacity(len);
            for _ in 0..len {
                buf.push((xorshift(&mut state) & 0xFF) as u8);
            }
            // Either Ok or Err; never a panic.
            let _ = decode_modified_utf8(&buf);
        }
    }
}
