//! CIL (Common Intermediate Language) instruction representation.
//!
//! This module provides the [`Instruction`] enum, which represents all CIL opcodes
//! at a high level. Branch targets use instruction indices (not byte offsets) -
//! the library handles offset calculation during serialization.
//!
//! # Examples
//!
//! ## Basic instructions
//!
//! ```rust
//! use dotnetdll::prelude::*;
//!
//! let instructions = vec![
//!     Instruction::LoadConstantInt32(42),
//!     Instruction::LoadConstantInt32(8),
//!     Instruction::Add,
//!     Instruction::Return,
//! ];
//! ```
//!
//! ## Using the `asm!` macro with labels
//!
//! The [`crate::asm!`] macro makes it easier to construct IL sequences with labels:
//!
//! Note: within `asm!`, `@label` introduces a *label definition* (it does **not** mean
//! “clone this variable”). Other proc macros in this crate (e.g. [`crate::resolved::types::ctype!`]
//! and [`crate::resolved::signature::msig!`]) use `@var` for variable substitution via
//! `var.clone()`.
//!
//! ```rust
//! use dotnetdll::prelude::*;
//! # let mut res = Resolution::new(Module::new("test"));
//! # let type_idx = res.type_definition_index(0).unwrap();
//! # let field = res.push_field(type_idx, Field::static_member(Accessibility::Public, "test", ctype! { bool }));
//!
//! // Labels are defined with `@label_name` on an instruction and used by name
//! let body = asm! {
//!     LoadConstantInt32 0;
//!     BranchFalsy else_branch;
//!     LoadString "condition was true".encode_utf16().collect();
//!     Branch end;
//!     @else_branch NoOperation;
//!     LoadString "condition was false".encode_utf16().collect();
//!     @end Return;
//! };
//! ```
//!
//! ## Conditional branching
//!
//! ```rust
//! use dotnetdll::prelude::*;
//!
//! // Compare two values and branch
//! let (instructions, loop_start, loop_end) = asm! {
//!     + loop_start NoOperation;
//!     LoadLocal 0;           // Load loop counter
//!     LoadConstantInt32 10;
//!     BranchLess NumberSign::Signed, loop_end;
//!
//!     // Loop body here
//!     LoadLocal 0;
//!     LoadConstantInt32 1;
//!     Add;
//!     StoreLocal 0;
//!
//!     Branch loop_start;
//!     + loop_end Return;
//! };
//! ```

use super::{members::*, signature, types::*, ResolvedDebug};
use crate::resolution::Resolution;

use dotnetdll_macros::r_instructions;
use num_derive::FromPrimitive;

#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum NumberSign {
    Signed,
    Unsigned,
}

#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum OverflowDetection {
    Check,
    NoCheck,
}

#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum ConversionType {
    Int8,
    UInt8,
    Int16,
    UInt16,
    Int32,
    UInt32,
    Int64,
    UInt64,
    IntPtr,
    UIntPtr,
}

#[derive(Debug, Copy, Clone, FromPrimitive, Eq, PartialEq)]
pub enum Alignment {
    Byte = 1,
    Double = 2,
    Quad = 4,
}

#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum LoadType {
    Int8,
    UInt8,
    Int16,
    UInt16,
    Int32,
    UInt32,
    Int64,
    Float32,
    Float64,
    IntPtr,
    Object,
}

#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum StoreType {
    Int8,
    Int16,
    Int32,
    Int64,
    Float32,
    Float64,
    IntPtr,
    Object,
}

type Flag = Option<String>;
trait InstructionFlag {
    fn show(self) -> Flag;
}
impl InstructionFlag for Option<Alignment> {
    fn show(self) -> Flag {
        self.map(|a| format!("aligned({:?})", a))
    }
}
impl InstructionFlag for (&'static str, bool) {
    fn show(self) -> Flag {
        if self.1 {
            Some(self.0.to_string())
        } else {
            None
        }
    }
}
fn show_flags(flags: impl IntoIterator<Item = Flag>) -> String {
    let set_flags: Vec<_> = flags.into_iter().flatten().collect();
    if set_flags.is_empty() {
        String::new()
    } else {
        format!("[{}]", set_flags.join(", "))
    }
}

trait InstructionShow {
    fn show(&self, _res: &Resolution) -> String;
}
impl<T: ResolvedDebug> InstructionShow for T {
    fn show(&self, res: &Resolution) -> String {
        self.show(res)
    }
}
macro_rules! impl_debug {
    ($($t:ty),*) => {
        $(
            impl InstructionShow for $t {
                fn show(&self, _res: &Resolution) -> String {
                    format!("{:?}", self)
                }
            }
        )*
    }
}
// can't just impl<T: Debug> since no specialization yet
impl_debug!(
    NumberSign,
    ConversionType,
    LoadType,
    StoreType,
    u16,
    i32,
    i64,
    f32,
    f64,
    usize,
    Vec<usize>
);
// special impl: UTF-16 LoadString
impl InstructionShow for Vec<u16> {
    fn show(&self, _res: &Resolution) -> String {
        format!("{:?}", String::from_utf16_lossy(self))
    }
}

r_instructions! {
    Add,
    AddOverflow(NumberSign),
    And,
    ArgumentList,
    BranchEqual(usize),
    BranchGreaterOrEqual(NumberSign, usize),
    BranchGreater(NumberSign, usize),
    BranchLessOrEqual(NumberSign, usize),
    BranchLess(NumberSign, usize),
    BranchNotEqual(usize),
    Branch(usize),
    Breakpoint,
    BranchFalsy(usize),
    BranchTruthy(usize),
    #[flags(tail_call)]
    Call(MethodSource),
    CallConstrained(MethodType, MethodSource),
    #[flags(tail_call)]
    CallIndirect(signature::MaybeUnmanagedMethod<MethodType>),
    CompareEqual,
    CompareGreater(NumberSign),
    CheckFinite,
    CompareLess(NumberSign),
    Convert(ConversionType),
    ConvertOverflow(ConversionType, NumberSign),
    ConvertFloat32,
    ConvertFloat64,
    ConvertUnsignedToFloat,
    #[flags(unaligned, volatile)]
    CopyMemoryBlock,
    Divide(NumberSign),
    Duplicate,
    EndFilter,
    EndFinally,
    #[flags(unaligned, volatile)]
    InitializeMemoryBlock,
    Jump(MethodSource),
    LoadArgument(u16),
    LoadArgumentAddress(u16),
    LoadConstantInt32(i32),
    LoadConstantInt64(i64),
    LoadConstantFloat32(f32),
    LoadConstantFloat64(f64),
    LoadMethodPointer(MethodSource),
    #[flags(unaligned, volatile)]
    LoadIndirect(LoadType),
    LoadLocal(u16),
    LoadLocalAddress(u16),
    LoadNull,
    Leave(usize),
    LocalMemoryAllocate,
    Multiply,
    MultiplyOverflow(NumberSign),
    Negate,
    NoOperation,
    Not,
    Or,
    Pop,
    Remainder(NumberSign),
    Return,
    ShiftLeft,
    ShiftRight(NumberSign),
    StoreArgument(u16),
    #[flags(unaligned, volatile)]
    StoreIndirect(StoreType),
    StoreLocal(u16),
    Subtract,
    SubtractOverflow(NumberSign),
    Switch(Vec<usize>),
    Xor,

    BoxValue(MethodType),
    #[flags(null)]
    CallVirtual(MethodSource),
    CallVirtualConstrained(MethodType, MethodSource),
    CallVirtualTail(MethodSource),
    #[flags(type)]
    CastClass(MethodType),
    CopyObject(MethodType),
    InitializeForObject(MethodType),
    IsInstance(MethodType),
    #[flags(range, null)]
    LoadElement(MethodType),
    #[flags(range, null)]
    LoadElementPrimitive(LoadType),
    #[flags(type, range, null)]
    LoadElementAddress(MethodType),
    LoadElementAddressReadonly(MethodType),
    #[flags(unaligned, volatile)]
    LoadField(FieldSource),
    LoadFieldAddress(FieldSource),
    LoadFieldSkipNullCheck(FieldSource),
    LoadLength,
    #[flags(unaligned, volatile)]
    LoadObject(MethodType),
    #[flags(volatile)]
    LoadStaticField(FieldSource),
    LoadStaticFieldAddress(FieldSource),
    #[skip_constructor]
    LoadString(Vec<u16>),
    LoadTokenField(FieldSource),
    LoadTokenMethod(MethodSource),
    LoadTokenType(MethodType),
    #[flags(null)]
    LoadVirtualMethodPointer(MethodSource),
    MakeTypedReference(MethodType),
    NewArray(MethodType),
    NewObject(UserMethod),
    ReadTypedReferenceType,
    ReadTypedReferenceValue(MethodType),
    Rethrow,
    Sizeof(MethodType),
    #[flags(type, range, null)]
    StoreElement(MethodType),
    #[flags(type, range, null)]
    StoreElementPrimitive(StoreType),
    #[flags(unaligned, volatile)]
    StoreField(FieldSource),
    StoreFieldSkipNullCheck(FieldSource),
    #[flags(unaligned, volatile)]
    StoreObject(MethodType),
    #[flags(volatile)]
    StoreStaticField(FieldSource),
    Throw,
    #[flags(type)]
    UnboxIntoAddress(MethodType),
    UnboxIntoValue(MethodType)
}

impl Instruction {
    pub fn load_string(s: impl AsRef<str>) -> Self {
        Instruction::LoadString(s.as_ref().encode_utf16().collect())
    }
}

#[macro_export]
macro_rules! asm {
    ($ins:ident) => {
        Instruction::$ins
    };
    ($ins:ident $($param:expr),+) => {
        Instruction::$ins($($param),+)
    };
    ($($(@ $label:ident)? $(+ $label_export:ident)? $ins:ident $($param:expr),*;)*) => {{
        let mut _counter = 0;
        $(
            $(let $label = _counter;)?
            $(let $label_export = _counter;)?
            _counter += 1;
        )*

        let _ins = vec![
            $(
                $crate::asm! { $ins $($param),* }
            ),*
        ];

        (_ins
            $(
                $(
                    ,$label_export
                )?
            )*
        )
    }};
}
