#![allow(non_camel_case_types)]
#[cfg(feature = "serde_borsh")]
use borsh::{
    maybestd::io::Result as BorshResult, maybestd::io::Write as BorshWrite, BorshDeserialize,
    BorshSerialize,
};
use std::fmt;
use std::fmt::Debug;
pub type OpIndex = i32;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum Opcode {
    VOID,
    DUPLICATE,
    LOAD_SLICE,
    STORE_SLICE,
    LOAD_ARRAY,
    STORE_ARRAY,
    LOAD_MAP,
    STORE_MAP,
    LOAD_STRUCT,
    STORE_STRUCT,
    LOAD_EMBEDDED,
    STORE_EMBEDDED,
    LOAD_PKG,
    STORE_PKG,
    LOAD_POINTER,
    STORE_POINTER,
    LOAD_UP_VALUE,
    STORE_UP_VALUE,
    ADD,            SUB,            MUL,            QUO,            REM,            AND,            OR,             XOR,            AND_NOT,        SHL,            SHR,            ADD_ASSIGN,     SUB_ASSIGN,     MUL_ASSIGN,     QUO_ASSIGN,     REM_ASSIGN,     AND_ASSIGN,     OR_ASSIGN,      XOR_ASSIGN,     AND_NOT_ASSIGN, SHL_ASSIGN,     SHR_ASSIGN,     INC,            DEC,            UNARY_SUB,      UNARY_XOR,      NOT,            EQL,            NEQ,            LSS,            GTR,            LEQ,            GEQ,            REF,            REF_UPVALUE,
    REF_SLICE_MEMBER,
    REF_STRUCT_FIELD,
    REF_EMBEDDED,
    REF_PKG_MEMBER,
    SEND, RECV, PACK_VARIADIC,
    CALL,
    RETURN,
    JUMP,
    JUMP_IF,
    JUMP_IF_NOT,
    SWITCH,
    SELECT,
    RANGE_INIT,
    RANGE,
    LOAD_INIT_FUNC,
    BIND_METHOD,
    BIND_I_METHOD,
    CAST,
    TYPE_ASSERT,
    TYPE,
    IMPORT,  SLICE,   CLOSURE, LITERAL, NEW,     MAKE,    COMPLEX, REAL,    IMAG,    LEN,     CAP,     APPEND,  COPY,    DELETE,  CLOSE,   PANIC,   RECOVER, ASSERT,  FFI,     }
impl fmt::Display for Opcode {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        fmt::Debug::fmt(&self, f)
    }
}
#[cfg(feature = "serde_borsh")]
impl BorshSerialize for Opcode {
    #[inline]
    fn serialize<W: BorshWrite>(&self, writer: &mut W) -> BorshResult<()> {
        let val: u8 = unsafe { std::mem::transmute(*self) };
        val.serialize(writer)
    }
}
#[cfg(feature = "serde_borsh")]
impl BorshDeserialize for Opcode {
    #[inline]
    fn deserialize_reader<R: std::io::Read>(reader: &mut R) -> BorshResult<Self> {
        let val = u8::deserialize_reader(reader)?;
        Ok(unsafe { std::mem::transmute(val) })
    }
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Ord, PartialOrd)]
#[repr(u8)]
pub enum ValueType {
    Void,
    Bool,
    Int,
    Int8,
    Int16,
    Int32,
    Int64,
    Uint,
    UintPtr,
    Uint8,
    Uint16,
    Uint32,
    Uint64,
    Float32,
    Float64,
    Complex64,
    Function,
    Package, Metadata,
    Complex128,
    String,
    Array,
    Struct,
    Pointer, UnsafePtr,
    Closure,
    Slice,
    Map,
    Interface,
    Channel,
    FlagA, FlagB,
    FlagC,
    FlagD,
    FlagE,
}
impl ValueType {
    #[inline]
    pub fn copyable(&self) -> bool {
        self <= &Self::Package
    }
    #[inline]
    pub fn comparable(&self) -> bool {
        self <= &Self::Pointer
    }
    #[inline]
    pub fn nilable(&self) -> bool {
        self >= &Self::Pointer && self <= &Self::Channel
    }
}
#[cfg(feature = "serde_borsh")]
impl BorshSerialize for ValueType {
    #[inline]
    fn serialize<W: BorshWrite>(&self, writer: &mut W) -> BorshResult<()> {
        let val: u8 = unsafe { std::mem::transmute(*self) };
        val.serialize(writer)
    }
}
#[cfg(feature = "serde_borsh")]
impl BorshDeserialize for ValueType {
    #[inline]
    fn deserialize_reader<R: std::io::Read>(reader: &mut R) -> BorshResult<Self> {
        let val = u8::deserialize_reader(reader)?;
        Ok(unsafe { std::mem::transmute(val) })
    }
}
impl fmt::Display for ValueType {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        fmt::Debug::fmt(&self, f)
    }
}
#[derive(Clone, Copy)]
pub struct Instruction {
    pub op0: Opcode,
    pub op1: Opcode,
    pub t0: ValueType,
    pub t1: ValueType,
    pub d: OpIndex,
    pub s0: OpIndex,
    pub s1: OpIndex,
}
impl Instruction {
    pub fn op1_as_t(&self) -> ValueType {
        unsafe { std::mem::transmute(self.op1) }
    }
    pub fn max_write_index(instructions: &[Instruction]) -> OpIndex {
        let mut i = 0;
        let mut result = 0;
        loop {
            let cur = &instructions[i];
            let index = match cur.op0 {
                Opcode::VOID => 0,
                Opcode::DUPLICATE => cur.d,
                Opcode::LOAD_SLICE => cur.d,
                Opcode::STORE_SLICE => 0,
                Opcode::LOAD_ARRAY => cur.d,
                Opcode::STORE_ARRAY => 0,
                Opcode::LOAD_MAP => {
                    i += 1;
                    match cur.t1 {
                        ValueType::FlagB => instructions[i].d,
                        _ => cur.d,
                    }
                }
                Opcode::STORE_MAP => {
                    i += 1;
                    0
                }
                Opcode::LOAD_STRUCT => cur.d,
                Opcode::STORE_STRUCT => 0,
                Opcode::LOAD_EMBEDDED => cur.d,
                Opcode::STORE_EMBEDDED => 0,
                Opcode::LOAD_PKG => cur.d,
                Opcode::STORE_PKG => 0,
                Opcode::LOAD_POINTER => cur.d,
                Opcode::STORE_POINTER => 0,
                Opcode::LOAD_UP_VALUE => cur.d,
                Opcode::STORE_UP_VALUE => 0,
                Opcode::ADD => cur.d,
                Opcode::SUB => cur.d,
                Opcode::MUL => cur.d,
                Opcode::QUO => cur.d,
                Opcode::REM => cur.d,
                Opcode::AND => cur.d,
                Opcode::OR => cur.d,
                Opcode::XOR => cur.d,
                Opcode::AND_NOT => cur.d,
                Opcode::SHL => cur.d,
                Opcode::SHR => cur.d,
                Opcode::ADD_ASSIGN => 0,
                Opcode::SUB_ASSIGN => 0,
                Opcode::MUL_ASSIGN => 0,
                Opcode::QUO_ASSIGN => 0,
                Opcode::REM_ASSIGN => 0,
                Opcode::AND_ASSIGN => 0,
                Opcode::OR_ASSIGN => 0,
                Opcode::XOR_ASSIGN => 0,
                Opcode::AND_NOT_ASSIGN => 0,
                Opcode::SHL_ASSIGN => 0,
                Opcode::SHR_ASSIGN => 0,
                Opcode::INC => 0,
                Opcode::DEC => 0,
                Opcode::UNARY_SUB => cur.d,
                Opcode::UNARY_XOR => cur.d,
                Opcode::NOT => cur.d,
                Opcode::EQL => cur.d,
                Opcode::NEQ => cur.d,
                Opcode::LSS => cur.d,
                Opcode::GTR => cur.d,
                Opcode::LEQ => cur.d,
                Opcode::GEQ => cur.d,
                Opcode::REF => cur.d,
                Opcode::REF_UPVALUE => cur.d,
                Opcode::REF_SLICE_MEMBER => cur.d,
                Opcode::REF_STRUCT_FIELD => cur.d,
                Opcode::REF_EMBEDDED => cur.d,
                Opcode::REF_PKG_MEMBER => cur.d,
                Opcode::SEND => 0,
                Opcode::RECV => match cur.t1 {
                    ValueType::FlagB => std::cmp::max(cur.d, cur.s1),
                    _ => cur.d,
                },
                Opcode::PACK_VARIADIC => cur.d,
                Opcode::CALL => 0,
                Opcode::RETURN => 0,
                Opcode::JUMP => 0,
                Opcode::JUMP_IF => 0,
                Opcode::JUMP_IF_NOT => 0,
                Opcode::SWITCH => 0,
                Opcode::SELECT => {
                    let begin = i + 1;
                    i += cur.s0 as usize;
                    instructions[begin..i].iter().fold(0, |acc, x| {
                        let val = match x.t0 {
                            ValueType::FlagC => x.s1,
                            ValueType::FlagD => x.s1 + 1,
                            _ => 0,
                        };
                        std::cmp::max(acc, val)
                    })
                }
                Opcode::RANGE_INIT => 0,
                Opcode::RANGE => 0,
                Opcode::LOAD_INIT_FUNC => {
                    i += 2;
                    std::cmp::max(cur.d, cur.s1)
                }
                Opcode::BIND_METHOD => cur.d,
                Opcode::BIND_I_METHOD => cur.d,
                Opcode::CAST => cur.d,
                Opcode::TYPE_ASSERT => match cur.t1 {
                    ValueType::FlagB => {
                        i += 1;
                        instructions[i].d
                    }
                    _ => cur.d,
                },
                Opcode::TYPE => match cur.t0 {
                    ValueType::FlagA => std::cmp::max(cur.d, cur.s1),
                    _ => cur.d,
                },
                Opcode::IMPORT => 0,
                Opcode::SLICE => {
                    i += 1;
                    cur.d
                }
                Opcode::CLOSURE => cur.d,
                Opcode::LITERAL => {
                    i += 1 + cur.s1 as usize;
                    cur.d
                }
                Opcode::NEW => cur.d,
                Opcode::MAKE => {
                    if cur.t0 == ValueType::FlagC {
                        i += 1;
                    }
                    cur.d
                }
                Opcode::COMPLEX => cur.d,
                Opcode::REAL => cur.d,
                Opcode::IMAG => cur.d,
                Opcode::LEN => cur.d,
                Opcode::CAP => cur.d,
                Opcode::APPEND => cur.d,
                Opcode::COPY => cur.d,
                Opcode::DELETE => 0,
                Opcode::CLOSE => 0,
                Opcode::PANIC => 0,
                Opcode::RECOVER => cur.d,
                Opcode::ASSERT => 0,
                Opcode::FFI => cur.d,
            };
            result = std::cmp::max(result, index);
            i += 1;
            if i >= instructions.len() {
                break;
            }
        }
        result
    }
}
#[cfg(feature = "serde_borsh")]
impl BorshSerialize for Instruction {
    fn serialize<W: BorshWrite>(&self, writer: &mut W) -> BorshResult<()> {
        self.op0.serialize(writer)?;
        self.op1.serialize(writer)?;
        self.t0.serialize(writer)?;
        self.t1.serialize(writer)?;
        self.d.serialize(writer)?;
        self.s0.serialize(writer)?;
        self.s1.serialize(writer)
    }
}
#[cfg(feature = "serde_borsh")]
impl BorshDeserialize for Instruction {
    fn deserialize_reader<R: std::io::Read>(reader: &mut R) -> BorshResult<Self> {
        const BYTE_COUNT: usize = 4;
        const OP_INDEX_SIZE: usize = core::mem::size_of::<OpIndex>();
        let data = <[u8; BYTE_COUNT + OP_INDEX_SIZE * 3]>::deserialize_reader(reader)?;
        let op0 = unsafe { std::mem::transmute(data[0]) };
        let op1 = unsafe { std::mem::transmute(data[1]) };
        let t0 = unsafe { std::mem::transmute(data[2]) };
        let t1 = unsafe { std::mem::transmute(data[3]) };
        let mut begin = BYTE_COUNT;
        let d = OpIndex::from_le_bytes(data[begin..begin + OP_INDEX_SIZE].try_into().unwrap());
        begin += OP_INDEX_SIZE;
        let s0 = OpIndex::from_le_bytes(data[begin..begin + OP_INDEX_SIZE].try_into().unwrap());
        begin += OP_INDEX_SIZE;
        let s1 = OpIndex::from_le_bytes(data[begin..begin + OP_INDEX_SIZE].try_into().unwrap());
        Ok(Instruction {
            op0,
            op1,
            t0,
            t1,
            d,
            s0,
            s1,
        })
    }
}
impl std::fmt::Debug for Instruction {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        let ops = if self.op1 == Opcode::VOID {
            self.op0.to_string()
        } else {
            format!("{}.{}", self.op0, self.op1)
        };
        write!(f, "{: <16}|", ops)?;
        fmt_index(self.d, f)?;
        f.write_str("\t|")?;
        fmt_index(self.s0, f)?;
        f.write_str("\t|")?;
        fmt_index(self.s1, f)?;
        f.write_str("\t|")?;
        fmt_type(self.t0, f)?;
        f.write_str("\t|")?;
        fmt_type(self.t1, f)?;
        Ok(())
    }
}
fn fmt_type(t: ValueType, f: &mut std::fmt::Formatter) -> std::fmt::Result {
    if t == ValueType::Void {
        f.write_str("...")
    } else {
        t.fmt(f)
    }
}
fn fmt_index(index: OpIndex, f: &mut std::fmt::Formatter) -> std::fmt::Result {
    if index == OpIndex::MAX {
        f.write_str("...")
    } else {
        index.fmt(f)
    }
}
#[cfg(test)]
mod test {
    use super::*;
    #[test]
    fn test_inst_size() {
        println!("size {} \n", std::mem::size_of::<Instruction>());
    }
}