use super::operation::*;
use super::HighLevelILFunction;

use crate::architecture::CoreIntrinsic;
use crate::rc::Ref;
use crate::types::{ConstantData, SSAVariable, Variable};

#[derive(Clone)]
pub enum HighLevelILLiftedOperand {
    ConstantData(ConstantData),
    Expr(HighLevelILLiftedInstruction),
    ExprList(Vec<HighLevelILLiftedInstruction>),
    Float(f64),
    Int(u64),
    IntList(Vec<u64>),
    Intrinsic(CoreIntrinsic),
    Label(GotoLabel),
    MemberIndex(Option<usize>),
    Var(Variable),
    VarSsa(SSAVariable),
    VarSsaList(Vec<SSAVariable>),
}

#[derive(Clone, Debug, PartialEq)]
pub struct HighLevelILLiftedInstruction {
    pub function: Ref<HighLevelILFunction>,
    pub address: u64,
    pub index: usize,
    pub size: usize,
    pub kind: HighLevelILLiftedInstructionKind,
}

#[derive(Clone, Debug, PartialEq)]
pub enum HighLevelILLiftedInstructionKind {
    Nop,
    Break,
    Continue,
    Noret,
    Unreachable,
    Bp,
    Undef,
    Unimpl,
    Adc(LiftedBinaryOpCarry),
    Sbb(LiftedBinaryOpCarry),
    Rlc(LiftedBinaryOpCarry),
    Rrc(LiftedBinaryOpCarry),
    Add(LiftedBinaryOp),
    Sub(LiftedBinaryOp),
    And(LiftedBinaryOp),
    Or(LiftedBinaryOp),
    Xor(LiftedBinaryOp),
    Lsl(LiftedBinaryOp),
    Lsr(LiftedBinaryOp),
    Asr(LiftedBinaryOp),
    Rol(LiftedBinaryOp),
    Ror(LiftedBinaryOp),
    Mul(LiftedBinaryOp),
    MuluDp(LiftedBinaryOp),
    MulsDp(LiftedBinaryOp),
    Divu(LiftedBinaryOp),
    DivuDp(LiftedBinaryOp),
    Divs(LiftedBinaryOp),
    DivsDp(LiftedBinaryOp),
    Modu(LiftedBinaryOp),
    ModuDp(LiftedBinaryOp),
    Mods(LiftedBinaryOp),
    ModsDp(LiftedBinaryOp),
    CmpE(LiftedBinaryOp),
    CmpNe(LiftedBinaryOp),
    CmpSlt(LiftedBinaryOp),
    CmpUlt(LiftedBinaryOp),
    CmpSle(LiftedBinaryOp),
    CmpUle(LiftedBinaryOp),
    CmpSge(LiftedBinaryOp),
    CmpUge(LiftedBinaryOp),
    CmpSgt(LiftedBinaryOp),
    CmpUgt(LiftedBinaryOp),
    TestBit(LiftedBinaryOp),
    AddOverflow(LiftedBinaryOp),
    Fadd(LiftedBinaryOp),
    Fsub(LiftedBinaryOp),
    Fmul(LiftedBinaryOp),
    Fdiv(LiftedBinaryOp),
    FcmpE(LiftedBinaryOp),
    FcmpNe(LiftedBinaryOp),
    FcmpLt(LiftedBinaryOp),
    FcmpLe(LiftedBinaryOp),
    FcmpGe(LiftedBinaryOp),
    FcmpGt(LiftedBinaryOp),
    FcmpO(LiftedBinaryOp),
    FcmpUo(LiftedBinaryOp),
    ArrayIndex(LiftedArrayIndex),
    ArrayIndexSsa(LiftedArrayIndexSsa),
    Assign(LiftedAssign),
    AssignMemSsa(LiftedAssignMemSsa),
    AssignUnpack(LiftedAssignUnpack),
    AssignUnpackMemSsa(LiftedAssignUnpackMemSsa),
    Block(LiftedBlock),
    Call(LiftedCall),
    Tailcall(LiftedCall),
    CallSsa(LiftedCallSsa),
    Case(LiftedCase),
    Const(Const),
    ConstPtr(Const),
    Import(Const),
    ConstData(LiftedConstData),
    Deref(LiftedUnaryOp),
    AddressOf(LiftedUnaryOp),
    Neg(LiftedUnaryOp),
    Not(LiftedUnaryOp),
    Sx(LiftedUnaryOp),
    Zx(LiftedUnaryOp),
    LowPart(LiftedUnaryOp),
    BoolToInt(LiftedUnaryOp),
    UnimplMem(LiftedUnaryOp),
    Fsqrt(LiftedUnaryOp),
    Fneg(LiftedUnaryOp),
    Fabs(LiftedUnaryOp),
    FloatToInt(LiftedUnaryOp),
    IntToFloat(LiftedUnaryOp),
    FloatConv(LiftedUnaryOp),
    RoundToInt(LiftedUnaryOp),
    Floor(LiftedUnaryOp),
    Ceil(LiftedUnaryOp),
    Ftrunc(LiftedUnaryOp),
    DerefFieldSsa(LiftedDerefFieldSsa),
    DerefSsa(LiftedDerefSsa),
    ExternPtr(ExternPtr),
    FloatConst(FloatConst),
    For(LiftedForLoop),
    ForSsa(LiftedForLoopSsa),
    Goto(LiftedLabel),
    Label(LiftedLabel),
    If(LiftedIf),
    Intrinsic(LiftedIntrinsic),
    IntrinsicSsa(LiftedIntrinsicSsa),
    Jump(LiftedJump),
    MemPhi(LiftedMemPhi),
    Ret(LiftedRet),
    Split(LiftedSplit),
    StructField(LiftedStructField),
    DerefField(LiftedStructField),
    Switch(LiftedSwitch),
    Syscall(LiftedSyscall),
    SyscallSsa(LiftedSyscallSsa),
    Trap(Trap),
    VarDeclare(Var),
    Var(Var),
    VarInit(LiftedVarInit),
    VarInitSsa(LiftedVarInitSsa),
    VarPhi(LiftedVarPhi),
    VarSsa(VarSsa),
    While(LiftedWhile),
    DoWhile(LiftedWhile),
    WhileSsa(LiftedWhileSsa),
    DoWhileSsa(LiftedWhileSsa),
}

impl HighLevelILLiftedInstruction {
    pub fn name(&self) -> &'static str {
        use HighLevelILLiftedInstructionKind::*;
        match self.kind {
            Nop => "Nop",
            Break => "Break",
            Continue => "Continue",
            Noret => "Noret",
            Unreachable => "Unreachable",
            Bp => "Bp",
            Undef => "Undef",
            Unimpl => "Unimpl",
            Adc(_) => "Adc",
            Sbb(_) => "Sbb",
            Rlc(_) => "Rlc",
            Rrc(_) => "Rrc",
            Add(_) => "Add",
            Sub(_) => "Sub",
            And(_) => "And",
            Or(_) => "Or",
            Xor(_) => "Xor",
            Lsl(_) => "Lsl",
            Lsr(_) => "Lsr",
            Asr(_) => "Asr",
            Rol(_) => "Rol",
            Ror(_) => "Ror",
            Mul(_) => "Mul",
            MuluDp(_) => "MuluDp",
            MulsDp(_) => "MulsDp",
            Divu(_) => "Divu",
            DivuDp(_) => "DivuDp",
            Divs(_) => "Divs",
            DivsDp(_) => "DivsDp",
            Modu(_) => "Modu",
            ModuDp(_) => "ModuDp",
            Mods(_) => "Mods",
            ModsDp(_) => "ModsDp",
            CmpE(_) => "CmpE",
            CmpNe(_) => "CmpNe",
            CmpSlt(_) => "CmpSlt",
            CmpUlt(_) => "CmpUlt",
            CmpSle(_) => "CmpSle",
            CmpUle(_) => "CmpUle",
            CmpSge(_) => "CmpSge",
            CmpUge(_) => "CmpUge",
            CmpSgt(_) => "CmpSgt",
            CmpUgt(_) => "CmpUgt",
            TestBit(_) => "TestBit",
            AddOverflow(_) => "AddOverflow",
            Fadd(_) => "Fadd",
            Fsub(_) => "Fsub",
            Fmul(_) => "Fmul",
            Fdiv(_) => "Fdiv",
            FcmpE(_) => "FcmpE",
            FcmpNe(_) => "FcmpNe",
            FcmpLt(_) => "FcmpLt",
            FcmpLe(_) => "FcmpLe",
            FcmpGe(_) => "FcmpGe",
            FcmpGt(_) => "FcmpGt",
            FcmpO(_) => "FcmpO",
            FcmpUo(_) => "FcmpUo",
            ArrayIndex(_) => "ArrayIndex",
            ArrayIndexSsa(_) => "ArrayIndexSsa",
            Assign(_) => "Assign",
            AssignMemSsa(_) => "AssignMemSsa",
            AssignUnpack(_) => "AssignUnpack",
            AssignUnpackMemSsa(_) => "AssignUnpackMemSsa",
            Block(_) => "Block",
            Call(_) => "Call",
            Tailcall(_) => "Tailcall",
            CallSsa(_) => "CallSsa",
            Case(_) => "Case",
            Const(_) => "Const",
            ConstPtr(_) => "ConstPtr",
            Import(_) => "Import",
            ConstData(_) => "ConstData",
            Deref(_) => "Deref",
            AddressOf(_) => "AddressOf",
            Neg(_) => "Neg",
            Not(_) => "Not",
            Sx(_) => "Sx",
            Zx(_) => "Zx",
            LowPart(_) => "LowPart",
            BoolToInt(_) => "BoolToInt",
            UnimplMem(_) => "UnimplMem",
            Fsqrt(_) => "Fsqrt",
            Fneg(_) => "Fneg",
            Fabs(_) => "Fabs",
            FloatToInt(_) => "FloatToInt",
            IntToFloat(_) => "IntToFloat",
            FloatConv(_) => "FloatConv",
            RoundToInt(_) => "RoundToInt",
            Floor(_) => "Floor",
            Ceil(_) => "Ceil",
            Ftrunc(_) => "Ftrunc",
            DerefFieldSsa(_) => "DerefFieldSsa",
            DerefSsa(_) => "DerefSsa",
            ExternPtr(_) => "ExternPtr",
            FloatConst(_) => "FloatConst",
            For(_) => "For",
            ForSsa(_) => "ForSsa",
            Goto(_) => "Goto",
            Label(_) => "Label",
            If(_) => "If",
            Intrinsic(_) => "Intrinsic",
            IntrinsicSsa(_) => "IntrinsicSsa",
            Jump(_) => "Jump",
            MemPhi(_) => "MemPhi",
            Ret(_) => "Ret",
            Split(_) => "Split",
            StructField(_) => "StructField",
            DerefField(_) => "DerefField",
            Switch(_) => "Switch",
            Syscall(_) => "Syscall",
            SyscallSsa(_) => "SyscallSsa",
            Trap(_) => "Trap",
            VarDeclare(_) => "VarDeclare",
            Var(_) => "Var",
            VarInit(_) => "VarInit",
            VarInitSsa(_) => "VarInitSsa",
            VarPhi(_) => "VarPhi",
            VarSsa(_) => "VarSsa",
            While(_) => "While",
            DoWhile(_) => "DoWhile",
            WhileSsa(_) => "WhileSsa",
            DoWhileSsa(_) => "DoWhileSsa",
        }
    }

    pub fn operands(&self) -> Vec<(&'static str, HighLevelILLiftedOperand)> {
        use HighLevelILLiftedInstructionKind::*;
        use HighLevelILLiftedOperand as Operand;
        match &self.kind {
            Nop | Break | Continue | Noret | Unreachable | Bp | Undef | Unimpl => vec![],
            Adc(op) | Sbb(op) | Rlc(op) | Rrc(op) => vec![
                ("left", Operand::Expr(*op.left.clone())),
                ("right", Operand::Expr(*op.right.clone())),
                ("carry", Operand::Expr(*op.carry.clone())),
            ],
            Add(op) | Sub(op) | And(op) | Or(op) | Xor(op) | Lsl(op) | Lsr(op) | Asr(op)
            | Rol(op) | Ror(op) | Mul(op) | MuluDp(op) | MulsDp(op) | Divu(op) | DivuDp(op)
            | Divs(op) | DivsDp(op) | Modu(op) | ModuDp(op) | Mods(op) | ModsDp(op) | CmpE(op)
            | CmpNe(op) | CmpSlt(op) | CmpUlt(op) | CmpSle(op) | CmpUle(op) | CmpSge(op)
            | CmpUge(op) | CmpSgt(op) | CmpUgt(op) | TestBit(op) | AddOverflow(op) | Fadd(op)
            | Fsub(op) | Fmul(op) | Fdiv(op) | FcmpE(op) | FcmpNe(op) | FcmpLt(op) | FcmpLe(op)
            | FcmpGe(op) | FcmpGt(op) | FcmpO(op) | FcmpUo(op) => vec![
                ("left", Operand::Expr(*op.left.clone())),
                ("right", Operand::Expr(*op.right.clone())),
            ],
            ArrayIndex(op) => vec![
                ("src", Operand::Expr(*op.src.clone())),
                ("index", Operand::Expr(*op.index.clone())),
            ],
            ArrayIndexSsa(op) => vec![
                ("src", Operand::Expr(*op.src.clone())),
                ("src_memory", Operand::Int(op.src_memory)),
                ("index", Operand::Expr(*op.index.clone())),
            ],
            Assign(op) => vec![
                ("dest", Operand::Expr(*op.dest.clone())),
                ("src", Operand::Expr(*op.src.clone())),
            ],
            AssignMemSsa(op) => vec![
                ("dest", Operand::Expr(*op.dest.clone())),
                ("dest_memory", Operand::Int(op.dest_memory)),
                ("src", Operand::Expr(*op.src.clone())),
                ("src_memory", Operand::Int(op.src_memory)),
            ],
            AssignUnpack(op) => vec![
                ("dest", Operand::ExprList(op.dest.clone())),
                ("src", Operand::Expr(*op.src.clone())),
            ],
            AssignUnpackMemSsa(op) => vec![
                ("dest", Operand::ExprList(op.dest.clone())),
                ("dest_memory", Operand::Int(op.dest_memory)),
                ("src", Operand::Expr(*op.src.clone())),
                ("src_memory", Operand::Int(op.src_memory)),
            ],
            Block(op) => vec![("body", Operand::ExprList(op.body.clone()))],
            Call(op) | Tailcall(op) => vec![
                ("dest", Operand::Expr(*op.dest.clone())),
                ("params", Operand::ExprList(op.params.clone())),
            ],
            CallSsa(op) => vec![
                ("dest", Operand::Expr(*op.dest.clone())),
                ("params", Operand::ExprList(op.params.clone())),
                ("dest_memory", Operand::Int(op.dest_memory)),
                ("src_memory", Operand::Int(op.src_memory)),
            ],
            Case(op) => vec![
                ("values", Operand::ExprList(op.values.clone())),
                ("body", Operand::Expr(*op.body.clone())),
            ],
            Const(op) | ConstPtr(op) | Import(op) => vec![("constant", Operand::Int(op.constant))],
            ConstData(op) => vec![(
                "constant_data",
                Operand::ConstantData(op.constant_data.clone()),
            )],
            Deref(op) | AddressOf(op) | Neg(op) | Not(op) | Sx(op) | Zx(op) | LowPart(op)
            | BoolToInt(op) | UnimplMem(op) | Fsqrt(op) | Fneg(op) | Fabs(op) | FloatToInt(op)
            | IntToFloat(op) | FloatConv(op) | RoundToInt(op) | Floor(op) | Ceil(op)
            | Ftrunc(op) => vec![("src", Operand::Expr(*op.src.clone()))],
            DerefFieldSsa(op) => vec![
                ("src", Operand::Expr(*op.src.clone())),
                ("src_memory", Operand::Int(op.src_memory)),
                ("offset", Operand::Int(op.offset)),
                ("member_index", Operand::MemberIndex(op.member_index)),
            ],
            DerefSsa(op) => vec![
                ("src", Operand::Expr(*op.src.clone())),
                ("src_memory", Operand::Int(op.src_memory)),
            ],
            ExternPtr(op) => vec![
                ("constant", Operand::Int(op.constant)),
                ("offset", Operand::Int(op.offset)),
            ],
            FloatConst(op) => vec![("constant", Operand::Float(op.constant))],
            For(op) => vec![
                ("init", Operand::Expr(*op.init.clone())),
                ("condition", Operand::Expr(*op.condition.clone())),
                ("update", Operand::Expr(*op.update.clone())),
                ("body", Operand::Expr(*op.body.clone())),
            ],
            ForSsa(op) => vec![
                ("init", Operand::Expr(*op.init.clone())),
                ("condition_phi", Operand::Expr(*op.condition_phi.clone())),
                ("condition", Operand::Expr(*op.condition.clone())),
                ("update", Operand::Expr(*op.update.clone())),
                ("body", Operand::Expr(*op.body.clone())),
            ],
            Goto(op) | Label(op) => vec![("target", Operand::Label(op.target.clone()))],
            If(op) => vec![
                ("condition", Operand::Expr(*op.condition.clone())),
                ("cond_true", Operand::Expr(*op.cond_true.clone())),
                ("cond_false", Operand::Expr(*op.cond_false.clone())),
            ],
            Intrinsic(op) => vec![
                ("intrinsic", Operand::Intrinsic(op.intrinsic)),
                ("params", Operand::ExprList(op.params.clone())),
            ],
            IntrinsicSsa(op) => vec![
                ("intrinsic", Operand::Intrinsic(op.intrinsic)),
                ("params", Operand::ExprList(op.params.clone())),
                ("dest_memory", Operand::Int(op.dest_memory)),
                ("src_memory", Operand::Int(op.src_memory)),
            ],
            Jump(op) => vec![("dest", Operand::Expr(*op.dest.clone()))],
            MemPhi(op) => vec![
                ("dest", Operand::Int(op.dest)),
                ("src", Operand::IntList(op.src.clone())),
            ],
            Ret(op) => vec![("src", Operand::ExprList(op.src.clone()))],
            Split(op) => vec![
                ("high", Operand::Expr(*op.high.clone())),
                ("low", Operand::Expr(*op.low.clone())),
            ],
            StructField(op) | DerefField(op) => vec![
                ("src", Operand::Expr(*op.src.clone())),
                ("offset", Operand::Int(op.offset)),
                ("member_index", Operand::MemberIndex(op.member_index)),
            ],
            Switch(op) => vec![
                ("condition", Operand::Expr(*op.condition.clone())),
                ("default", Operand::Expr(*op.default.clone())),
                ("cases", Operand::ExprList(op.cases.clone())),
            ],
            Syscall(op) => vec![("params", Operand::ExprList(op.params.clone()))],
            SyscallSsa(op) => vec![
                ("params", Operand::ExprList(op.params.clone())),
                ("dest_memory", Operand::Int(op.dest_memory)),
                ("src_memory", Operand::Int(op.src_memory)),
            ],
            Trap(op) => vec![("vector", Operand::Int(op.vector))],
            VarDeclare(op) | Var(op) => vec![("var", Operand::Var(op.var))],
            VarInit(op) => vec![
                ("dest", Operand::Var(op.dest)),
                ("src", Operand::Expr(*op.src.clone())),
            ],
            VarInitSsa(op) => vec![
                ("dest", Operand::VarSsa(op.dest)),
                ("src", Operand::Expr(*op.src.clone())),
            ],
            VarPhi(op) => vec![
                ("dest", Operand::VarSsa(op.dest)),
                ("src", Operand::VarSsaList(op.src.clone())),
            ],
            VarSsa(op) => vec![("var", Operand::VarSsa(op.var))],
            While(op) | DoWhile(op) => vec![
                ("condition", Operand::Expr(*op.condition.clone())),
                ("body", Operand::Expr(*op.body.clone())),
            ],
            WhileSsa(op) | DoWhileSsa(op) => vec![
                ("condition_phi", Operand::Expr(*op.condition_phi.clone())),
                ("condition", Operand::Expr(*op.condition.clone())),
                ("body", Operand::Expr(*op.body.clone())),
            ],
        }
    }
}
