use std::io::{BufReader, Read};

#[derive(Debug, Copy, Clone)]
pub enum Instruction {
    AALoad,
    AAStore,
    AConstNull,
    ALoad(u8),
    ALoad0,
    ALoad1,
    ALoad2,
    ALoad3,
    ANewArray(u16),
    AReturn,
    ArrayLength,
    AStore(u8),
    AStore0,
    AStore1,
    AStore2,
    AStore3,
    AThrow,
    BALoad,
    BAStore,
    BiPush(u8),
    Breakpoint,
    CALoad,
    CAStore,
    CheckCast(u16),
    D2F,
    D2I,
    D2L,
    DAdd,
    DALoad,
    DAStore,
    DCmpG,
    DCmpL,
    DConst0,
    DConst1,
    DDiv,
    DLoad(u8),
    DLoad0,
    DLoad1,
    DLoad2,
    DLoad3,
    DMul,
    DNeg,
    DRem,
    DReturn,
    DStore(u8),
    DStore0,
    DStore1,
    DStore2,
    DStore3,
    DSub,
    Dup,
    DupX1,
    DupX2,
    Dup2,
    Dup2X1,
    Dup2X2,
    F2D,
    F2I,
    F2L,
    FAdd,
    FALoad,
    FAStore,
    FCmpG,
    FCmpL,
    FConst0,
    FConst1,
    FConst2,
    FDiv,
    FLoad(u8),
    FLoad0,
    FLoad1,
    FLoad2,
    FLoad3,
    FMul,
    FNeg,
    FRem,
    FReturn,
    FStore(u8),
    FStore0,
    FStore1,
    FStore2,
    FStore3,
    FSub,
    GetField(u16),
    GetStatic(u16),
    GoTo(u16),
    GoToW(u32),
    I2C,
    I2B,
    I2D,
    I2F,
    I2L,
    I2S,
    IAdd,
    IALoad,
    IAnd,
    IAStore,
    IConstM1,
    IConst0,
    IConst1,
    IConst2,
    IConst3,
    IConst4,
    IConst5,
    IDiv,
    IfACmpEq(u16),
    IfACmpNe(u16),
    IfICmpEq(u16),
    IfICmpGe(u16),
    IfICmpGt(u16),
    IfICmpLe(u16),
    IfICmpLt(u16),
    IfICmpNe(u16),
    IfEq(u16),
    IfGe(u16),
    IfGt(u16),
    IfLe(u16),
    IfLt(u16),
    IfNe(u16),
    IfNonNull(u16),
    IfNull(u16),
    IInc(u8, u8),
    ILoad(u8),
    ILoad0,
    ILoad1,
    ILoad2,
    ILoad3,
    ImpDep1,
    ImpDep2,
    IMul,
    INeg,
    InstanceOf(u16),
    InvokeDynamic(u16, u8, u8),
    InvokeInterface(u16, u8, u8),
    InvokeSpecial(u16),
    InvokeStatic(u16),
    InvokeVirtual(u16),
    IOr,
    IRem,
    IReturn,
    IShl,
    IShr,
    IStore(u8),
    IStore0,
    IStore1,
    IStore2,
    IStore3,
    ISub,
    IUShr,
    IXor,
    Jsr(u16),
    JsrW(u32),
    L2D,
    L2F,
    L2I,
    LAdd,
    LALoad,
    LAnd,
    LAStore,
    LCmp,
    LConst0,
    LConst1,
    Ldc(u8),
    LdcW(u16),
    Ldc2W(u16),
    LDiv,
    LLoad(u8),
    LLoad0,
    LLoad1,
    LLoad2,
    LLoad3,
    LMul,
    LNeg,
    // LookUpSwitch,
    LOr,
    LRem,
    LReturn,
    LShl,
    LShr,
    LStore(u8),
    LStore0,
    LStore1,
    LStore2,
    LStore3,
    LSub,
    LUShr,
    LXor,
    MonitorEnter,
    MonitorExit,
    MultiANewArray(u16, u8),
    New(u16),
    NewArray(u8),
    Nop,
    Pop,
    Pop2,
    PutField(u16),
    PutStatic(u16),
    Ret(u8),
    Return,
    SALoad,
    SAStore,
    SiPush(u16),
    Swap,
    // TableSwitch,
    // Wide,
}

struct InstructionStream<'a> {
    pub inner: BufReader<&'a [u8]>,
    pub index: usize,
}

pub fn parse(code_stream: BufReader<&[u8]>, code_length: usize) -> Vec<Instruction> {
    let mut instructions = Vec::new();

    let stream = &mut InstructionStream {
        inner: code_stream,
        index: 0,
    };

    while stream.index < code_length {
        instructions.push(match read(stream) {
            0x00 => Instruction::Nop,
            0x01 => Instruction::AConstNull,
            0x02 => Instruction::IConstM1,
            0x03 => Instruction::IConst0,
            0x04 => Instruction::IConst1,
            0x05 => Instruction::IConst2,
            0x06 => Instruction::IConst3,
            0x07 => Instruction::IConst4,
            0x08 => Instruction::IConst5,
            0x09 => Instruction::LConst0,
            0x0a => Instruction::LConst1,
            0x0b => Instruction::FConst0,
            0x0c => Instruction::FConst1,
            0x0d => Instruction::FConst2,
            0x0e => Instruction::DConst0,
            0x0f => Instruction::DConst1,
            0x10 => Instruction::BiPush(read(stream)),
            0x11 => Instruction::SiPush(u16::from_be_bytes([read(stream), read(stream)])),
            0x12 => Instruction::Ldc(read(stream)),
            0x13 => Instruction::LdcW(u16::from_be_bytes([read(stream), read(stream)])),
            0x14 => Instruction::Ldc2W(u16::from_be_bytes([read(stream), read(stream)])),
            0x15 => Instruction::ILoad(read(stream)),
            0x16 => Instruction::LLoad(read(stream)),
            0x17 => Instruction::FLoad(read(stream)),
            0x18 => Instruction::DLoad(read(stream)),
            0x19 => Instruction::ALoad(read(stream)),
            0x1a => Instruction::ILoad0,
            0x1b => Instruction::ILoad1,
            0x1c => Instruction::ILoad2,
            0x1d => Instruction::ILoad3,
            0x1e => Instruction::LLoad0,
            0x1f => Instruction::LLoad1,
            0x20 => Instruction::LLoad2,
            0x21 => Instruction::LLoad3,
            0x22 => Instruction::FLoad0,
            0x23 => Instruction::FLoad1,
            0x24 => Instruction::FLoad2,
            0x25 => Instruction::FLoad3,
            0x26 => Instruction::DLoad0,
            0x27 => Instruction::DLoad1,
            0x28 => Instruction::DLoad2,
            0x29 => Instruction::DLoad3,
            0x2a => Instruction::ALoad0,
            0x2b => Instruction::ALoad1,
            0x2c => Instruction::ALoad2,
            0x2d => Instruction::ALoad3,
            0x2e => Instruction::IALoad,
            0x2f => Instruction::LALoad,
            0x30 => Instruction::FALoad,
            0x31 => Instruction::DALoad,
            0x32 => Instruction::AALoad,
            0x33 => Instruction::BALoad,
            0x34 => Instruction::CALoad,
            0x35 => Instruction::SALoad,
            0x36 => Instruction::IStore(read(stream)),
            0x37 => Instruction::LStore(read(stream)),
            0x38 => Instruction::FStore(read(stream)),
            0x39 => Instruction::DStore(read(stream)),
            0x3a => Instruction::AStore(read(stream)),
            0x3b => Instruction::IStore0,
            0x3c => Instruction::IStore1,
            0x3d => Instruction::IStore2,
            0x3e => Instruction::IStore3,
            0x3f => Instruction::LStore0,
            0x40 => Instruction::LStore1,
            0x41 => Instruction::LStore2,
            0x42 => Instruction::LStore3,
            0x43 => Instruction::FStore0,
            0x44 => Instruction::FStore1,
            0x45 => Instruction::FStore2,
            0x46 => Instruction::FStore3,
            0x47 => Instruction::DStore0,
            0x48 => Instruction::DStore1,
            0x49 => Instruction::DStore2,
            0x4a => Instruction::DStore3,
            0x4b => Instruction::AStore0,
            0x4c => Instruction::AStore1,
            0x4d => Instruction::AStore2,
            0x4e => Instruction::AStore3,
            0x4f => Instruction::IAStore,
            0x50 => Instruction::LAStore,
            0x51 => Instruction::FAStore,
            0x52 => Instruction::DAStore,
            0x53 => Instruction::AAStore,
            0x54 => Instruction::BAStore,
            0x55 => Instruction::CAStore,
            0x56 => Instruction::SAStore,
            0x57 => Instruction::Pop,
            0x58 => Instruction::Pop2,
            0x59 => Instruction::Dup,
            0x5a => Instruction::DupX1,
            0x5b => Instruction::DupX2,
            0x5c => Instruction::Dup2,
            0x5d => Instruction::Dup2X1,
            0x5e => Instruction::Dup2X2,
            0x5f => Instruction::Swap,
            0x60 => Instruction::IAdd,
            0x61 => Instruction::LAdd,
            0x62 => Instruction::FAdd,
            0x63 => Instruction::DAdd,
            0x64 => Instruction::ISub,
            0x65 => Instruction::LSub,
            0x66 => Instruction::FSub,
            0x67 => Instruction::DSub,
            0x68 => Instruction::IMul,
            0x69 => Instruction::LMul,
            0x6a => Instruction::FMul,
            0x6b => Instruction::DMul,
            0x6c => Instruction::IDiv,
            0x6d => Instruction::LDiv,
            0x6e => Instruction::FDiv,
            0x6f => Instruction::DDiv,
            0x70 => Instruction::IRem,
            0x71 => Instruction::LRem,
            0x72 => Instruction::FRem,
            0x73 => Instruction::DRem,
            0x74 => Instruction::INeg,
            0x75 => Instruction::LNeg,
            0x76 => Instruction::FNeg,
            0x77 => Instruction::DNeg,
            0x78 => Instruction::IShl,
            0x79 => Instruction::LShl,
            0x7a => Instruction::IShr,
            0x7b => Instruction::LShr,
            0x7c => Instruction::IUShr,
            0x7d => Instruction::LUShr,
            0x7e => Instruction::IAnd,
            0x7f => Instruction::LAnd,
            0x80 => Instruction::IOr,
            0x81 => Instruction::LOr,
            0x82 => Instruction::IXor,
            0x83 => Instruction::LXor,
            0x84 => Instruction::IInc(read(stream), read(stream)),
            0x85 => Instruction::I2L,
            0x86 => Instruction::I2F,
            0x87 => Instruction::I2D,
            0x88 => Instruction::L2I,
            0x89 => Instruction::L2F,
            0x8a => Instruction::L2D,
            0x8b => Instruction::F2I,
            0x8c => Instruction::F2L,
            0x8d => Instruction::F2D,
            0x8e => Instruction::D2I,
            0x8f => Instruction::D2L,
            0x90 => Instruction::D2F,
            0x91 => Instruction::I2B,
            0x92 => Instruction::I2C,
            0x93 => Instruction::I2S,
            0x94 => Instruction::LCmp,
            0x95 => Instruction::FCmpL,
            0x96 => Instruction::FCmpG,
            0x97 => Instruction::DCmpL,
            0x98 => Instruction::DCmpG,
            0x99 => Instruction::IfEq(u16::from_be_bytes([read(stream), read(stream)])),
            0x9a => Instruction::IfNe(u16::from_be_bytes([read(stream), read(stream)])),
            0x9b => Instruction::IfLt(u16::from_be_bytes([read(stream), read(stream)])),
            0x9c => Instruction::IfGe(u16::from_be_bytes([read(stream), read(stream)])),
            0x9d => Instruction::IfGt(u16::from_be_bytes([read(stream), read(stream)])),
            0x9e => Instruction::IfLe(u16::from_be_bytes([read(stream), read(stream)])),
            0x9f => Instruction::IfICmpEq(u16::from_be_bytes([read(stream), read(stream)])),
            0xa0 => Instruction::IfICmpNe(u16::from_be_bytes([read(stream), read(stream)])),
            0xa1 => Instruction::IfICmpLt(u16::from_be_bytes([read(stream), read(stream)])),
            0xa2 => Instruction::IfICmpGe(u16::from_be_bytes([read(stream), read(stream)])),
            0xa3 => Instruction::IfICmpGt(u16::from_be_bytes([read(stream), read(stream)])),
            0xa4 => Instruction::IfICmpLe(u16::from_be_bytes([read(stream), read(stream)])),
            0xa5 => Instruction::IfACmpEq(u16::from_be_bytes([read(stream), read(stream)])),
            0xa6 => Instruction::IfACmpNe(u16::from_be_bytes([read(stream), read(stream)])),
            0xa7 => Instruction::GoTo(u16::from_be_bytes([read(stream), read(stream)])),
            0xa8 => Instruction::Jsr(u16::from_be_bytes([read(stream), read(stream)])),
            0xa9 => Instruction::Ret(read(stream)),
            0xaa => unimplemented!("TableSwitch isn't implemented yet"),
            0xab => unimplemented!("LookUpSwitch isn't implemented yet"),
            0xac => Instruction::IReturn,
            0xad => Instruction::LReturn,
            0xae => Instruction::FReturn,
            0xaf => Instruction::DReturn,
            0xb0 => Instruction::AReturn,
            0xb1 => Instruction::Return,
            0xb2 => Instruction::GetStatic(u16::from_be_bytes([read(stream), read(stream)])),
            0xb3 => Instruction::PutStatic(u16::from_be_bytes([read(stream), read(stream)])),
            0xb4 => Instruction::GetField(u16::from_be_bytes([read(stream), read(stream)])),
            0xb5 => Instruction::PutField(u16::from_be_bytes([read(stream), read(stream)])),
            0xb6 => Instruction::InvokeVirtual(u16::from_be_bytes([read(stream), read(stream)])),
            0xb7 => Instruction::InvokeSpecial(u16::from_be_bytes([read(stream), read(stream)])),
            0xb8 => Instruction::InvokeStatic(u16::from_be_bytes([read(stream), read(stream)])),
            0xb9 => Instruction::InvokeInterface(
                u16::from_be_bytes([read(stream), read(stream)]),
                read(stream),
                read(stream),
            ),
            0xba => Instruction::InvokeDynamic(
                u16::from_be_bytes([read(stream), read(stream)]),
                read(stream),
                read(stream),
            ),
            0xbb => Instruction::New(u16::from_be_bytes([read(stream), read(stream)])),
            0xbc => Instruction::NewArray(read(stream)),
            0xbd => Instruction::ANewArray(u16::from_be_bytes([read(stream), read(stream)])),
            0xbe => Instruction::ArrayLength,
            0xbf => Instruction::AThrow,
            0xc0 => Instruction::CheckCast(u16::from_be_bytes([read(stream), read(stream)])),
            0xc1 => Instruction::InstanceOf(u16::from_be_bytes([read(stream), read(stream)])),
            0xc2 => Instruction::MonitorEnter,
            0xc3 => Instruction::MonitorExit,
            0xc4 => unimplemented!("Wide isn't implemented yet"),
            0xc5 => Instruction::MultiANewArray(
                u16::from_be_bytes([read(stream), read(stream)]),
                read(stream),
            ),
            0xc6 => Instruction::IfNull(u16::from_be_bytes([read(stream), read(stream)])),
            0xc7 => Instruction::IfNonNull(u16::from_be_bytes([read(stream), read(stream)])),
            0xc8 => Instruction::GoToW(u32::from_be_bytes([
                read(stream),
                read(stream),
                read(stream),
                read(stream),
            ])),
            0xc9 => Instruction::JsrW(u32::from_be_bytes([
                read(stream),
                read(stream),
                read(stream),
                read(stream),
            ])),
            0xca => Instruction::Breakpoint,
            0xfe => Instruction::ImpDep1,
            0xff => Instruction::ImpDep2,
            instruction => unimplemented!("Unknown instruction: {}", instruction),
        });
    }
    instructions
}

fn read(stream: &mut InstructionStream) -> u8 {
    let buf: &mut [u8; 1] = &mut [0u8];
    stream.inner.read_exact(buf).expect("Invalid Code Stream");
    stream.index += 1;
    buf[0]
}
