//! Value-producing operation types and SSA value metadata for Slonik IR.

use core::fmt;

use crate::ir::{Block, Stmt, Type};

/// Memory access width in bytes.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum MemSize {
    S1,
    S2,
    S4,
    S8,
    S16,
}

impl MemSize {
    /// Returns the access width in bytes.
    pub const fn bytes(self) -> u8 {
        match self {
            Self::S1 => 1,
            Self::S2 => 2,
            Self::S4 => 4,
            Self::S8 => 8,
            Self::S16 => 16,
        }
    }

    /// Returns the access width in bits.
    pub const fn bits(self) -> u16 {
        (self.bytes() as u16) * 8
    }
}

impl fmt::Display for MemSize {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}B", self.bytes())
    }
}

/// Integer comparison condition codes.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum IntCC {
    Eq,
    Ne,

    Ult,
    Ule,
    Ugt,
    Uge,

    Slt,
    Sle,
    Sgt,
    Sge,
}

impl IntCC {
    /// Returns the logical inverse of this condition code.
    pub const fn invert(self) -> Self {
        match self {
            Self::Eq => Self::Ne,
            Self::Ne => Self::Eq,

            Self::Ult => Self::Uge,
            Self::Ule => Self::Ugt,
            Self::Ugt => Self::Ule,
            Self::Uge => Self::Ult,

            Self::Slt => Self::Sge,
            Self::Sle => Self::Sgt,
            Self::Sgt => Self::Sle,
            Self::Sge => Self::Slt,
        }
    }

    /// Returns the condition code obtained by swapping the operands.
    pub const fn swap_args(self) -> Self {
        match self {
            Self::Eq => Self::Eq,
            Self::Ne => Self::Ne,

            Self::Ult => Self::Ugt,
            Self::Ule => Self::Uge,
            Self::Ugt => Self::Ult,
            Self::Uge => Self::Ule,

            Self::Slt => Self::Sgt,
            Self::Sle => Self::Sge,
            Self::Sgt => Self::Slt,
            Self::Sge => Self::Sle,
        }
    }
}

impl fmt::Display for IntCC {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let s = match self {
            Self::Eq => "eq",
            Self::Ne => "ne",

            Self::Ult => "ult",
            Self::Ule => "ule",
            Self::Ugt => "ugt",
            Self::Uge => "uge",

            Self::Slt => "slt",
            Self::Sle => "sle",
            Self::Sgt => "sgt",
            Self::Sge => "sge",
        };

        f.write_str(s)
    }
}

/// Floating-point comparison condition codes.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum FloatCC {
    Eq,
    Ne,
    Lt,
    Le,
    Gt,
    Ge,
    Ordered,
    Unordered,
}

impl FloatCC {
    /// Returns the logical inverse of this condition code.
    pub const fn invert(self) -> Self {
        match self {
            Self::Eq => Self::Ne,
            Self::Ne => Self::Eq,
            Self::Lt => Self::Ge,
            Self::Le => Self::Gt,
            Self::Gt => Self::Le,
            Self::Ge => Self::Lt,
            Self::Ordered => Self::Unordered,
            Self::Unordered => Self::Ordered,
        }
    }

    /// Returns the condition code obtained by swapping the operands.
    pub const fn swap_args(self) -> Self {
        match self {
            Self::Eq => Self::Eq,
            Self::Ne => Self::Ne,
            Self::Lt => Self::Gt,
            Self::Le => Self::Ge,
            Self::Gt => Self::Lt,
            Self::Ge => Self::Le,
            Self::Ordered => Self::Ordered,
            Self::Unordered => Self::Unordered,
        }
    }
}

impl fmt::Display for FloatCC {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let s = match self {
            Self::Eq => "eq",
            Self::Ne => "ne",
            Self::Lt => "lt",
            Self::Le => "le",
            Self::Gt => "gt",
            Self::Ge => "ge",
            Self::Ordered => "ord",
            Self::Unordered => "uno",
        };

        f.write_str(s)
    }
}

/// Unary expression operators.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum UnaryOp {
    /// Integer negation.
    Neg,

    /// Bitwise not.
    Not,

    /// Floating-point negation.
    FNeg,
}

impl fmt::Display for UnaryOp {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let s = match self {
            Self::Neg => "neg",
            Self::Not => "not",
            Self::FNeg => "fneg",
        };

        f.write_str(s)
    }
}

/// Binary expression operators.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum BinaryOp {
    IAdd,
    ISub,
    IMul,
    UDiv,
    SDiv,
    URem,
    SRem,

    And,
    Or,
    Xor,

    Shl,
    LShr,
    AShr,

    FAdd,
    FSub,
    FMul,
    FDiv,
}

impl BinaryOp {
    /// Returns whether this binary operator is commutative.
    pub const fn is_commutative(self) -> bool {
        matches!(
            self,
            Self::IAdd | Self::IMul | Self::And | Self::Or | Self::Xor | Self::FAdd | Self::FMul
        )
    }
}

impl fmt::Display for BinaryOp {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let s = match self {
            Self::IAdd => "iadd",
            Self::ISub => "isub",
            Self::IMul => "imul",
            Self::UDiv => "udiv",
            Self::SDiv => "sdiv",
            Self::URem => "urem",
            Self::SRem => "srem",

            Self::And => "and",
            Self::Or => "or",
            Self::Xor => "xor",

            Self::Shl => "shl",
            Self::LShr => "lshr",
            Self::AShr => "ashr",

            Self::FAdd => "fadd",
            Self::FSub => "fsub",
            Self::FMul => "fmul",
            Self::FDiv => "fdiv",
        };

        f.write_str(s)
    }
}

/// Cast and conversion operators.
///
/// The source type is taken from the operand value.
/// The destination type is taken from the result value.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum CastOp {
    /// Zero-extension.
    Zext,

    /// Sign-extension.
    Sext,

    /// Truncation.
    Trunc,

    /// Bit-preserving reinterpretation.
    Bitcast,

    /// Integer-to-pointer conversion.
    IntToPtr,

    /// Pointer-to-integer conversion.
    PtrToInt,
}

impl fmt::Display for CastOp {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let s = match self {
            Self::Zext => "zext",
            Self::Sext => "sext",
            Self::Trunc => "trunc",
            Self::Bitcast => "bitcast",
            Self::IntToPtr => "inttoptr",
            Self::PtrToInt => "ptrtoint",
        };

        f.write_str(s)
    }
}

/// The definition site of an SSA value.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum ValueDef {
    /// A value defined by a statement.
    Inst(Stmt),

    /// A block parameter at position `index`.
    Param(Block, u16),
}

/// Metadata attached to an SSA value.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct ValueData {
    /// The semantic type of the value.
    pub ty: Type,

    /// The definition site of the value.
    pub def: ValueDef,
}
