use super::TypeKind;
use crate::binary::signature::kinds::PropertySig;
use crate::dll::DLLError;
use crate::{
    binary::{
        heap::{BlobWriter, UserStringWriter, Writer},
        il::Instruction as BInstruction,
        metadata::{
            index::{Blob, MethodDefOrRef, Token, TokenTarget, TypeDefOrRef},
            table::{Kind, MethodSpec, StandAloneSig, TypeSpec},
        },
        signature::{
            encoded::{CustomMod, Param, ParamType, RetType, RetTypeType, Type as SType},
            kinds::{
                FieldSig, LocalVar, LocalVarSig, MethodDefSig, MethodRefSig, MethodSpec as MethodSpecSig,
                StandAloneMethodSig,
            },
        },
    },
    dll::Result,
    resolved::{
        il::*,
        members::{ExternalFieldReference, Field, FieldSource, MethodSource, Property, UserMethod},
        signature::*,
        types::*,
    },
};
use paste::paste;
use scroll::{ctx::TryIntoCtx, Pwrite};
use scroll_buffer::DynamicBuffer;
use std::collections::HashMap;
use std::fmt::Debug;

#[derive(Debug)]
pub struct Context<'a> {
    pub blobs: &'a mut BlobWriter,
    pub specs: &'a mut Vec<TypeSpec>,
    pub type_cache: &'a mut HashMap<u64, TypeDefOrRef>,
    pub blob_scratch: &'a mut DynamicBuffer,
}

#[tracing::instrument]
pub fn index(t: &impl TypeKind, ctx: &mut Context) -> Result<TypeDefOrRef> {
    let hash = crate::utils::hash(t);

    if let Some(&i) = ctx.type_cache.get(&hash) {
        return Ok(i);
    }

    let result = t.as_idx(ctx)?;

    ctx.type_cache.insert(hash, result);

    Ok(result)
}

#[tracing::instrument]
pub fn user_index(t: UserType) -> TypeDefOrRef {
    match t {
        UserType::Definition(d) => TypeDefOrRef::TypeDef(d.0 + 1),
        UserType::Reference(r) => TypeDefOrRef::TypeRef(r.0 + 1),
    }
}

#[tracing::instrument]
fn source_sig(value_kind: Option<ValueKind>, t: &TypeSource<impl TypeKind>, ctx: &mut Context) -> Result<SType> {
    let Some(value_kind) = value_kind else {
        return Err(DLLError::CLI(scroll::Error::Custom(
            "attempted to use type of unknown value kind inside a signature".to_string(),
        )));
    };
    Ok(match t {
        TypeSource::User(u) => match value_kind {
            ValueKind::Class => SType::Class(user_index(*u).into()),
            ValueKind::ValueType => SType::ValueType(user_index(*u).into()),
        },
        TypeSource::Generic { base, parameters } => {
            let base = user_index(*base).into();
            let params = parameters.iter().map(|g| g.as_sig(ctx)).collect::<Result<_>>()?;
            match value_kind {
                ValueKind::Class => SType::GenericInstClass(base, params),
                ValueKind::ValueType => SType::GenericInstValueType(base, params),
            }
        }
    })
}

#[tracing::instrument]
pub fn source_index(
    value_kind: Option<ValueKind>,
    t: &TypeSource<impl TypeKind>,
    ctx: &mut Context,
) -> Result<TypeDefOrRef> {
    if let TypeSource::User(u) = t {
        Ok(user_index(*u))
    } else {
        into_index(source_sig(value_kind, t, ctx)?, ctx)
    }
}

#[tracing::instrument]
pub fn base_index(base: &BaseType<impl TypeKind>, ctx: &mut Context) -> Result<TypeDefOrRef> {
    Ok(match base {
        BaseType::Type { value_kind, source } => source_index(*value_kind, source, ctx)?,
        rest => into_index(base_sig(rest, ctx)?, ctx)?,
    })
}

#[tracing::instrument]
pub fn into_blob(
    sig: impl TryIntoCtx<(), DynamicBuffer, Error = scroll::Error> + Debug,
    ctx: &mut Context,
) -> Result<Blob> {
    ctx.blob_scratch.clear();

    ctx.blob_scratch.pwrite(sig, 0)?;

    Ok(ctx.blobs.write(ctx.blob_scratch.get())?)
}

#[tracing::instrument]
pub(super) fn into_index(
    sig: impl TryIntoCtx<(), DynamicBuffer, Error = scroll::Error> + Debug,
    ctx: &mut Context,
) -> Result<TypeDefOrRef> {
    let len = ctx.specs.len();

    let t = TypeSpec {
        signature: into_blob(sig, ctx)?,
    };
    ctx.specs.push(t);

    Ok(TypeDefOrRef::TypeSpec(len + 1))
}

#[tracing::instrument]
pub(super) fn base_sig(base: &BaseType<impl TypeKind>, ctx: &mut Context) -> Result<SType> {
    use BaseType::*;

    Ok(match base {
        Type { value_kind, source } => source_sig(*value_kind, source, ctx)?,
        Boolean => SType::Boolean,
        Char => SType::Char,
        Int8 => SType::Int8,
        UInt8 => SType::UInt8,
        Int16 => SType::Int16,
        UInt16 => SType::UInt16,
        Int32 => SType::Int32,
        UInt32 => SType::UInt32,
        Int64 => SType::Int64,
        UInt64 => SType::UInt64,
        Float32 => SType::Float32,
        Float64 => SType::Float64,
        IntPtr => SType::IntPtr,
        UIntPtr => SType::UIntPtr,
        Object => SType::Object,
        String => SType::String,
        Vector(m, t) => SType::SzArray(custom_modifiers(m), Box::new(t.as_sig(ctx)?)),
        Array(t, shape) => SType::Array(Box::new(t.as_sig(ctx)?), shape.clone()),
        ValuePointer(m, opt) => SType::Ptr(
            custom_modifiers(m),
            match opt {
                Some(t) => Some(Box::new(t.as_sig(ctx)?)),
                None => None,
            },
        ),
        FunctionPointer(sig) => SType::FnPtr(Box::new(maybe_unmanaged_method(sig, ctx)?)),
    })
}

#[tracing::instrument]
fn maybe_unmanaged_method<T: TypeKind>(
    sig: &MaybeUnmanagedMethod<T>,
    ctx: &mut Context,
) -> Result<StandAloneMethodSig> {
    Ok(StandAloneMethodSig {
        has_this: sig.instance,
        explicit_this: sig.explicit_this,
        calling_convention: sig.calling_convention,
        ret_type: ret_type_sig(&sig.return_type, ctx)?,
        params: sig
            .parameters
            .iter()
            .map(|p| parameter_sig(p, ctx))
            .collect::<Result<_>>()?,
        varargs: sig
            .varargs
            .iter()
            .flatten()
            .map(|p| parameter_sig(p, ctx))
            .collect::<Result<_>>()?,
    })
}

#[tracing::instrument]
pub fn custom_modifiers(mods: &[CustomTypeModifier]) -> Vec<CustomMod> {
    mods.iter()
        .map(|m| match m {
            CustomTypeModifier::Optional(t) => CustomMod::Optional(user_index(*t).into()),
            CustomTypeModifier::Required(t) => CustomMod::Required(user_index(*t).into()),
        })
        .collect()
}

#[tracing::instrument]
fn parameter_sig<T: TypeKind>(p: &Parameter<T>, ctx: &mut Context) -> Result<Param> {
    Ok(Param(
        custom_modifiers(&p.0),
        match &p.1 {
            ParameterType::Value(v) => ParamType::Type(v.as_sig(ctx)?),
            ParameterType::Ref(r) => ParamType::ByRef(r.as_sig(ctx)?),
            ParameterType::TypedReference => ParamType::TypedByRef,
        },
    ))
}

// pub fn parameter(p: &Parameter, ctx: &mut Context) -> Result<Blob> {
//     into_blob(parameter_sig(p, ctx)?, ctx)
// }

#[tracing::instrument]
fn ret_type_sig<T: TypeKind>(r: &ReturnType<T>, ctx: &mut Context) -> Result<RetType> {
    Ok(RetType(
        custom_modifiers(&r.0),
        match &r.1 {
            Some(ParameterType::Value(v)) => RetTypeType::Type(v.as_sig(ctx)?),
            Some(ParameterType::Ref(r)) => RetTypeType::ByRef(r.as_sig(ctx)?),
            Some(ParameterType::TypedReference) => RetTypeType::TypedByRef,
            None => RetTypeType::Void,
        },
    ))
}

#[tracing::instrument]
fn method_def_sig<T: TypeKind>(sig: &ManagedMethod<T>, ctx: &mut Context) -> Result<MethodDefSig> {
    Ok(MethodDefSig {
        has_this: sig.instance,
        explicit_this: sig.explicit_this,
        calling_convention: sig.calling_convention,
        ret_type: ret_type_sig(&sig.return_type, ctx)?,
        params: sig
            .parameters
            .iter()
            .map(|p| parameter_sig(p, ctx))
            .collect::<Result<_>>()?,
    })
}

#[tracing::instrument]
pub fn method_def<T: TypeKind>(sig: &ManagedMethod<T>, ctx: &mut Context) -> Result<Blob> {
    into_blob(method_def_sig(sig, ctx)?, ctx)
}

#[tracing::instrument]
fn method_ref_sig<T: TypeKind>(sig: &ManagedMethod<T>, ctx: &mut Context) -> Result<MethodRefSig> {
    Ok(MethodRefSig {
        method_def: method_def_sig(sig, ctx)?,
        varargs: sig
            .varargs
            .iter()
            .flatten()
            .map(|p| parameter_sig(p, ctx))
            .collect::<Result<_>>()?,
    })
}

#[tracing::instrument]
pub fn method_ref<T: TypeKind>(sig: &ManagedMethod<T>, ctx: &mut Context) -> Result<Blob> {
    into_blob(method_ref_sig(sig, ctx)?, ctx)
}

#[tracing::instrument]
fn field_sig(f: &Field, ctx: &mut Context) -> Result<FieldSig> {
    Ok(FieldSig {
        custom_modifiers: custom_modifiers(&f.type_modifiers),
        by_ref: f.by_ref,
        field_type: f.return_type.as_sig(ctx)?,
    })
}

#[tracing::instrument]
pub fn field_def(f: &Field, ctx: &mut Context) -> Result<Blob> {
    into_blob(field_sig(f, ctx)?, ctx)
}

#[tracing::instrument]
fn field_ref_sig(f: &ExternalFieldReference, ctx: &mut Context) -> Result<FieldSig> {
    Ok(FieldSig {
        custom_modifiers: custom_modifiers(&f.custom_modifiers),
        by_ref: false,
        field_type: f.field_type.as_sig(ctx)?,
    })
}

#[tracing::instrument]
pub fn field_ref(f: &ExternalFieldReference, ctx: &mut Context) -> Result<Blob> {
    into_blob(field_ref_sig(f, ctx)?, ctx)
}

#[tracing::instrument]
fn property_sig(p: &Property, ctx: &mut Context) -> Result<PropertySig> {
    Ok(PropertySig {
        has_this: !p.static_member,
        property_type: parameter_sig(&p.property_type, ctx)?,
        params: p
            .parameters
            .iter()
            .map(|p| parameter_sig(p, ctx))
            .collect::<Result<_>>()?,
    })
}

#[tracing::instrument]
pub fn property(p: &Property, ctx: &mut Context) -> Result<Blob> {
    into_blob(property_sig(p, ctx)?, ctx)
}

#[tracing::instrument]
pub fn idx_with_modifiers(t: &impl TypeKind, mods: &[CustomTypeModifier], ctx: &mut Context) -> Result<TypeDefOrRef> {
    if let Some(BaseType::Type {
        source: TypeSource::User(u),
        ..
    }) = t.as_base()
    {
        Ok(user_index(*u))
    } else {
        let sig = t.as_sig(ctx)?;
        let mods = custom_modifiers(mods);

        #[derive(Debug)]
        struct Wrapper(Vec<CustomMod>, SType);
        impl TryIntoCtx<(), DynamicBuffer> for Wrapper {
            type Error = scroll::Error;

            fn try_into_ctx(self, buf: &mut DynamicBuffer, (): ()) -> std::result::Result<usize, Self::Error> {
                let offset = &mut 0;

                for m in self.0 {
                    buf.gwrite(m, offset)?;
                }
                buf.gwrite(self.1, offset)?;

                Ok(*offset)
            }
        }

        into_index(Wrapper(mods, sig), ctx)
    }
}

#[tracing::instrument]
fn local_var_sig(vars: &[LocalVariable], ctx: &mut Context) -> Result<LocalVarSig> {
    Ok(LocalVarSig(
        vars.iter()
            .map(|v| {
                Ok(match v {
                    LocalVariable::TypedReference => LocalVar::TypedByRef,
                    LocalVariable::Variable {
                        custom_modifiers: cmod,
                        pinned,
                        by_ref,
                        var_type,
                    } => LocalVar::Variable {
                        custom_modifiers: custom_modifiers(cmod),
                        pinned: *pinned,
                        by_ref: *by_ref,
                        var_type: var_type.as_sig(ctx)?,
                    },
                })
            })
            .collect::<Result<_>>()?,
    ))
}

#[tracing::instrument]
pub fn local_vars(vars: &[LocalVariable], ctx: &mut Context) -> Result<Blob> {
    into_blob(local_var_sig(vars, ctx)?, ctx)
}

pub struct MethodContext<'a, T, U> {
    pub stand_alone_sigs: &'a mut Vec<StandAloneSig>,
    pub method_specs: &'a mut Vec<MethodSpec>,
    pub userstrings: &'a mut UserStringWriter,
    pub user_method: &'a T,
    pub field_source: &'a U,
}

#[tracing::instrument(skip(m_ctx))]
#[allow(clippy::too_many_lines)]
pub fn instruction(
    instruction: &Instruction,
    ctx: &mut Context,
    m_ctx: &mut MethodContext<'_, impl Fn(UserMethod) -> MethodDefOrRef, impl Fn(FieldSource) -> Token>,
) -> Result<BInstruction> {
    use Instruction::*;
    use NumberSign::*;

    fn check_mask(skip_type: bool, skip_range: bool, skip_null: bool) -> u8 {
        let mut mask = 0;
        if skip_type {
            mask |= 0x1;
        }
        if skip_range {
            mask |= 0x2;
        }
        if skip_null {
            mask |= 0x4;
        }
        mask
    }

    macro_rules! method_source {
        ($m:ident) => {
            match $m {
                MethodSource::User(u) => (m_ctx.user_method)(*u).into(),
                MethodSource::Generic(g) => {
                    let idx = m_ctx.method_specs.len() + 1;

                    m_ctx.method_specs.push(MethodSpec {
                        method: (m_ctx.user_method)(g.base),
                        instantiation: into_blob(
                            MethodSpecSig(
                                g.parameters
                                    .iter()
                                    .map(|t| t.as_sig(ctx))
                                    .collect::<Result<Vec<_>>>()?,
                            ),
                            ctx,
                        )?,
                    });

                    Token {
                        target: TokenTarget::Table(Kind::MethodSpec),
                        index: idx,
                    }
                }
            }
        };
    }

    macro_rules! stand_alone_sig {
        ($method_sig:ident) => {{
            let sig = maybe_unmanaged_method($method_sig, ctx)?;
            m_ctx.stand_alone_sigs.push(StandAloneSig {
                signature: into_blob(sig, ctx)?,
            });
            Token {
                target: TokenTarget::Table(Kind::StandAloneSig),
                index: m_ctx.stand_alone_sigs.len(),
            }
        }};
    }

    macro_rules! short {
        ($var:ident, $i:ty, $val:ident) => {
            paste! {
                match $i::try_from(*$val).ok() {
                    Some(u) => BInstruction::[<$var S>](u),
                    None => BInstruction::$var(*$val)
                }
            }
        };
    }

    macro_rules! make_convert {
        ($($src:ident => $dest:ident),+) => {
            paste! {
                macro_rules! convert {
                    ($t:ident) => {
                        match $t {
                            $(ConversionType::$src => BInstruction::[<Conv $dest>]),+
                        }
                    }
                }

                macro_rules! convert_overflow {
                    ($t:ident, $sign:ident) => {
                        match ($t, $sign) {
                            $(
                                (ConversionType::$src, Signed) => BInstruction::[<ConvOvf $dest>],
                            )+
                            $(
                                (ConversionType::$src, Unsigned) => BInstruction::[<ConvOvf $dest Un>]
                            ),+
                        }
                    }
                }
            }
        }
    }

    make_convert! {
        Int8 => I1,
        Int16 => I2,
        Int32 => I4,
        Int64 => I8,
        UInt8 => U1,
        UInt16 => U2,
        UInt32 => U4,
        UInt64 => U8,
        IntPtr => I,
        UIntPtr => U
    }

    macro_rules! indirect {
        ($unaligned:ident, $volatile:ident, $ty:ident, $enum:ty => $instr:ident, { $($src:ident => $dest:ident),+ }) => {
            paste! {
                match ($unaligned, $volatile) {
                    (None, false) => match $ty {
                        $($enum::$src => BInstruction::[<$instr $dest>]),+
                    },
                    (None, true) => match $ty {
                        $($enum::$src => BInstruction::[<Volatile $instr $dest>]),+
                    },
                    (Some(a), false) => match $ty {
                        $($enum::$src => BInstruction::[<Unaligned $instr $dest>](*a as u8)),+
                    },
                    (Some(a), true) => match $ty {
                        $($enum::$src => BInstruction::[<UnalignedVolatile $instr $dest>](*a as u8)),+
                    }
                }
            }
        }
    }

    macro_rules! element {
        ($t:expr, $r:expr, $n:expr, $e:ident, $ty:ident => $prefix:ident, { $($src:ident => $dest:ident),+ }) => {
            paste! {
                match ($t, $r, $n) {
                    (false, false, false) => match $e {
                        $(
                            $ty::$src => BInstruction::[<$prefix $dest>]
                        ),+
                    },
                    (t, r, n) => match $e {
                        $(
                            $ty::$src => BInstruction::[<Nocheck $prefix $dest>](check_mask(t, r, n))
                        ),+
                    }
                }
            }
        }
    }

    macro_rules! field {
        ($u:ident, $v:ident, $f:ident, $instr:ident) => {{
            let f = (m_ctx.field_source)(*$f);
            paste! {
                match ($u, $v) {
                    (None, false) => BInstruction::$instr(f),
                    (None, true) => BInstruction::[<Volatile $instr>](f),
                    (Some(a), false) => BInstruction::[<Unaligned $instr>](*a as u8, f),
                    (Some(a), true) => BInstruction::[<UnalignedVolatile $instr>](*a as u8, f),
                }
            }
        }};
    }

    Ok(match instruction {
        Add => BInstruction::Add,
        AddOverflow(Signed) => BInstruction::AddOvf,
        AddOverflow(Unsigned) => BInstruction::AddOvfUn,
        And => BInstruction::And,
        ArgumentList => BInstruction::Arglist,
        BranchEqual(o) => BInstruction::Beq(*o as i32),
        BranchGreaterOrEqual(Signed, o) => BInstruction::Bge(*o as i32),
        BranchGreaterOrEqual(Unsigned, o) => BInstruction::BgeUn(*o as i32),
        BranchGreater(Signed, o) => BInstruction::Bgt(*o as i32),
        BranchGreater(Unsigned, o) => BInstruction::BgtUn(*o as i32),
        BranchLessOrEqual(Signed, o) => BInstruction::Ble(*o as i32),
        BranchLessOrEqual(Unsigned, o) => BInstruction::BleUn(*o as i32),
        BranchLess(Signed, o) => BInstruction::Blt(*o as i32),
        BranchLess(Unsigned, o) => BInstruction::BltUn(*o as i32),
        BranchNotEqual(o) => BInstruction::BneUn(*o as i32),
        Branch(o) => BInstruction::Br(*o as i32),
        Breakpoint => BInstruction::Break,
        BranchFalsy(o) => BInstruction::Brfalse(*o as i32),
        BranchTruthy(o) => BInstruction::Brtrue(*o as i32),
        Call {
            tail_call: false,
            param0: method,
        } => BInstruction::Call(method_source!(method)),
        Call {
            tail_call: true,
            param0: method,
        } => BInstruction::TailCall(method_source!(method)),
        CallConstrained(constraint, method) => {
            BInstruction::ConstrainedCall(constraint.as_idx(ctx)?.into(), method_source!(method))
        }
        CallIndirect {
            tail_call: false,
            param0: signature,
        } => BInstruction::Calli(stand_alone_sig!(signature)),
        CallIndirect {
            tail_call: true,
            param0: signature,
        } => BInstruction::TailCalli(stand_alone_sig!(signature)),
        CompareEqual => BInstruction::Ceq,
        CompareGreater(Signed) => BInstruction::Cgt,
        CompareGreater(Unsigned) => BInstruction::CgtUn,
        CheckFinite => BInstruction::Ckfinite,
        CompareLess(Signed) => BInstruction::Clt,
        CompareLess(Unsigned) => BInstruction::CltUn,
        Convert(t) => convert!(t),
        ConvertOverflow(t, sign) => convert_overflow!(t, sign),
        ConvertFloat32 => BInstruction::ConvR4,
        ConvertFloat64 => BInstruction::ConvR8,
        ConvertUnsignedToFloat => BInstruction::ConvRUn,
        CopyMemoryBlock {
            unaligned: None,
            volatile: false,
        } => BInstruction::Cpblk,
        CopyMemoryBlock {
            unaligned: None,
            volatile: true,
        } => BInstruction::VolatileCpblk,
        CopyMemoryBlock {
            unaligned: Some(a),
            volatile: false,
        } => BInstruction::UnalignedCpblk(*a as u8),
        CopyMemoryBlock {
            unaligned: Some(a),
            volatile: true,
        } => BInstruction::UnalignedVolatileCpblk(*a as u8),
        Divide(Signed) => BInstruction::Div,
        Divide(Unsigned) => BInstruction::DivUn,
        Duplicate => BInstruction::Dup,
        EndFilter => BInstruction::Endfilter,
        EndFinally => BInstruction::Endfinally,
        InitializeMemoryBlock {
            unaligned: None,
            volatile: false,
        } => BInstruction::Initblk,
        InitializeMemoryBlock {
            unaligned: None,
            volatile: true,
        } => BInstruction::VolatileInitblk,
        InitializeMemoryBlock {
            unaligned: Some(a),
            volatile: false,
        } => BInstruction::UnalignedInitblk(*a as u8),
        InitializeMemoryBlock {
            unaligned: Some(a),
            volatile: true,
        } => BInstruction::UnalignedVolatileInitblk(*a as u8),
        Jump(m) => BInstruction::Jmp(method_source!(m)),
        LoadArgument(0) => BInstruction::Ldarg0,
        LoadArgument(1) => BInstruction::Ldarg1,
        LoadArgument(2) => BInstruction::Ldarg2,
        LoadArgument(3) => BInstruction::Ldarg3,
        LoadArgument(i) => short!(Ldarg, u8, i),
        LoadArgumentAddress(i) => short!(Ldarga, u8, i),
        LoadConstantInt32(0) => BInstruction::LdcI40,
        LoadConstantInt32(1) => BInstruction::LdcI41,
        LoadConstantInt32(2) => BInstruction::LdcI42,
        LoadConstantInt32(3) => BInstruction::LdcI43,
        LoadConstantInt32(4) => BInstruction::LdcI44,
        LoadConstantInt32(5) => BInstruction::LdcI45,
        LoadConstantInt32(6) => BInstruction::LdcI46,
        LoadConstantInt32(7) => BInstruction::LdcI47,
        LoadConstantInt32(8) => BInstruction::LdcI48,
        LoadConstantInt32(-1) => BInstruction::LdcI4M1,
        LoadConstantInt32(i) => short!(LdcI4, i8, i),
        LoadConstantInt64(i) => BInstruction::LdcI8(*i),
        LoadConstantFloat32(f) => BInstruction::LdcR4(*f),
        LoadConstantFloat64(f) => BInstruction::LdcR8(*f),
        LoadMethodPointer(m) => BInstruction::Ldftn(method_source!(m)),
        LoadIndirect {
            unaligned,
            volatile,
            param0: value_type,
        } => indirect!(unaligned, volatile, value_type, LoadType => Ldind, {
            Int8 => I1,
            Int16 => I2,
            Int32 => I4,
            Int64 => I8,
            UInt8 => U1,
            UInt16 => U2,
            UInt32 => U4,
            Float32 => R4,
            Float64 => R8,
            IntPtr => I,
            Object => Ref
        }),
        LoadLocal(0) => BInstruction::Ldloc0,
        LoadLocal(1) => BInstruction::Ldloc1,
        LoadLocal(2) => BInstruction::Ldloc2,
        LoadLocal(3) => BInstruction::Ldloc3,
        LoadLocal(i) => short!(Ldloc, u8, i),
        LoadLocalAddress(i) => short!(Ldloca, u8, i),
        LoadNull => BInstruction::Ldnull,
        Leave(o) => BInstruction::Leave(*o as i32),
        LocalMemoryAllocate => BInstruction::Localloc,
        Multiply => BInstruction::Mul,
        MultiplyOverflow(Signed) => BInstruction::MulOvf,
        MultiplyOverflow(Unsigned) => BInstruction::MulOvfUn,
        Negate => BInstruction::Neg,
        NoOperation => BInstruction::Nop,
        Not => BInstruction::Not,
        Or => BInstruction::Or,
        Pop => BInstruction::Pop,
        Remainder(Signed) => BInstruction::Rem,
        Remainder(Unsigned) => BInstruction::RemUn,
        Return => BInstruction::Ret,
        ShiftLeft => BInstruction::Shl,
        ShiftRight(Signed) => BInstruction::Shr,
        ShiftRight(Unsigned) => BInstruction::ShrUn,
        StoreArgument(i) => short!(Starg, u8, i),
        StoreIndirect {
            unaligned,
            volatile,
            param0: value_type,
        } => indirect!(unaligned, volatile, value_type, StoreType => Stind, {
            Int8 => I1,
            Int16 => I2,
            Int32 => I4,
            Int64 => I8,
            Float32 => R4,
            Float64 => R8,
            IntPtr => I,
            Object => Ref
        }),
        StoreLocal(0) => BInstruction::Stloc0,
        StoreLocal(1) => BInstruction::Stloc1,
        StoreLocal(2) => BInstruction::Stloc2,
        StoreLocal(3) => BInstruction::Stloc3,
        StoreLocal(i) => short!(Stloc, u8, i),
        Subtract => BInstruction::Sub,
        SubtractOverflow(Signed) => BInstruction::SubOvf,
        SubtractOverflow(Unsigned) => BInstruction::SubOvfUn,
        Switch(os) => BInstruction::Switch(os.iter().map(|&o| o as i32).collect()),
        Xor => BInstruction::Xor,

        BoxValue(t) => BInstruction::Box(t.as_idx(ctx)?.into()),
        CallVirtual {
            skip_null_check: false,
            param0: method,
        } => BInstruction::Callvirt(method_source!(method)),
        CallVirtual {
            skip_null_check: true,
            param0: method,
        } => BInstruction::NocheckCallvirt(check_mask(false, true, false), method_source!(method)),
        CallVirtualConstrained(constraint, method) => {
            BInstruction::ConstrainedCallvirt(constraint.as_idx(ctx)?.into(), method_source!(method))
        }
        CallVirtualTail(m) => BInstruction::TailCallvirt(method_source!(m)),
        CastClass {
            skip_type_check: false,
            param0: cast_type,
        } => BInstruction::Castclass(cast_type.as_idx(ctx)?.into()),
        CastClass {
            skip_type_check: true,
            param0: cast_type,
        } => BInstruction::NocheckCastclass(check_mask(true, false, false), cast_type.as_idx(ctx)?.into()),
        CopyObject(t) => BInstruction::Cpobj(t.as_idx(ctx)?.into()),
        InitializeForObject(t) => BInstruction::Initobj(t.as_idx(ctx)?.into()),
        IsInstance(t) => BInstruction::Isinst(t.as_idx(ctx)?.into()),
        LoadElement {
            skip_range_check: false,
            skip_null_check: false,
            param0: element_type,
        } => BInstruction::Ldelem(element_type.as_idx(ctx)?.into()),
        LoadElement {
            skip_range_check,
            skip_null_check,
            param0: element_type,
        } => BInstruction::NocheckLdelem(
            check_mask(false, *skip_range_check, *skip_null_check),
            element_type.as_idx(ctx)?.into(),
        ),
        LoadElementPrimitive {
            skip_range_check,
            skip_null_check,
            param0: element_type,
        } => {
            element!(false, *skip_range_check, *skip_null_check, element_type, LoadType => Ldelem, {
                Int8 => I1,
                Int16 => I2,
                Int32 => I4,
                Int64 => I8,
                UInt8 => U1,
                UInt16 => U2,
                UInt32 => U4,
                Float32 => R4,
                Float64 => R8,
                IntPtr => I,
                Object => Ref
            })
        }
        LoadElementAddress {
            skip_type_check: false,
            skip_range_check: false,
            skip_null_check: false,
            param0: element_type,
        } => BInstruction::Ldelema(element_type.as_idx(ctx)?.into()),
        LoadElementAddress {
            skip_type_check,
            skip_range_check,
            skip_null_check,
            param0: element_type,
        } => BInstruction::NocheckLdelema(
            check_mask(*skip_type_check, *skip_range_check, *skip_null_check),
            element_type.as_idx(ctx)?.into(),
        ),
        LoadElementAddressReadonly(t) => BInstruction::ReadonlyLdelema(t.as_idx(ctx)?.into()),
        LoadField {
            unaligned,
            volatile,
            param0: field,
        } => field!(unaligned, volatile, field, Ldfld),
        LoadFieldAddress(f) => BInstruction::Ldflda((m_ctx.field_source)(*f)),
        LoadFieldSkipNullCheck(f) => {
            BInstruction::NocheckLdfld(check_mask(false, false, true), (m_ctx.field_source)(*f))
        }
        LoadLength => BInstruction::Ldlen,
        LoadObject {
            unaligned: None,
            volatile: false,
            param0: object_type,
        } => BInstruction::Ldobj(object_type.as_idx(ctx)?.into()),
        LoadObject {
            unaligned: None,
            volatile: true,
            param0: object_type,
        } => BInstruction::VolatileLdobj(object_type.as_idx(ctx)?.into()),
        LoadObject {
            unaligned: Some(a),
            volatile: false,
            param0: object_type,
        } => BInstruction::UnalignedLdobj(*a as u8, object_type.as_idx(ctx)?.into()),
        LoadObject {
            unaligned: Some(a),
            volatile: true,
            param0: object_type,
        } => BInstruction::UnalignedVolatileLdobj(*a as u8, object_type.as_idx(ctx)?.into()),
        LoadStaticField {
            volatile: false,
            param0: field,
        } => BInstruction::Ldsfld((m_ctx.field_source)(*field)),
        LoadStaticField {
            volatile: true,
            param0: field,
        } => BInstruction::VolatileLdsfld((m_ctx.field_source)(*field)),
        LoadStaticFieldAddress(f) => BInstruction::Ldsflda((m_ctx.field_source)(*f)),
        LoadString(s) => BInstruction::Ldstr(Token {
            target: TokenTarget::UserString,
            index: m_ctx.userstrings.write(s)?,
        }),
        LoadTokenField(f) => BInstruction::Ldtoken((m_ctx.field_source)(*f)),
        LoadTokenMethod(m) => BInstruction::Ldtoken(method_source!(m)),
        LoadTokenType(t) => BInstruction::Ldtoken(t.as_idx(ctx)?.into()),
        LoadVirtualMethodPointer {
            skip_null_check: false,
            param0: method,
        } => BInstruction::Ldvirtftn(method_source!(method)),
        LoadVirtualMethodPointer {
            skip_null_check: true,
            param0: method,
        } => BInstruction::NocheckLdvirtftn(check_mask(false, false, true), method_source!(method)),
        MakeTypedReference(t) => BInstruction::Mkrefany(t.as_idx(ctx)?.into()),
        NewArray(t) => BInstruction::Newarr(t.as_idx(ctx)?.into()),
        NewObject(m) => BInstruction::Newobj((m_ctx.user_method)(*m).into()),
        ReadTypedReferenceType => BInstruction::Refanytype,
        ReadTypedReferenceValue(t) => BInstruction::Refanyval(t.as_idx(ctx)?.into()),
        Rethrow => BInstruction::Rethrow,
        Sizeof(t) => BInstruction::Sizeof(t.as_idx(ctx)?.into()),
        StoreElement {
            skip_type_check: false,
            skip_range_check: false,
            skip_null_check: false,
            param0: element_type,
        } => BInstruction::Stelem(element_type.as_idx(ctx)?.into()),
        StoreElement {
            skip_type_check,
            skip_range_check,
            skip_null_check,
            param0: element_type,
        } => BInstruction::NocheckStelem(
            check_mask(*skip_type_check, *skip_range_check, *skip_null_check),
            element_type.as_idx(ctx)?.into(),
        ),
        StoreElementPrimitive {
            skip_type_check,
            skip_range_check,
            skip_null_check,
            param0: element_type,
        } => element!(
            *skip_type_check,
            *skip_range_check,
            *skip_null_check,
            element_type,
            StoreType => Stelem,
            {
                Int8 => I1,
                Int16 => I2,
                Int32 => I4,
                Int64 => I8,
                Float32 => R4,
                Float64 => R8,
                IntPtr => I,
                Object => Ref
            }
        ),
        StoreField {
            unaligned,
            volatile,
            param0: field,
        } => field!(unaligned, volatile, field, Stfld),
        StoreFieldSkipNullCheck(f) => {
            BInstruction::NocheckStfld(check_mask(false, false, true), (m_ctx.field_source)(*f))
        }
        StoreObject {
            unaligned: None,
            volatile: false,
            param0: object_type,
        } => BInstruction::Stobj(object_type.as_idx(ctx)?.into()),
        StoreObject {
            unaligned: None,
            volatile: true,
            param0: object_type,
        } => BInstruction::VolatileStobj(object_type.as_idx(ctx)?.into()),
        StoreObject {
            unaligned: Some(a),
            volatile: false,
            param0: object_type,
        } => BInstruction::UnalignedStobj(*a as u8, object_type.as_idx(ctx)?.into()),
        StoreObject {
            unaligned: Some(a),
            volatile: true,
            param0: object_type,
        } => BInstruction::UnalignedVolatileStobj(*a as u8, object_type.as_idx(ctx)?.into()),
        StoreStaticField {
            volatile: false,
            param0: field,
        } => BInstruction::Stsfld((m_ctx.field_source)(*field)),
        StoreStaticField {
            volatile: true,
            param0: field,
        } => BInstruction::VolatileStsfld((m_ctx.field_source)(*field)),
        Throw => BInstruction::Throw,
        UnboxIntoAddress {
            skip_type_check: false,
            param0: unbox_type,
        } => BInstruction::Unbox(unbox_type.as_idx(ctx)?.into()),
        UnboxIntoAddress {
            skip_type_check: true,
            param0: unbox_type,
        } => BInstruction::NocheckUnbox(check_mask(true, false, false), unbox_type.as_idx(ctx)?.into()),
        UnboxIntoValue(t) => BInstruction::UnboxAny(t.as_idx(ctx)?.into()),
    })
}
