//! The instructions in a code item.

//use crate as androscalpel_serializer;
use crate::{Error, ReadSeek, Result, Serializable};
//use log::debug;
use std::io::{SeekFrom, Write};
use std::iter::zip;

/// An instruction, following the formats described at
/// <https://source.android.com/docs/core/runtime/dalvik-bytecode>
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Instruction {
    Format10X {
        op: u8,
    },
    /// va and vb are in fact u4 values
    Format12X {
        vb: u8,
        va: u8,
        op: u8,
    },
    /// va and b are in fact u4 values
    Format11N {
        b: i8,
        va: u8,
        op: u8,
    },
    Format11X {
        va: u8,
        op: u8,
    },
    Format10T {
        a: i8,
        op: u8,
    },
    Format20T {
        op: u8,
        a: i16,
    },
    Format20BC {
        a: i8,
        op: u8,
        b: u16,
    },
    Format22X {
        va: u8,
        op: u8,
        vb: u16,
    },
    Format21T {
        va: u8,
        op: u8,
        b: i16,
    },
    Format21S {
        va: u8,
        op: u8,
        b: i16,
    },
    /// b is a a signed hat, ie, the 16 hight order bits of a i32 or i64 value
    Format21H {
        va: u8,
        op: u8,
        b: i16,
    },
    Format21C {
        va: u8,
        op: u8,
        b: u16,
    },
    Format23X {
        va: u8,
        op: u8,
        vc: u8,
        vb: u8,
    },
    Format22B {
        va: u8,
        op: u8,
        c: i8,
        vb: u8,
    },
    /// vb and vb are in fact u4 values
    Format22T {
        vb: u8,
        va: u8,
        op: u8,
        c: i16,
    },
    /// vb and vb are in fact u4 values
    Format22S {
        vb: u8,
        va: u8,
        op: u8,
        c: i16,
    },
    /// vb and vb are in fact u4 values
    Format22C {
        vb: u8,
        va: u8,
        op: u8,
        c: u16,
    },
    /// vb and vb are in fact u4 values
    Format22CS {
        vb: u8,
        va: u8,
        op: u8,
        c: u16,
    },
    Format30T {
        op: u8,
        a: i32,
    },
    Format32X {
        op: u8,
        va: u16,
        vb: u16,
    },
    /// b can also be a f32
    Format31I {
        va: u8,
        op: u8,
        b: i32,
    },
    Format31T {
        va: u8,
        op: u8,
        b: i32,
    },
    Format31C {
        va: u8,
        op: u8,
        b: u32,
    },
    /// a, vg, vf, ve, vd and vc are in fact u4 values
    Format35C {
        a: u8,
        vg: u8,
        op: u8,
        b: u16,
        vf: u8,
        ve: u8,
        vd: u8,
        vc: u8,
    },
    /// a, vg, vf, ve, vd and vc are in fact u4 values
    Format35MS {
        a: u8,
        vg: u8,
        op: u8,
        b: u16,
        vf: u8,
        ve: u8,
        vd: u8,
        vc: u8,
    },
    /// a, vg, vf, ve, vd and vc are in fact u4 values
    Format35MI {
        a: u8,
        vg: u8,
        op: u8,
        b: u16,
        vf: u8,
        ve: u8,
        vd: u8,
        vc: u8,
    },
    Format3RC {
        a: u8,
        op: u8,
        b: u16,
        vc: u16,
    },
    Format3RMS {
        a: u8,
        op: u8,
        b: u16,
        vc: u16,
    },
    Format3RMI {
        a: u8,
        op: u8,
        b: u16,
        vc: u16,
    },
    /// a, vg, vf, ve, vd and vc are in fact u4 values
    Format45CC {
        a: u8,
        vg: u8,
        op: u8,
        b: u16,
        vf: u8,
        ve: u8,
        vd: u8,
        vc: u8,
        h: u16,
    },
    Format4RCC {
        a: u8,
        op: u8,
        b: u16,
        vc: u16,
        h: u16,
    },
    /// B can also be a f64...
    Format51L {
        va: u8,
        op: u8,
        b: i64,
    },
    FormatPackedSwitchPayload {
        first_key: i32,
        targets: Vec<i32>,
    },
    FormatSparseSwitchPayload {
        key_targets: Vec<(i32, i32)>,
    },
    FormatFillArrayDataPayload {
        elt_width: u16,
        data: Vec<u8>,
    },
}

impl Instruction {
    pub fn deserialize_10x(input: &mut dyn ReadSeek) -> Result<Self> {
        let op = u8::deserialize(input)?;
        let v = u8::deserialize(input)?;
        if v != 0 {
            // TODO: is it enforced on actual android system?
            return Err(Error::InconsistantStruct(format!(
                "Dalvik instruction format for op 0x{op:x}: '10x' (00|op) \
                requires the first byte to be 0 found {v}"
            )));
        }
        Ok(Self::Format10X { op })
    }

    pub fn deserialize_12x(input: &mut dyn ReadSeek) -> Result<Self> {
        let op = u8::deserialize(input)?;
        let val = u8::deserialize(input)?;
        let vb = (val & 0b1111_0000) >> 4;
        let va = val & 0b0000_1111;
        Ok(Self::Format12X { vb, va, op })
    }

    pub fn deserialize_11n(input: &mut dyn ReadSeek) -> Result<Self> {
        let op = u8::deserialize(input)?;
        let val = u8::deserialize(input)?;
        let b = (val & 0b1111_0000) >> 4;
        let b = if b & 0b0000_1000 != 0 {
            // Sign extention
            b | 0b1111_0000
        } else {
            b
        };
        let b = i8::from_be_bytes([b]);
        let va = val & 0b0000_1111;
        Ok(Self::Format11N { b, va, op })
    }

    pub fn deserialize_11x(input: &mut dyn ReadSeek) -> Result<Self> {
        let op = u8::deserialize(input)?;
        let va = u8::deserialize(input)?;
        Ok(Self::Format11X { va, op })
    }

    pub fn deserialize_10t(input: &mut dyn ReadSeek) -> Result<Self> {
        let op = u8::deserialize(input)?;
        let a = i8::deserialize(input)?;
        Ok(Self::Format10T { a, op })
    }

    pub fn deserialize_20t(input: &mut dyn ReadSeek) -> Result<Self> {
        let op = u8::deserialize(input)?;
        let v = u8::deserialize(input)?;
        if v != 0 {
            // TODO: is it enforced on actual android system?
            return Err(Error::InconsistantStruct(format!(
                "Dalvik instruction format for op 0x{op:x}: '20t' (00|op AAAA) \
                requires the first byte to be 0 found {v}"
            )));
        }
        let a = i16::deserialize(input)?;
        Ok(Self::Format20T { op, a })
    }

    pub fn deserialize_20bc(input: &mut dyn ReadSeek) -> Result<Self> {
        let op = u8::deserialize(input)?;
        let a = i8::deserialize(input)?;
        let b = u16::deserialize(input)?;
        Ok(Self::Format20BC { a, op, b })
    }

    pub fn deserialize_22x(input: &mut dyn ReadSeek) -> Result<Self> {
        let op = u8::deserialize(input)?;
        let va = u8::deserialize(input)?;
        let vb = u16::deserialize(input)?;
        Ok(Self::Format22X { va, op, vb })
    }

    pub fn deserialize_21t(input: &mut dyn ReadSeek) -> Result<Self> {
        let op = u8::deserialize(input)?;
        let va = u8::deserialize(input)?;
        let b = i16::deserialize(input)?;
        Ok(Self::Format21T { va, op, b })
    }

    pub fn deserialize_21s(input: &mut dyn ReadSeek) -> Result<Self> {
        let op = u8::deserialize(input)?;
        let va = u8::deserialize(input)?;
        let b = i16::deserialize(input)?;
        Ok(Self::Format21S { va, op, b })
    }

    pub fn deserialize_21h(input: &mut dyn ReadSeek) -> Result<Self> {
        let op = u8::deserialize(input)?;
        let va = u8::deserialize(input)?;
        let b = i16::deserialize(input)?;
        Ok(Self::Format21H { va, op, b })
    }

    pub fn deserialize_21c(input: &mut dyn ReadSeek) -> Result<Self> {
        let op = u8::deserialize(input)?;
        let va = u8::deserialize(input)?;
        let b = u16::deserialize(input)?;
        Ok(Self::Format21C { va, op, b })
    }

    pub fn deserialize_23x(input: &mut dyn ReadSeek) -> Result<Self> {
        let op = u8::deserialize(input)?;
        let va = u8::deserialize(input)?;
        let vb = u8::deserialize(input)?;
        let vc = u8::deserialize(input)?;
        Ok(Self::Format23X { va, op, vc, vb })
    }

    pub fn deserialize_22b(input: &mut dyn ReadSeek) -> Result<Self> {
        let op = u8::deserialize(input)?;
        let va = u8::deserialize(input)?;
        let vb = u8::deserialize(input)?;
        let c = i8::deserialize(input)?;
        Ok(Self::Format22B { va, op, c, vb })
    }

    pub fn deserialize_22t(input: &mut dyn ReadSeek) -> Result<Self> {
        let op = u8::deserialize(input)?;
        let val = u8::deserialize(input)?;
        let va = val & 0b0000_1111;
        let vb = (val & 0b1111_0000) >> 4;
        let c = i16::deserialize(input)?;
        Ok(Self::Format22T { vb, va, op, c })
    }

    pub fn deserialize_22s(input: &mut dyn ReadSeek) -> Result<Self> {
        let op = u8::deserialize(input)?;
        let val = u8::deserialize(input)?;
        let va = val & 0b0000_1111;
        let vb = (val & 0b1111_0000) >> 4;
        let c = i16::deserialize(input)?;
        Ok(Self::Format22S { vb, va, op, c })
    }

    pub fn deserialize_22c(input: &mut dyn ReadSeek) -> Result<Self> {
        let op = u8::deserialize(input)?;
        let val = u8::deserialize(input)?;
        let va = val & 0b0000_1111;
        let vb = (val & 0b1111_0000) >> 4;
        let c = u16::deserialize(input)?;
        Ok(Self::Format22C { vb, va, op, c })
    }

    pub fn deserialize_22cs(input: &mut dyn ReadSeek) -> Result<Self> {
        let op = u8::deserialize(input)?;
        let val = u8::deserialize(input)?;
        let va = val & 0b0000_1111;
        let vb = (val & 0b1111_0000) >> 4;
        let c = u16::deserialize(input)?;
        Ok(Self::Format22CS { vb, va, op, c })
    }

    pub fn deserialize_30t(input: &mut dyn ReadSeek) -> Result<Self> {
        let op = u8::deserialize(input)?;
        let v = u8::deserialize(input)?;
        if v != 0 {
            // TODO: is it enforced on actual android system?
            return Err(Error::InconsistantStruct(format!(
                "Dalvik instruction format for op 0x{op:x}: '30t' (00|op AAAA AAAA) \
                requires the first byte to be 0 found {v}"
            )));
        }
        /*
        let [a_l0, a_l1, a_h0, a_h1] = i32::deserialize(input)?.to_be_bytes();
        let a = i32::from_be_bytes([a_h0, a_h1, a_l0, a_l1]);
        */
        let a = i32::deserialize(input)?;
        Ok(Self::Format30T { op, a })
    }

    pub fn deserialize_32x(input: &mut dyn ReadSeek) -> Result<Self> {
        let op = u8::deserialize(input)?;
        let v = u8::deserialize(input)?;
        if v != 0 {
            // TODO: is it enforced on actual android system?
            return Err(Error::InconsistantStruct(format!(
                "Dalvik instruction format for op 0x{op:x}: '32x' (00|op AAAA BBBB) \
                requires the first byte to be 0 found {v}"
            )));
        }
        let va = u16::deserialize(input)?;
        let vb = u16::deserialize(input)?;
        Ok(Self::Format32X { op, va, vb })
    }

    pub fn deserialize_31i(input: &mut dyn ReadSeek) -> Result<Self> {
        let op = u8::deserialize(input)?;
        let va = u8::deserialize(input)?;
        let b = i32::deserialize(input)?;
        /*
        let [b_l0, b_l1, b_h0, b_h1] = i32::deserialize(input)?.to_be_bytes();
        let b = i32::from_be_bytes([b_h0, b_h1, b_l0, b_l1]);
        */
        Ok(Self::Format31I { va, op, b })
    }

    pub fn deserialize_31t(input: &mut dyn ReadSeek) -> Result<Self> {
        let op = u8::deserialize(input)?;
        let va = u8::deserialize(input)?;
        let b = i32::deserialize(input)?;
        /*
        let [b_l0, b_l1, b_h0, b_h1] = i32::deserialize(input)?.to_be_bytes();
        let b = i32::from_be_bytes([b_h0, b_h1, b_l0, b_l1]);
        */
        Ok(Self::Format31T { va, op, b })
    }

    pub fn deserialize_31c(input: &mut dyn ReadSeek) -> Result<Self> {
        let op = u8::deserialize(input)?;
        let va = u8::deserialize(input)?;
        let b = u32::deserialize(input)?;
        /*
        let [b_l0, b_l1, b_h0, b_h1] = u32::deserialize(input)?.to_be_bytes();
        let b = u32::from_be_bytes([b_h0, b_h1, b_l0, b_l1]);
        */
        Ok(Self::Format31C { va, op, b })
    }

    pub fn deserialize_35c(input: &mut dyn ReadSeek) -> Result<Self> {
        let op = u8::deserialize(input)?;
        let val = u8::deserialize(input)?;
        let a = (val & 0b1111_0000) >> 4;
        let vg = val & 0b0000_1111;
        if a > 5 {
            return Err(Error::InconsistantStruct(format!(
                "Dalvik instruction format for op 0x{op:x}: '35c' (A|G|op BBBB F|E|D|C) \
                         requires A to be between 0 and 5, found {a}"
            )));
        }
        let b = u16::deserialize(input)?;

        let val = u8::deserialize(input)?;
        let vd = (val & 0b1111_0000) >> 4;
        let vc = val & 0b0000_1111;

        let val = u8::deserialize(input)?;
        let vf = (val & 0b1111_0000) >> 4;
        let ve = val & 0b0000_1111;

        Ok(Self::Format35C {
            a,
            vg,
            op,
            b,
            vf,
            ve,
            vd,
            vc,
        })
    }

    pub fn deserialize_35ms(input: &mut dyn ReadSeek) -> Result<Self> {
        let op = u8::deserialize(input)?;
        let val = u8::deserialize(input)?;
        let a = (val & 0b1111_0000) >> 4;
        let vg = val & 0b0000_1111;
        if a > 5 {
            return Err(Error::InconsistantStruct(format!(
                "Dalvik instruction format for op 0x{op:x}: '35ms' (A|G|op BBBB F|E|D|C) \
                         requires A to be between 0 and 5, found {a}"
            )));
        }
        let b = u16::deserialize(input)?;

        let val = u8::deserialize(input)?;
        let vd = (val & 0b1111_0000) >> 4;
        let vc = val & 0b0000_1111;

        let val = u8::deserialize(input)?;
        let vf = (val & 0b1111_0000) >> 4;
        let ve = val & 0b0000_1111;

        Ok(Self::Format35MS {
            a,
            vg,
            op,
            b,
            vf,
            ve,
            vd,
            vc,
        })
    }

    pub fn deserialize_35mi(input: &mut dyn ReadSeek) -> Result<Self> {
        let op = u8::deserialize(input)?;
        let val = u8::deserialize(input)?;
        let a = (val & 0b1111_0000) >> 4;
        let vg = val & 0b0000_1111;
        if a > 5 {
            return Err(Error::InconsistantStruct(format!(
                "Dalvik instruction format for op 0x{op:x}: '35mi' (A|G|op BBBB F|E|D|C) \
                         requires A to be between 0 and 5, found {a}"
            )));
        }
        let b = u16::deserialize(input)?;

        let val = u8::deserialize(input)?;
        let vd = (val & 0b1111_0000) >> 4;
        let vc = val & 0b0000_1111;

        let val = u8::deserialize(input)?;
        let vf = (val & 0b1111_0000) >> 4;
        let ve = val & 0b0000_1111;

        Ok(Self::Format35MI {
            a,
            vg,
            op,
            b,
            vf,
            ve,
            vd,
            vc,
        })
    }

    pub fn deserialize_3rc(input: &mut dyn ReadSeek) -> Result<Self> {
        let op = u8::deserialize(input)?;
        let a = u8::deserialize(input)?;
        let b = u16::deserialize(input)?;
        let vc = u16::deserialize(input)?;
        Ok(Self::Format3RC { a, op, b, vc })
    }

    pub fn deserialize_3rms(input: &mut dyn ReadSeek) -> Result<Self> {
        let op = u8::deserialize(input)?;
        let a = u8::deserialize(input)?;
        let b = u16::deserialize(input)?;
        let vc = u16::deserialize(input)?;
        Ok(Self::Format3RMS { a, op, b, vc })
    }

    pub fn deserialize_3rmi(input: &mut dyn ReadSeek) -> Result<Self> {
        let op = u8::deserialize(input)?;
        let a = u8::deserialize(input)?;
        let b = u16::deserialize(input)?;
        let vc = u16::deserialize(input)?;
        Ok(Self::Format3RMI { a, op, b, vc })
    }

    pub fn deserialize_45cc(input: &mut dyn ReadSeek) -> Result<Self> {
        let op = u8::deserialize(input)?;
        let val = u8::deserialize(input)?;
        let a = (val & 0b1111_0000) >> 4;
        let vg = val & 0b0000_1111;
        if a > 5 {
            return Err(Error::InconsistantStruct(format!(
                "Dalvik instruction format for op 0x{op:x}: '45cc' (A|G|op BBBB F|E|D|C HHHH) \
                         requires A to be between 0 and 5, found {a}"
            )));
        }
        let b = u16::deserialize(input)?;

        let val = u8::deserialize(input)?;
        let vd = (val & 0b1111_0000) >> 4;
        let vc = val & 0b0000_1111;

        let val = u8::deserialize(input)?;
        let vf = (val & 0b1111_0000) >> 4;
        let ve = val & 0b0000_1111;

        let h = u16::deserialize(input)?;

        Ok(Self::Format45CC {
            a,
            vg,
            op,
            b,
            vf,
            ve,
            vd,
            vc,
            h,
        })
    }

    pub fn deserialize_4rcc(input: &mut dyn ReadSeek) -> Result<Self> {
        let op = u8::deserialize(input)?;
        let a = u8::deserialize(input)?;
        let b = u16::deserialize(input)?;
        let vc = u16::deserialize(input)?;
        let h = u16::deserialize(input)?;
        Ok(Self::Format4RCC { a, op, b, vc, h })
    }

    pub fn deserialize_51l(input: &mut dyn ReadSeek) -> Result<Self> {
        let op = u8::deserialize(input)?;
        let va = u8::deserialize(input)?;
        let b = i64::deserialize(input)?;
        Ok(Self::Format51L { va, op, b })
    }

    pub fn deserialize_packed_switch(input: &mut dyn ReadSeek) -> Result<Self> {
        let _ = u16::deserialize(input)?;
        let size = u16::deserialize(input)?;
        let first_key = i32::deserialize(input)?;
        let mut targets = vec![];
        for _ in 0..size {
            targets.push(i32::deserialize(input)?);
        }
        Ok(Self::FormatPackedSwitchPayload { first_key, targets })
    }

    pub fn deserialize_sparse_switch(input: &mut dyn ReadSeek) -> Result<Self> {
        let _ = u16::deserialize(input)?;
        let size = u16::deserialize(input)?;
        let mut keys = vec![];
        let mut targets = vec![];
        for _ in 0..size {
            keys.push(i32::deserialize(input)?);
        }
        for _ in 0..size {
            targets.push(i32::deserialize(input)?);
        }
        let key_targets = zip(keys, targets).collect();
        Ok(Self::FormatSparseSwitchPayload { key_targets })
    }

    pub fn deserialize_fill_array_data(input: &mut dyn ReadSeek) -> Result<Self> {
        let _ = u16::deserialize(input)?;
        let elt_width = u16::deserialize(input)?;
        let size = u32::deserialize(input)?;
        let len = size * elt_width as u32;
        let mut data = vec![];
        for _ in 0..len {
            data.push(u8::deserialize(input)?);
        }
        if len % 2 != 0 {
            let _ = u8::deserialize(input)?;
        }

        Ok(Self::FormatFillArrayDataPayload { elt_width, data })
    }
}

impl Serializable for Instruction {
    fn serialize(&self, output: &mut dyn Write) -> Result<()> {
        match self {
            Self::Format10X { op } => {
                op.serialize(output)?;
                0u8.serialize(output)
            }
            Self::Format12X { vb, va, op } => {
                if vb & 0b1111_0000 != 0 {
                    return Err(Error::InconsistantStruct(format!(
                        "Dalvik instruction format for op 0x{op:x}: '12x' (B|A|op) \
                         requires B to be between 0 and 15, found {vb}"
                    )));
                }
                if va & 0b1111_0000 != 0 {
                    return Err(Error::InconsistantStruct(format!(
                        "Dalvik instruction format for op 0x{op:x}: '12x' (B|A|op) \
                         requires A to be between 0 and 15, found {va}"
                    )));
                }
                let val: u8 = (vb << 4) | va;
                op.serialize(output)?;
                val.serialize(output)
            }
            Self::Format11N { b, va, op } => {
                if *b < -8 || *b > 7 {
                    return Err(Error::InconsistantStruct(format!(
                        "Dalvik instruction format for op 0x{op:x}: '11n' (B|A|op) \
                         requires B to be between -8 and 7, found {b}"
                    )));
                }
                let b = b.to_be_bytes()[0] & 0b0000_1111;
                if va & 0b1111_0000 != 0 {
                    return Err(Error::InconsistantStruct(format!(
                        "Dalvik instruction format for op 0x{op:x}: '11n' (B|A|op) \
                         requires A to be between 0 and 15, found {va}"
                    )));
                }
                let val: u8 = (b << 4) | va;
                op.serialize(output)?;
                val.serialize(output)
            }
            Self::Format11X { va, op } => {
                op.serialize(output)?;
                va.serialize(output)
            }
            Self::Format10T { a, op } => {
                op.serialize(output)?;
                a.serialize(output)
            }
            Self::Format20T { op, a } => {
                op.serialize(output)?;
                0u8.serialize(output)?;
                a.serialize(output)
            }
            Self::Format20BC { a, op, b } => {
                op.serialize(output)?;
                a.serialize(output)?;
                b.serialize(output)
            }
            Self::Format22X { va, op, vb } => {
                op.serialize(output)?;
                va.serialize(output)?;
                vb.serialize(output)
            }
            Self::Format21T { va, op, b } => {
                op.serialize(output)?;
                va.serialize(output)?;
                b.serialize(output)
            }
            Self::Format21S { va, op, b } => {
                op.serialize(output)?;
                va.serialize(output)?;
                b.serialize(output)
            }
            Self::Format21H { va, op, b } => {
                op.serialize(output)?;
                va.serialize(output)?;
                b.serialize(output)
            }
            Self::Format21C { va, op, b } => {
                op.serialize(output)?;
                va.serialize(output)?;
                b.serialize(output)
            }
            Self::Format23X { va, op, vc, vb } => {
                op.serialize(output)?;
                va.serialize(output)?;
                vb.serialize(output)?;
                vc.serialize(output)
            }
            Self::Format22B { va, op, c, vb } => {
                op.serialize(output)?;
                va.serialize(output)?;
                vb.serialize(output)?;
                c.serialize(output)
            }
            Self::Format22T { vb, va, op, c } => {
                if vb & 0b1111_0000 != 0 {
                    return Err(Error::InconsistantStruct(format!(
                        "Dalvik instruction format for op 0x{op:x}: '22t' (B|A|op CCCC) \
                         requires B to be between 0 and 15, found {vb}"
                    )));
                }
                if va & 0b1111_0000 != 0 {
                    return Err(Error::InconsistantStruct(format!(
                        "Dalvik instruction format for op 0x{op:x}: '22t' (B|A|op CCCC) \
                         requires A to be between 0 and 15, found {va}"
                    )));
                }
                let val: u8 = (vb << 4) | va;
                op.serialize(output)?;
                val.serialize(output)?;
                c.serialize(output)
            }
            Self::Format22S { vb, va, op, c } => {
                if vb & 0b1111_0000 != 0 {
                    return Err(Error::InconsistantStruct(format!(
                        "Dalvik instruction format for op 0x{op:x}: '22s' (B|A|op CCCC) \
                         requires B to be between 0 and 15, found {vb}"
                    )));
                }
                if va & 0b1111_0000 != 0 {
                    return Err(Error::InconsistantStruct(format!(
                        "Dalvik instruction format for op 0x{op:x}: '22s' (B|A|op CCCC) \
                         requires A to be between 0 and 15, found {va}"
                    )));
                }
                let val: u8 = (vb << 4) | va;
                op.serialize(output)?;
                val.serialize(output)?;
                c.serialize(output)
            }

            Self::Format22C { vb, va, op, c } => {
                if vb & 0b1111_0000 != 0 {
                    return Err(Error::InconsistantStruct(format!(
                        "Dalvik instruction format for op 0x{op:x}: '22c' (B|A|op CCCC) \
                         requires B to be between 0 and 15, found {vb}"
                    )));
                }
                if va & 0b1111_0000 != 0 {
                    return Err(Error::InconsistantStruct(format!(
                        "Dalvik instruction format for op 0x{op:x}: '22c' (B|A|op CCCC) \
                         requires A to be between 0 and 15, found {va}"
                    )));
                }
                let val: u8 = (vb << 4) | va;
                op.serialize(output)?;
                val.serialize(output)?;
                c.serialize(output)
            }
            Self::Format22CS { vb, va, op, c } => {
                if vb & 0b1111_0000 != 0 {
                    return Err(Error::InconsistantStruct(format!(
                        "Dalvik instruction format for op 0x{op:x}: '22cs' (B|A|op CCCC) \
                         requires B to be between 0 and 15, found {vb}"
                    )));
                }
                if va & 0b1111_0000 != 0 {
                    return Err(Error::InconsistantStruct(format!(
                        "Dalvik instruction format for op 0x{op:x}: '22cs' (B|A|op CCCC) \
                         requires A to be between 0 and 15, found {va}"
                    )));
                }
                let val: u8 = (vb << 4) | va;
                op.serialize(output)?;
                val.serialize(output)?;
                c.serialize(output)
            }
            Self::Format30T { op, a } => {
                op.serialize(output)?;
                0u8.serialize(output)?;
                /*
                let [a_h0, a_h1, a_l0, a_l1] = a.to_be_bytes();
                u32::from_be_bytes([a_l0, a_l1, a_h0, a_h1]).serialize(output)
                */
                a.serialize(output)
            }
            Self::Format32X { op, va, vb } => {
                op.serialize(output)?;
                0u8.serialize(output)?;
                va.serialize(output)?;
                vb.serialize(output)
            }
            Self::Format31I { va, op, b } => {
                op.serialize(output)?;
                va.serialize(output)?;
                /*
                let [b_h0, b_h1, b_l0, b_l1] = b.to_be_bytes();
                u32::from_be_bytes([b_l0, b_l1, b_h0, b_h1]).serialize(output)
                */
                b.serialize(output)
            }
            Self::Format31T { va, op, b } => {
                op.serialize(output)?;
                va.serialize(output)?;
                /*
                let [b_h0, b_h1, b_l0, b_l1] = b.to_be_bytes();
                u32::from_be_bytes([b_l0, b_l1, b_h0, b_h1]).serialize(output)
                */
                b.serialize(output)
            }
            Self::Format31C { va, op, b } => {
                op.serialize(output)?;
                va.serialize(output)?;
                /*
                let [b_h0, b_h1, b_l0, b_l1] = b.to_be_bytes();
                u32::from_be_bytes([b_l0, b_l1, b_h0, b_h1]).serialize(output)
                */
                b.serialize(output)
            }
            Self::Format35C {
                a,
                vg,
                op,
                b,
                vf,
                ve,
                vd,
                vc,
            } => {
                if *a > 5 {
                    return Err(Error::InconsistantStruct(format!(
                        "Dalvik instruction format for op 0x{op:x}: '35c' (A|G|op BBBB F|E|D|C) \
                         requires A to be between 0 and 5, found {a}"
                    )));
                }
                if vg & 0b1111_0000 != 0 {
                    return Err(Error::InconsistantStruct(format!(
                        "Dalvik instruction format for op 0x{op:x}: '35c' (A|G|op BBBB F|E|D|C) \
                         requires G to be between 0 and 15, found {vg}"
                    )));
                }
                if vf & 0b1111_0000 != 0 {
                    return Err(Error::InconsistantStruct(format!(
                        "Dalvik instruction format for op 0x{op:x}: '35c' (A|G|op BBBB F|E|D|C) \
                         requires F to be between 0 and 15, found {vf}"
                    )));
                }
                if ve & 0b1111_0000 != 0 {
                    return Err(Error::InconsistantStruct(format!(
                        "Dalvik instruction format for op 0x{op:x}: '35c' (A|G|op BBBB F|E|D|C) \
                         requires E to be between 0 and 15, found {ve}"
                    )));
                }
                if vd & 0b1111_0000 != 0 {
                    return Err(Error::InconsistantStruct(format!(
                        "Dalvik instruction format for op 0x{op:x}: '35c' (A|G|op BBBB F|E|D|C) \
                         requires D to be between 0 and 15, found {vd}"
                    )));
                }
                if vc & 0b1111_0000 != 0 {
                    return Err(Error::InconsistantStruct(format!(
                        "Dalvik instruction format for op 0x{op:x}: '35c' (A|G|op BBBB F|E|D|C) \
                         requires C to be between 0 and 15, found {vc}"
                    )));
                }
                op.serialize(output)?;
                let val = (a << 4) | vg;
                val.serialize(output)?;
                b.serialize(output)?;
                let val = (vd << 4) | vc;
                val.serialize(output)?;
                let val = (vf << 4) | ve;
                val.serialize(output)
            }
            Self::Format35MS {
                a,
                vg,
                op,
                b,
                vf,
                ve,
                vd,
                vc,
            } => {
                if *a > 5 {
                    return Err(Error::InconsistantStruct(format!(
                        "Dalvik instruction format for op 0x{op:x}: '35ms' (A|G|op BBBB F|E|D|C) \
                         requires A to be between 0 and 5, found {a}"
                    )));
                }
                if vg & 0b1111_0000 != 0 {
                    return Err(Error::InconsistantStruct(format!(
                        "Dalvik instruction format for op 0x{op:x}: '35ms' (A|G|op BBBB F|E|D|C) \
                         requires G to be between 0 and 15, found {vg}"
                    )));
                }
                if vf & 0b1111_0000 != 0 {
                    return Err(Error::InconsistantStruct(format!(
                        "Dalvik instruction format for op 0x{op:x}: '35ms' (A|G|op BBBB F|E|D|C) \
                         requires F to be between 0 and 15, found {vf}"
                    )));
                }
                if ve & 0b1111_0000 != 0 {
                    return Err(Error::InconsistantStruct(format!(
                        "Dalvik instruction format for op 0x{op:x}: '35ms' (A|G|op BBBB F|E|D|C) \
                         requires E to be between 0 and 15, found {ve}"
                    )));
                }
                if vd & 0b1111_0000 != 0 {
                    return Err(Error::InconsistantStruct(format!(
                        "Dalvik instruction format for op 0x{op:x}: '35ms' (A|G|op BBBB F|E|D|C) \
                         requires D to be between 0 and 15, found {vd}"
                    )));
                }
                if vc & 0b1111_0000 != 0 {
                    return Err(Error::InconsistantStruct(format!(
                        "Dalvik instruction format for op 0x{op:x}: '35ms' (A|G|op BBBB F|E|D|C) \
                         requires C to be between 0 and 15, found {vc}"
                    )));
                }
                let val = (a << 4) | vg;
                op.serialize(output)?;
                val.serialize(output)?;
                b.serialize(output)?;
                let val = (vd << 4) | vc;
                val.serialize(output)?;
                let val = (vf << 4) | ve;
                val.serialize(output)
            }
            Self::Format35MI {
                a,
                vg,
                op,
                b,
                vf,
                ve,
                vd,
                vc,
            } => {
                if *a > 5 {
                    return Err(Error::InconsistantStruct(format!(
                        "Dalvik instruction format for op 0x{op:x}: '35mi' (A|G|op BBBB F|E|D|C) \
                         requires A to be between 0 and 5, found {a}"
                    )));
                }
                if vg & 0b1111_0000 != 0 {
                    return Err(Error::InconsistantStruct(format!(
                        "Dalvik instruction format for op 0x{op:x}: '35mi' (A|G|op BBBB F|E|D|C) \
                         requires G to be between 0 and 15, found {vg}"
                    )));
                }
                if vf & 0b1111_0000 != 0 {
                    return Err(Error::InconsistantStruct(format!(
                        "Dalvik instruction format for op 0x{op:x}: '35mi' (A|G|op BBBB F|E|D|C) \
                         requires F to be between 0 and 15, found {vf}"
                    )));
                }
                if ve & 0b1111_0000 != 0 {
                    return Err(Error::InconsistantStruct(format!(
                        "Dalvik instruction format for op 0x{op:x}: '35mi' (A|G|op BBBB F|E|D|C) \
                         requires E to be between 0 and 15, found {ve}"
                    )));
                }
                if vd & 0b1111_0000 != 0 {
                    return Err(Error::InconsistantStruct(format!(
                        "Dalvik instruction format for op 0x{op:x}: '35mi' (A|G|op BBBB F|E|D|C) \
                         requires D to be between 0 and 15, found {vd}"
                    )));
                }
                if vc & 0b1111_0000 != 0 {
                    return Err(Error::InconsistantStruct(format!(
                        "Dalvik instruction format for op 0x{op:x}: '35mi' (A|G|op BBBB F|E|D|C) \
                         requires C to be between 0 and 15, found {vc}"
                    )));
                }
                op.serialize(output)?;
                let val = (a << 4) | vg;
                val.serialize(output)?;
                b.serialize(output)?;
                let val = (vd << 4) | vc;
                val.serialize(output)?;
                let val = (vf << 4) | ve;
                val.serialize(output)
            }
            Self::Format3RC { a, op, b, vc } => {
                op.serialize(output)?;
                a.serialize(output)?;
                b.serialize(output)?;
                vc.serialize(output)
            }
            Self::Format3RMS { a, op, b, vc } => {
                op.serialize(output)?;
                a.serialize(output)?;
                b.serialize(output)?;
                vc.serialize(output)
            }
            Self::Format3RMI { a, op, b, vc } => {
                op.serialize(output)?;
                a.serialize(output)?;
                b.serialize(output)?;
                vc.serialize(output)
            }
            Self::Format45CC {
                a,
                vg,
                op,
                b,
                vf,
                ve,
                vd,
                vc,
                h,
            } => {
                if *a > 5 {
                    return Err(Error::InconsistantStruct(format!(
                        "Dalvik instruction format for op 0x{op:x}: '45cc' (A|G|op BBBB F|E|D|C) \
                         requires A to be between 0 and 5, found {a}"
                    )));
                }
                if vg & 0b1111_0000 != 0 {
                    return Err(Error::InconsistantStruct(format!(
                        "Dalvik instruction format for op 0x{op:x}: '45cc' (A|G|op BBBB F|E|D|C) \
                         requires G to be between 0 and 15, found {vg}"
                    )));
                }
                if vf & 0b1111_0000 != 0 {
                    return Err(Error::InconsistantStruct(format!(
                        "Dalvik instruction format for op 0x{op:x}: '45cc' (A|G|op BBBB F|E|D|C) \
                         requires F to be between 0 and 15, found {vf}"
                    )));
                }
                if ve & 0b1111_0000 != 0 {
                    return Err(Error::InconsistantStruct(format!(
                        "Dalvik instruction format for op 0x{op:x}: '45cc' (A|G|op BBBB F|E|D|C) \
                         requires E to be between 0 and 15, found {ve}"
                    )));
                }
                if vd & 0b1111_0000 != 0 {
                    return Err(Error::InconsistantStruct(format!(
                        "Dalvik instruction format for op 0x{op:x}: '45cc' (A|G|op BBBB F|E|D|C) \
                         requires D to be between 0 and 15, found {vd}"
                    )));
                }
                if vc & 0b1111_0000 != 0 {
                    return Err(Error::InconsistantStruct(format!(
                        "Dalvik instruction format for op 0x{op:x}: '45cc' (A|G|op BBBB F|E|D|C) \
                         requires C to be between 0 and 15, found {vc}"
                    )));
                }
                op.serialize(output)?;
                let val = (a << 4) | vg;
                val.serialize(output)?;
                b.serialize(output)?;
                let val = (vd << 4) | vc;
                val.serialize(output)?;
                let val = (vf << 4) | ve;
                val.serialize(output)?;
                h.serialize(output)
            }
            Self::Format4RCC { a, op, b, vc, h } => {
                op.serialize(output)?;
                a.serialize(output)?;
                b.serialize(output)?;
                vc.serialize(output)?;
                h.serialize(output)
            }
            Self::Format51L { va, op, b } => {
                op.serialize(output)?;
                va.serialize(output)?;
                b.serialize(output)
            }
            Self::FormatPackedSwitchPayload { first_key, targets } => {
                0x0100u16.serialize(output)?;
                let size = targets.len() as u16;
                size.serialize(output)?;
                first_key.serialize(output)?;
                for target in targets {
                    target.serialize(output)?;
                }
                Ok(())
            }
            Self::FormatSparseSwitchPayload { key_targets } => {
                0x0200u16.serialize(output)?;
                let size = key_targets.len() as u16;
                size.serialize(output)?;
                for (key, _) in key_targets {
                    key.serialize(output)?;
                }
                for (_, target) in key_targets {
                    target.serialize(output)?;
                }
                Ok(())
            }
            Self::FormatFillArrayDataPayload { elt_width, data } => {
                0x0300u16.serialize(output)?;
                elt_width.serialize(output)?;
                let size = (data.len() / *elt_width as usize) as u32;
                size.serialize(output)?;
                for d in data {
                    d.serialize(output)?;
                }
                if data.len() % 2 != 0 {
                    0u8.serialize(output)?;
                }
                Ok(())
            }
        }
    }

    fn deserialize(input: &mut dyn ReadSeek) -> Result<Self> {
        let pos = input.stream_position().map_err(|err| {
            Error::SerializationError(format!("Failled to get position in steam: {err}"))
        })?;
        let op = u8::deserialize(input)?;
        let id = u8::deserialize(input)?;
        input.seek(SeekFrom::Start(pos)).map_err(|err| {
            Error::SerializationError(format!("Failled to get to position in steam: {err}"))
        })?;

        match op {
            0x00 => match id {
                0x01 => Self::deserialize_packed_switch(input),
                0x02 => Self::deserialize_sparse_switch(input),
                0x03 => Self::deserialize_fill_array_data(input),
                _ => Self::deserialize_10x(input),
            },
            0x01 => Self::deserialize_12x(input),
            0x02 => Self::deserialize_22x(input),
            0x03 => Self::deserialize_32x(input),
            0x04 => Self::deserialize_12x(input),
            0x05 => Self::deserialize_22x(input),
            0x06 => Self::deserialize_32x(input),
            0x07 => Self::deserialize_12x(input),
            0x08 => Self::deserialize_22x(input),
            0x09 => Self::deserialize_32x(input),
            0x0a => Self::deserialize_11x(input),
            0x0b => Self::deserialize_11x(input),
            0x0c => Self::deserialize_11x(input),
            0x0d => Self::deserialize_11x(input),
            0x0e => Self::deserialize_10x(input),
            0x0f => Self::deserialize_11x(input),
            0x10 => Self::deserialize_11x(input),
            0x11 => Self::deserialize_11x(input),
            0x12 => Self::deserialize_11n(input),
            0x13 => Self::deserialize_21s(input),
            0x14 => Self::deserialize_31i(input),
            0x15 => Self::deserialize_21h(input),
            0x16 => Self::deserialize_21s(input),
            0x17 => Self::deserialize_31i(input),
            0x18 => Self::deserialize_51l(input),
            0x19 => Self::deserialize_21h(input),
            0x1a => Self::deserialize_21c(input),
            0x1b => Self::deserialize_31c(input),
            0x1c => Self::deserialize_21c(input),
            0x1d => Self::deserialize_11x(input),
            0x1e => Self::deserialize_11x(input),
            0x1f => Self::deserialize_21c(input),
            0x20 => Self::deserialize_22c(input),
            0x21 => Self::deserialize_12x(input),
            0x22 => Self::deserialize_21c(input),
            0x23 => Self::deserialize_22c(input),
            0x24 => Self::deserialize_35c(input),
            0x25 => Self::deserialize_3rc(input),
            0x26 => Self::deserialize_31t(input),
            0x27 => Self::deserialize_11x(input),
            0x28 => Self::deserialize_10t(input),
            0x29 => Self::deserialize_20t(input),
            0x2a => Self::deserialize_30t(input),
            0x2b => Self::deserialize_31t(input),
            0x2c => Self::deserialize_31t(input),
            0x2d..=0x31 => Self::deserialize_23x(input),
            0x32..=0x37 => Self::deserialize_22t(input),
            0x38..=0x3d => Self::deserialize_21t(input),
            0x3e..=0x43 => Self::deserialize_10x(input),
            0x44..=0x51 => Self::deserialize_23x(input),
            0x52..=0x5f => Self::deserialize_22c(input),
            0x60..=0x6d => Self::deserialize_21c(input),
            0x6e..=0x72 => Self::deserialize_35c(input),
            0x73 => Self::deserialize_10x(input),
            0x74..=0x78 => Self::deserialize_3rc(input),
            0x79..=0x7a => Self::deserialize_10x(input),
            0x7b..=0x8f => Self::deserialize_12x(input),
            0x90..=0xaf => Self::deserialize_23x(input),
            0xb0..=0xcf => Self::deserialize_12x(input),
            0xd0..=0xd7 => Self::deserialize_22s(input),
            0xd8..=0xe2 => Self::deserialize_22b(input),
            0xe3..=0xf9 => Self::deserialize_10x(input),
            0xfa => Self::deserialize_45cc(input),
            0xfb => Self::deserialize_4rcc(input),
            0xfc => Self::deserialize_35c(input),
            0xfd => Self::deserialize_3rc(input),
            0xfe => Self::deserialize_21c(input),
            0xff => Self::deserialize_21c(input),
        }
    }

    fn size(&self) -> usize {
        match self {
            Self::Format10X { .. } => 2,
            Self::Format12X { .. } => 2,
            Self::Format11N { .. } => 2,
            Self::Format11X { .. } => 2,
            Self::Format10T { .. } => 2,
            Self::Format20T { .. } => 4,
            Self::Format20BC { .. } => 4,
            Self::Format22X { .. } => 4,
            Self::Format21T { .. } => 4,
            Self::Format21S { .. } => 4,
            Self::Format21H { .. } => 4,
            Self::Format21C { .. } => 4,
            Self::Format23X { .. } => 4,
            Self::Format22B { .. } => 4,
            Self::Format22T { .. } => 4,
            Self::Format22S { .. } => 4,
            Self::Format22C { .. } => 4,
            Self::Format22CS { .. } => 4,
            Self::Format30T { .. } => 6,
            Self::Format32X { .. } => 6,
            Self::Format31I { .. } => 6,
            Self::Format31T { .. } => 6,
            Self::Format31C { .. } => 6,
            Self::Format35C { .. } => 6,
            Self::Format35MS { .. } => 6,
            Self::Format35MI { .. } => 6,
            Self::Format3RC { .. } => 6,
            Self::Format3RMS { .. } => 6,
            Self::Format3RMI { .. } => 6,
            Self::Format45CC { .. } => 8,
            Self::Format4RCC { .. } => 8,
            Self::Format51L { .. } => 10,
            Self::FormatPackedSwitchPayload { targets, .. } => 2 + 2 + 4 + targets.len() * 4,
            Self::FormatSparseSwitchPayload { key_targets } => 2 + 2 + key_targets.len() * 8,
            Self::FormatFillArrayDataPayload { data, .. } => {
                2 + 2 + 4 + data.len() + (data.len() % 2)
            }
        }
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use std::io::Cursor;

    const INSN_RAW_1: &[u8] = &[
        0x70, 0x10, 0x8b, 0x84, 0x05, 0x00, 0x22, 0x00, 0x2d, 0x24, 0x70, 0x10, 0x51, 0x87, 0x00,
        0x00, 0x5b, 0x50, 0x54, 0x06, 0x5b, 0x57, 0x58, 0x06, 0x22, 0x00, 0x84, 0x05, 0x70, 0x10,
        0x09, 0x1c, 0x00, 0x00, 0x5b, 0x50, 0x56, 0x06, 0x54, 0x77, 0xcc, 0x09, 0x71, 0x20, 0xed,
        0x18, 0x76, 0x00, 0x0c, 0x07, 0x5b, 0x57, 0x55, 0x06, 0x71, 0x10, 0xb9, 0x17, 0x06, 0x00,
        0x0c, 0x06, 0x5b, 0x56, 0x57, 0x06, 0x22, 0x06, 0x1e, 0x24, 0x70, 0x10, 0xa1, 0x86, 0x06,
        0x00, 0x54, 0x70, 0x01, 0x08, 0x1f, 0x00, 0x7a, 0x04, 0x6e, 0x10, 0x8e, 0x84, 0x00, 0x00,
        0x54, 0x00, 0x04, 0x08, 0x6e, 0x10, 0x2a, 0x04, 0x00, 0x00, 0x0c, 0x00, 0x71, 0x10, 0xbc,
        0x86, 0x00, 0x00, 0x0c, 0x00, 0x39, 0x08, 0x16, 0x00, 0x72, 0x10, 0x8d, 0x87, 0x00, 0x00,
        0x0c, 0x07, 0x72, 0x10, 0x6c, 0x87, 0x07, 0x00, 0x0a, 0x08, 0x38, 0x08, 0x58, 0x00, 0x72,
        0x10, 0x6d, 0x87, 0x07, 0x00, 0x0c, 0x08, 0x1f, 0x08, 0x87, 0x23, 0x6e, 0x20, 0xa5, 0x86,
        0x86, 0x00, 0x28, 0xf1, 0x6e, 0x10, 0xf3, 0x19, 0x08, 0x00, 0x0c, 0x01, 0x71, 0x30, 0x73,
        0x17, 0x17, 0x00, 0x0c, 0x07, 0x28, 0x02, 0x12, 0x07, 0x22, 0x01, 0x1e, 0x24, 0x70, 0x10,
        0xa1, 0x86, 0x01, 0x00, 0x72, 0x10, 0x8d, 0x87, 0x00, 0x00, 0x0c, 0x00, 0x72, 0x10, 0x6c,
        0x87, 0x00, 0x00, 0x0a, 0x02, 0x38, 0x02, 0x17, 0x00, 0x72, 0x10, 0x6d, 0x87, 0x00, 0x00,
        0x0c, 0x02, 0x1f, 0x02, 0x87, 0x23, 0x6e, 0x20, 0xb5, 0x84, 0x72, 0x00, 0x0a, 0x03, 0x38,
        0x03, 0x03, 0x00, 0x28, 0xee, 0x6e, 0x20, 0x8f, 0x16, 0x25, 0x00, 0x0c, 0x02, 0x6e, 0x20,
        0xa5, 0x86, 0x21, 0x00, 0x28, 0xe6, 0x6e, 0x20, 0xf2, 0x19, 0x18, 0x00, 0x0c, 0x07, 0x72,
        0x10, 0x8d, 0x87, 0x07, 0x00, 0x0c, 0x07, 0x72, 0x10, 0x6c, 0x87, 0x07, 0x00, 0x0a, 0x08,
        0x38, 0x08, 0x12, 0x00, 0x72, 0x10, 0x6d, 0x87, 0x07, 0x00, 0x0c, 0x08, 0x1f, 0x08, 0xd4,
        0x04, 0x1f, 0x08, 0x7d, 0x05, 0x72, 0x10, 0xf3, 0x1b, 0x08, 0x00, 0x0c, 0x08, 0x6e, 0x20,
        0xa5, 0x86, 0x86, 0x00, 0x28, 0xeb, 0x22, 0x07, 0x1e, 0x24, 0x70, 0x10, 0xa1, 0x86, 0x07,
        0x00, 0x6e, 0x10, 0xaf, 0x86, 0x06, 0x00, 0x0c, 0x06, 0x72, 0x10, 0x6c, 0x87, 0x06, 0x00,
        0x0a, 0x08, 0x38, 0x08, 0x57, 0x00, 0x72, 0x10, 0x6d, 0x87, 0x06, 0x00, 0x0c, 0x08, 0x1f,
        0x08, 0x87, 0x23, 0x1a, 0x00, 0xb5, 0x0d, 0x6e, 0x20, 0xb5, 0x84, 0x08, 0x00, 0x0a, 0x00,
        0x39, 0x00, 0x45, 0x00, 0x1a, 0x00, 0xd0, 0x0d, 0x6e, 0x20, 0xb5, 0x84, 0x08, 0x00, 0x0a,
        0x00, 0x38, 0x00, 0x03, 0x00, 0x28, 0x3b, 0x62, 0x00, 0xe2, 0x00, 0x1a, 0x01, 0x49, 0xc0,
        0x6e, 0x20, 0xb5, 0x84, 0x01, 0x00, 0x0a, 0x00, 0x38, 0x00, 0x03, 0x00, 0x28, 0x1a, 0x54,
        0x50, 0x55, 0x06, 0x6e, 0x20, 0xee, 0x18, 0x80, 0x00, 0x0c, 0x00, 0x62, 0x01, 0xb6, 0x00,
        0x6e, 0x20, 0xc4, 0x18, 0x10, 0x00, 0x0c, 0x00, 0x1f, 0x00, 0x9c, 0x2e, 0x12, 0x01, 0x38,
        0x00, 0x0f, 0x00, 0x21, 0x02, 0x01, 0x13, 0x35, 0x23, 0x0b, 0x00, 0x44, 0x04, 0x00, 0x03,
        0x39, 0x04, 0x04, 0x00, 0x12, 0x11, 0x28, 0x04, 0xd8, 0x03, 0x03, 0x01, 0x28, 0xf6, 0x38,
        0x01, 0xba, 0xff, 0x6e, 0x20, 0xa5, 0x86, 0x87, 0x00, 0x28, 0xb5, 0x0d, 0x06, 0x22, 0x07,
        0x1e, 0x05, 0x71, 0x10, 0xff, 0x48, 0x06, 0x00, 0x0c, 0x06, 0x70, 0x20, 0xc2, 0x1a, 0x67,
        0x00, 0x27, 0x07, 0x6e, 0x20, 0xa5, 0x86, 0x87, 0x00, 0x28, 0xa6, 0x5b, 0x57, 0x53, 0x06,
        0x0e, 0x00, 0x0d, 0x06, 0x22, 0x07, 0x47, 0x04, 0x70, 0x20, 0x84, 0x18, 0x67, 0x00, 0x27,
        0x07, 0x0d, 0x06, 0x22, 0x07, 0x1e, 0x05, 0x70, 0x20, 0xc2, 0x1a, 0x67, 0x00, 0x27, 0x07,
        0x0d, 0x06, 0x22, 0x07, 0x1e, 0x05, 0x71, 0x10, 0xff, 0x48, 0x06, 0x00, 0x0c, 0x06, 0x70,
        0x20, 0xc2, 0x1a, 0x67, 0x00, 0x27, 0x07,
    ];
    const INSN_VAL_1: &[Instruction] = &[
        Instruction::Format35C {
            a: 1,
            b: 33931,
            vc: 5,
            vd: 0,
            ve: 0,
            vf: 0,
            vg: 0,
            op: 0x70,
        },
        Instruction::Format21C {
            va: 0,
            b: 9261,
            op: 0x22,
        },
        Instruction::Format35C {
            a: 1,
            b: 34641,
            vc: 0,
            vd: 0,
            ve: 0,
            vf: 0,
            vg: 0,
            op: 0x70,
        },
        Instruction::Format22C {
            va: 0,
            vb: 5,
            c: 1620,
            op: 0x5b,
        },
        Instruction::Format22C {
            va: 7,
            vb: 5,
            c: 1624,
            op: 0x5b,
        },
        Instruction::Format21C {
            va: 0,
            b: 1412,
            op: 0x22,
        },
        Instruction::Format35C {
            a: 1,
            b: 7177,
            vc: 0,
            vd: 0,
            ve: 0,
            vf: 0,
            vg: 0,
            op: 0x70,
        },
        Instruction::Format22C {
            va: 0,
            vb: 5,
            c: 1622,
            op: 0x5b,
        },
        Instruction::Format22C {
            va: 7,
            vb: 7,
            c: 2508,
            op: 0x54,
        },
        Instruction::Format35C {
            a: 2,
            b: 6381,
            vc: 6,
            vd: 7,
            ve: 0,
            vf: 0,
            vg: 0,
            op: 0x71,
        },
        Instruction::Format11X { va: 7, op: 0xc },
        Instruction::Format22C {
            va: 7,
            vb: 5,
            c: 1621,
            op: 0x5b,
        },
        Instruction::Format35C {
            a: 1,
            b: 6073,
            vc: 6,
            vd: 0,
            ve: 0,
            vf: 0,
            vg: 0,
            op: 0x71,
        },
        Instruction::Format11X { va: 6, op: 0xc },
        Instruction::Format22C {
            va: 6,
            vb: 5,
            c: 1623,
            op: 0x5b,
        },
        Instruction::Format21C {
            va: 6,
            b: 9246,
            op: 0x22,
        },
        Instruction::Format35C {
            a: 1,
            b: 34465,
            vc: 6,
            vd: 0,
            ve: 0,
            vf: 0,
            vg: 0,
            op: 0x70,
        },
        Instruction::Format22C {
            va: 0,
            vb: 7,
            c: 2049,
            op: 0x54,
        },
        Instruction::Format21C {
            va: 0,
            b: 1146,
            op: 0x1f,
        },
        Instruction::Format35C {
            a: 1,
            b: 33934,
            vc: 0,
            vd: 0,
            ve: 0,
            vf: 0,
            vg: 0,
            op: 0x6e,
        },
        Instruction::Format22C {
            va: 0,
            vb: 0,
            c: 2052,
            op: 0x54,
        },
        Instruction::Format35C {
            a: 1,
            b: 1066,
            vc: 0,
            vd: 0,
            ve: 0,
            vf: 0,
            vg: 0,
            op: 0x6e,
        },
        Instruction::Format11X { va: 0, op: 0xc },
        Instruction::Format35C {
            a: 1,
            b: 34492,
            vc: 0,
            vd: 0,
            ve: 0,
            vf: 0,
            vg: 0,
            op: 0x71,
        },
        Instruction::Format11X { va: 0, op: 0xc },
        Instruction::Format21T {
            va: 8,
            b: 22,
            op: 0x39,
        },
        Instruction::Format35C {
            a: 1,
            b: 34701,
            vc: 0,
            vd: 0,
            ve: 0,
            vf: 0,
            vg: 0,
            op: 0x72,
        },
        Instruction::Format11X { va: 7, op: 0xc },
        Instruction::Format35C {
            a: 1,
            b: 34668,
            vc: 7,
            vd: 0,
            ve: 0,
            vf: 0,
            vg: 0,
            op: 0x72,
        },
        Instruction::Format11X { va: 8, op: 0xa },
        Instruction::Format21T {
            va: 8,
            b: 88,
            op: 0x38,
        },
        Instruction::Format35C {
            a: 1,
            b: 34669,
            vc: 7,
            vd: 0,
            ve: 0,
            vf: 0,
            vg: 0,
            op: 0x72,
        },
        Instruction::Format11X { va: 8, op: 0xc },
        Instruction::Format21C {
            va: 8,
            b: 9095,
            op: 0x1f,
        },
        Instruction::Format35C {
            a: 2,
            b: 34469,
            vc: 6,
            vd: 8,
            ve: 0,
            vf: 0,
            vg: 0,
            op: 0x6e,
        },
        Instruction::Format10T { a: -15, op: 0x28 },
        Instruction::Format35C {
            a: 1,
            b: 6643,
            vc: 8,
            vd: 0,
            ve: 0,
            vf: 0,
            vg: 0,
            op: 0x6e,
        },
        Instruction::Format11X { va: 1, op: 0xc },
        Instruction::Format35C {
            a: 3,
            b: 6003,
            vc: 7,
            vd: 1,
            ve: 0,
            vf: 0,
            vg: 0,
            op: 0x71,
        },
        Instruction::Format11X { va: 7, op: 0xc },
        Instruction::Format10T { a: 2, op: 0x28 },
        Instruction::Format11N {
            va: 7,
            b: 0,
            op: 0x12,
        },
        Instruction::Format21C {
            va: 1,
            b: 9246,
            op: 0x22,
        },
        Instruction::Format35C {
            a: 1,
            b: 34465,
            vc: 1,
            vd: 0,
            ve: 0,
            vf: 0,
            vg: 0,
            op: 0x70,
        },
        Instruction::Format35C {
            a: 1,
            b: 34701,
            vc: 0,
            vd: 0,
            ve: 0,
            vf: 0,
            vg: 0,
            op: 0x72,
        },
        Instruction::Format11X { va: 0, op: 0xc },
        Instruction::Format35C {
            a: 1,
            b: 34668,
            vc: 0,
            vd: 0,
            ve: 0,
            vf: 0,
            vg: 0,
            op: 0x72,
        },
        Instruction::Format11X { va: 2, op: 0xa },
        Instruction::Format21T {
            va: 2,
            b: 23,
            op: 0x38,
        },
        Instruction::Format35C {
            a: 1,
            b: 34669,
            vc: 0,
            vd: 0,
            ve: 0,
            vf: 0,
            vg: 0,
            op: 0x72,
        },
        Instruction::Format11X { va: 2, op: 0xc },
        Instruction::Format21C {
            va: 2,
            b: 9095,
            op: 0x1f,
        },
        Instruction::Format35C {
            a: 2,
            b: 33973,
            vc: 2,
            vd: 7,
            ve: 0,
            vf: 0,
            vg: 0,
            op: 0x6e,
        },
        Instruction::Format11X { va: 3, op: 0xa },
        Instruction::Format21T {
            va: 3,
            b: 3,
            op: 0x38,
        },
        Instruction::Format10T { a: -18, op: 0x28 },
        Instruction::Format35C {
            a: 2,
            b: 5775,
            vc: 5,
            vd: 2,
            ve: 0,
            vf: 0,
            vg: 0,
            op: 0x6e,
        },
        Instruction::Format11X { va: 2, op: 0xc },
        Instruction::Format35C {
            a: 2,
            b: 34469,
            vc: 1,
            vd: 2,
            ve: 0,
            vf: 0,
            vg: 0,
            op: 0x6e,
        },
        Instruction::Format10T { a: -26, op: 0x28 },
        Instruction::Format35C {
            a: 2,
            b: 6642,
            vc: 8,
            vd: 1,
            ve: 0,
            vf: 0,
            vg: 0,
            op: 0x6e,
        },
        Instruction::Format11X { va: 7, op: 0xc },
        Instruction::Format35C {
            a: 1,
            b: 34701,
            vc: 7,
            vd: 0,
            ve: 0,
            vf: 0,
            vg: 0,
            op: 0x72,
        },
        Instruction::Format11X { va: 7, op: 0xc },
        Instruction::Format35C {
            a: 1,
            b: 34668,
            vc: 7,
            vd: 0,
            ve: 0,
            vf: 0,
            vg: 0,
            op: 0x72,
        },
        Instruction::Format11X { va: 8, op: 0xa },
        Instruction::Format21T {
            va: 8,
            b: 18,
            op: 0x38,
        },
        Instruction::Format35C {
            a: 1,
            b: 34669,
            vc: 7,
            vd: 0,
            ve: 0,
            vf: 0,
            vg: 0,
            op: 0x72,
        },
        Instruction::Format11X { va: 8, op: 0xc },
        Instruction::Format21C {
            va: 8,
            b: 1236,
            op: 0x1f,
        },
        Instruction::Format21C {
            va: 8,
            b: 1405,
            op: 0x1f,
        },
        Instruction::Format35C {
            a: 1,
            b: 7155,
            vc: 8,
            vd: 0,
            ve: 0,
            vf: 0,
            vg: 0,
            op: 0x72,
        },
        Instruction::Format11X { va: 8, op: 0xc },
        Instruction::Format35C {
            a: 2,
            b: 34469,
            vc: 6,
            vd: 8,
            ve: 0,
            vf: 0,
            vg: 0,
            op: 0x6e,
        },
        Instruction::Format10T { a: -21, op: 0x28 },
        Instruction::Format21C {
            va: 7,
            b: 9246,
            op: 0x22,
        },
        Instruction::Format35C {
            a: 1,
            b: 34465,
            vc: 7,
            vd: 0,
            ve: 0,
            vf: 0,
            vg: 0,
            op: 0x70,
        },
        Instruction::Format35C {
            a: 1,
            b: 34479,
            vc: 6,
            vd: 0,
            ve: 0,
            vf: 0,
            vg: 0,
            op: 0x6e,
        },
        Instruction::Format11X { va: 6, op: 0xc },
        Instruction::Format35C {
            a: 1,
            b: 34668,
            vc: 6,
            vd: 0,
            ve: 0,
            vf: 0,
            vg: 0,
            op: 0x72,
        },
        Instruction::Format11X { va: 8, op: 0xa },
        Instruction::Format21T {
            va: 8,
            b: 87,
            op: 0x38,
        },
        Instruction::Format35C {
            a: 1,
            b: 34669,
            vc: 6,
            vd: 0,
            ve: 0,
            vf: 0,
            vg: 0,
            op: 0x72,
        },
        Instruction::Format11X { va: 8, op: 0xc },
        Instruction::Format21C {
            va: 8,
            b: 9095,
            op: 0x1f,
        },
        Instruction::Format21C {
            va: 0,
            b: 3509,
            op: 0x1a,
        },
        Instruction::Format35C {
            a: 2,
            b: 33973,
            vc: 8,
            vd: 0,
            ve: 0,
            vf: 0,
            vg: 0,
            op: 0x6e,
        },
        Instruction::Format11X { va: 0, op: 0xa },
        Instruction::Format21T {
            va: 0,
            b: 69,
            op: 0x39,
        },
        Instruction::Format21C {
            va: 0,
            b: 3536,
            op: 0x1a,
        },
        Instruction::Format35C {
            a: 2,
            b: 33973,
            vc: 8,
            vd: 0,
            ve: 0,
            vf: 0,
            vg: 0,
            op: 0x6e,
        },
        Instruction::Format11X { va: 0, op: 0xa },
        Instruction::Format21T {
            va: 0,
            b: 3,
            op: 0x38,
        },
        Instruction::Format10T { a: 59, op: 0x28 },
        Instruction::Format21C {
            va: 0,
            b: 226,
            op: 0x62,
        },
        Instruction::Format21C {
            va: 1,
            b: 49225,
            op: 0x1a,
        },
        Instruction::Format35C {
            a: 2,
            b: 33973,
            vc: 1,
            vd: 0,
            ve: 0,
            vf: 0,
            vg: 0,
            op: 0x6e,
        },
        Instruction::Format11X { va: 0, op: 0xa },
        Instruction::Format21T {
            va: 0,
            b: 3,
            op: 0x38,
        },
        Instruction::Format10T { a: 26, op: 0x28 },
        Instruction::Format22C {
            va: 0,
            vb: 5,
            c: 1621,
            op: 0x54,
        },
        Instruction::Format35C {
            a: 2,
            b: 6382,
            vc: 0,
            vd: 8,
            ve: 0,
            vf: 0,
            vg: 0,
            op: 0x6e,
        },
        Instruction::Format11X { va: 0, op: 0xc },
        Instruction::Format21C {
            va: 1,
            b: 182,
            op: 0x62,
        },
        Instruction::Format35C {
            a: 2,
            b: 6340,
            vc: 0,
            vd: 1,
            ve: 0,
            vf: 0,
            vg: 0,
            op: 0x6e,
        },
        Instruction::Format11X { va: 0, op: 0xc },
        Instruction::Format21C {
            va: 0,
            b: 11932,
            op: 0x1f,
        },
        Instruction::Format11N {
            va: 1,
            b: 0,
            op: 0x12,
        },
        Instruction::Format21T {
            va: 0,
            b: 15,
            op: 0x38,
        },
        Instruction::Format12X {
            va: 2,
            vb: 0,
            op: 0x21,
        },
        Instruction::Format12X {
            va: 3,
            vb: 1,
            op: 0x1,
        },
        Instruction::Format22T {
            va: 3,
            vb: 2,
            c: 11,
            op: 0x35,
        },
        Instruction::Format23X {
            va: 4,
            vb: 0,
            vc: 3,
            op: 0x44,
        },
        Instruction::Format21T {
            va: 4,
            b: 4,
            op: 0x39,
        },
        Instruction::Format11N {
            va: 1,
            b: 1,
            op: 0x12,
        },
        Instruction::Format10T { a: 4, op: 0x28 },
        Instruction::Format22B {
            va: 3,
            vb: 3,
            c: 1,
            op: 0xd8,
        },
        Instruction::Format10T { a: -10, op: 0x28 },
        Instruction::Format21T {
            va: 1,
            b: -70,
            op: 0x38,
        },
        Instruction::Format35C {
            a: 2,
            b: 34469,
            vc: 7,
            vd: 8,
            ve: 0,
            vf: 0,
            vg: 0,
            op: 0x6e,
        },
        Instruction::Format10T { a: -75, op: 0x28 },
        Instruction::Format11X { va: 6, op: 0xd },
        Instruction::Format21C {
            va: 7,
            b: 1310,
            op: 0x22,
        },
        Instruction::Format35C {
            a: 1,
            b: 18687,
            vc: 6,
            vd: 0,
            ve: 0,
            vf: 0,
            vg: 0,
            op: 0x71,
        },
        Instruction::Format11X { va: 6, op: 0xc },
        Instruction::Format35C {
            a: 2,
            b: 6850,
            vc: 7,
            vd: 6,
            ve: 0,
            vf: 0,
            vg: 0,
            op: 0x70,
        },
        Instruction::Format11X { va: 7, op: 0x27 },
        Instruction::Format35C {
            a: 2,
            b: 34469,
            vc: 7,
            vd: 8,
            ve: 0,
            vf: 0,
            vg: 0,
            op: 0x6e,
        },
        Instruction::Format10T { a: -90, op: 0x28 },
        Instruction::Format22C {
            va: 7,
            vb: 5,
            c: 1619,
            op: 0x5b,
        },
        Instruction::Format10X { op: 0xe },
        Instruction::Format11X { va: 6, op: 0xd },
        Instruction::Format21C {
            va: 7,
            b: 1095,
            op: 0x22,
        },
        Instruction::Format35C {
            a: 2,
            b: 6276,
            vc: 7,
            vd: 6,
            ve: 0,
            vf: 0,
            vg: 0,
            op: 0x70,
        },
        Instruction::Format11X { va: 7, op: 0x27 },
        Instruction::Format11X { va: 6, op: 0xd },
        Instruction::Format21C {
            va: 7,
            b: 1310,
            op: 0x22,
        },
        Instruction::Format35C {
            a: 2,
            b: 6850,
            vc: 7,
            vd: 6,
            ve: 0,
            vf: 0,
            vg: 0,
            op: 0x70,
        },
        Instruction::Format11X { va: 7, op: 0x27 },
        Instruction::Format11X { va: 6, op: 0xd },
        Instruction::Format21C {
            va: 7,
            b: 1310,
            op: 0x22,
        },
        Instruction::Format35C {
            a: 1,
            b: 18687,
            vc: 6,
            vd: 0,
            ve: 0,
            vf: 0,
            vg: 0,
            op: 0x71,
        },
        Instruction::Format11X { va: 6, op: 0xc },
        Instruction::Format35C {
            a: 2,
            b: 6850,
            vc: 7,
            vd: 6,
            ve: 0,
            vf: 0,
            vg: 0,
            op: 0x70,
        },
        Instruction::Format11X { va: 7, op: 0x27 },
    ];

    #[test]
    fn test_insn_parsing() {
        // TODO: comute n on the fly
        let n = INSN_VAL_1.len();
        let mut instructions = vec![];
        let mut buffer = Cursor::new(INSN_RAW_1);
        for _ in 0..n {
            instructions.push(Instruction::deserialize(&mut buffer).unwrap());
        }
        assert_eq!(instructions, INSN_VAL_1);
        let mut output = Cursor::new(Vec::<u8>::new());
        for ins in INSN_VAL_1 {
            ins.serialize(&mut output).unwrap();
        }
        assert_eq!(output.into_inner(), INSN_RAW_1);
        assert_eq!(
            INSN_RAW_1.len(),
            INSN_VAL_1.iter().map(|ins| ins.size()).sum()
        );
    }

    #[test]
    fn test_parsing_35c() {
        for (raw, expected) in [
            (
                vec![0x70, 0x10, 0x8b, 0x84, 0x05, 0x00],
                Instruction::Format35C {
                    a: 1,
                    vg: 0,
                    op: 0x70,
                    b: 0x848b,
                    vf: 0,
                    ve: 0,
                    vd: 0,
                    vc: 5,
                },
            ),
            (
                vec![0x70, 0x10, 0x51, 0x87, 0x00, 0x00],
                Instruction::Format35C {
                    a: 1,
                    vg: 0,
                    op: 0x70,
                    b: 0x8751,
                    vf: 0,
                    ve: 0,
                    vd: 0,
                    vc: 0,
                },
            ),
            (
                vec![0x70, 0x10, 0x02, 0x00, 0x02, 0x00],
                Instruction::Format35C {
                    a: 1,
                    vg: 0,
                    op: 112,
                    b: 2,
                    vf: 0,
                    ve: 0,
                    vd: 0,
                    vc: 2,
                },
            ),
        ] {
            assert_eq!(raw, expected.serialize_to_vec().unwrap());
            assert_eq!(expected, Instruction::deserialize_from_slice(&raw).unwrap());
            assert_eq!(expected.size(), raw.len());
        }
    }

    #[test]
    fn test_parsing_51l() {
        for (raw, expected) in [
            (
                vec![0x18, 0x0c, 0x18, 0x2d, 0x44, 0x54, 0xfb, 0x21, 0xf9, 0x3f],
                Instruction::Format51L {
                    va: 12,
                    b: 0x3ff921fb54442d18,
                    op: 0x18,
                },
            ),
            (
                vec![0x18, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00],
                Instruction::Format51L {
                    va: 13,
                    b: 0x200000000,
                    op: 0x18,
                },
            ),
            (
                vec![0x18, 0x05, 0x80, 0x3f, 0xe0, 0x0f, 0xf8, 0x03, 0xfe, 0xff],
                Instruction::Format51L {
                    va: 5,
                    b: -558586000294016,
                    op: 0x18,
                },
            ),
        ] {
            assert_eq!(expected.size(), raw.len());
            assert_eq!(expected, Instruction::deserialize_from_slice(&raw).unwrap());
            assert_eq!(raw, expected.serialize_to_vec().unwrap());
        }
    }

    #[test]
    fn test_parsing_20t() {
        for (raw, expected) in [
            (
                vec![0x29, 0x00, 0xd4, 0xfe],
                Instruction::Format20T { a: -300, op: 0x29 },
            ),
            (
                vec![0x29, 0x00, 0x16, 0x01],
                Instruction::Format20T { a: 278, op: 0x29 },
            ),
        ] {
            assert_eq!(expected.size(), raw.len());
            assert_eq!(expected, Instruction::deserialize_from_slice(&raw).unwrap());
            assert_eq!(raw, expected.serialize_to_vec().unwrap());
        }
    }

    #[test]
    fn test_parsing_22x() {
        for (raw, expected) in [
            (
                vec![0x08, 0x0b, 0x11, 0x00],
                Instruction::Format22X {
                    va: 11,
                    vb: 17,
                    op: 0x8,
                },
            ),
            (
                vec![0x08, 0x03, 0x11, 0x00],
                Instruction::Format22X {
                    va: 3,
                    vb: 17,
                    op: 0x8,
                },
            ),
        ] {
            assert_eq!(expected.size(), raw.len());
            assert_eq!(expected, Instruction::deserialize_from_slice(&raw).unwrap());
            assert_eq!(raw, expected.serialize_to_vec().unwrap());
        }
    }

    #[test]
    fn test_parsing_21h() {
        for (raw, expected) in [
            (
                vec![0x15, 0x0b, 0x10, 0x00],
                Instruction::Format21H {
                    va: 11,
                    b: (1048576 / 65536) as i16,
                    op: 0x15,
                },
            ),
            (
                vec![0x15, 0x06, 0x00, 0x80],
                Instruction::Format21H {
                    va: 6,
                    b: (-2147483648 / 65536) as i16,
                    op: 0x15,
                },
            ),
        ] {
            assert_eq!(expected.size(), raw.len());
            assert_eq!(expected, Instruction::deserialize_from_slice(&raw).unwrap());
            assert_eq!(raw, expected.serialize_to_vec().unwrap());
        }
    }

    #[test]
    fn test_parsing_21c() {
        let raw = vec![0x22, 0x00, 0x2d, 0x24];
        let expected = Instruction::Format21C {
            va: 0,
            op: 0x22,
            b: 0x242d,
        };
        assert_eq!(expected.size(), raw.len());
        assert_eq!(expected, Instruction::deserialize_from_slice(&raw).unwrap());
        assert_eq!(raw, expected.serialize_to_vec().unwrap());
    }

    #[test]
    fn test_parsing_22c() {
        let raw = vec![0x5b, 0x50, 0x54, 0x06];
        let expected = Instruction::Format22C {
            va: 0,
            op: 0x5b,
            vb: 5,
            c: 0x654,
        };
        assert_eq!(expected.size(), raw.len());
        assert_eq!(expected, Instruction::deserialize_from_slice(&raw).unwrap());
        assert_eq!(raw, expected.serialize_to_vec().unwrap());
    }
}
