use std::fmt;

/// This operand represents a register.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Ord, PartialOrd)]
pub struct A64Reg
{
    // Size in bits
    pub num_bits: u8,

    // Register index number
    pub reg_no: u8,
}

impl A64Reg {
    pub fn with_num_bits(&self, num_bits: u8) -> Self {
        assert!(num_bits == 8 || num_bits == 16 || num_bits == 32 || num_bits == 64);
        Self { num_bits, reg_no: self.reg_no }
    }
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub struct A64Mem
{
    // Size in bits
    pub num_bits: u8,

    /// Base register number
    pub base_reg_no: u8,

    /// Constant displacement from the base, not scaled
    pub disp: i32,
}

impl A64Mem {
    pub fn new(num_bits: u8, reg: A64Opnd, disp: i32) -> Self {
        match reg {
            A64Opnd::Reg(reg) => {
                Self { num_bits, base_reg_no: reg.reg_no, disp }
            },
            _ => panic!("Expected register operand")
        }
    }
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub enum A64Opnd
{
    // Dummy operand
    None,

    // Immediate value
    Imm(i64),

    // Unsigned immediate
    UImm(u64),

    // Register
    Reg(A64Reg),

    // Memory
    Mem(A64Mem)
}

impl A64Opnd {
    /// Create a new immediate value operand.
    pub fn new_imm(value: i64) -> Self {
        A64Opnd::Imm(value)
    }

    /// Create a new unsigned immediate value operand.
    pub fn new_uimm(value: u64) -> Self {
        A64Opnd::UImm(value)
    }

    /// Creates a new memory operand.
    pub fn new_mem(num_bits: u8, reg: A64Opnd, disp: i32) -> Self {
        A64Opnd::Mem(A64Mem::new(num_bits, reg, disp))
    }

    /// Convenience function to check if this operand is a register.
    pub fn is_reg(&self) -> bool {
        matches!(self, A64Opnd::Reg(_))
    }

    /// Unwrap a register from an operand.
    pub fn unwrap_reg(&self) -> A64Reg {
        match self {
            A64Opnd::Reg(reg) => *reg,
            _ => panic!("Expected register operand")
        }
    }
}

// argument registers
pub const X0_REG: A64Reg = A64Reg { num_bits: 64, reg_no: 0 };
pub const X1_REG: A64Reg = A64Reg { num_bits: 64, reg_no: 1 };
pub const X2_REG: A64Reg = A64Reg { num_bits: 64, reg_no: 2 };
pub const X3_REG: A64Reg = A64Reg { num_bits: 64, reg_no: 3 };
pub const X4_REG: A64Reg = A64Reg { num_bits: 64, reg_no: 4 };
pub const X5_REG: A64Reg = A64Reg { num_bits: 64, reg_no: 5 };

// caller-save registers
pub const X9_REG: A64Reg = A64Reg { num_bits: 64, reg_no: 9 };
pub const X10_REG: A64Reg = A64Reg { num_bits: 64, reg_no: 10 };
pub const X11_REG: A64Reg = A64Reg { num_bits: 64, reg_no: 11 };
pub const X12_REG: A64Reg = A64Reg { num_bits: 64, reg_no: 12 };
pub const X13_REG: A64Reg = A64Reg { num_bits: 64, reg_no: 13 };
pub const X14_REG: A64Reg = A64Reg { num_bits: 64, reg_no: 14 };
pub const X15_REG: A64Reg = A64Reg { num_bits: 64, reg_no: 15 };
pub const X16_REG: A64Reg = A64Reg { num_bits: 64, reg_no: 16 };
pub const X17_REG: A64Reg = A64Reg { num_bits: 64, reg_no: 17 };

// callee-save registers
pub const X19_REG: A64Reg = A64Reg { num_bits: 64, reg_no: 19 };
pub const X20_REG: A64Reg = A64Reg { num_bits: 64, reg_no: 20 };
pub const X21_REG: A64Reg = A64Reg { num_bits: 64, reg_no: 21 };
pub const X22_REG: A64Reg = A64Reg { num_bits: 64, reg_no: 22 };

// frame pointer (base pointer)
pub const X29_REG: A64Reg = A64Reg { num_bits: 64, reg_no: 29 };

// link register
pub const X30_REG: A64Reg = A64Reg { num_bits: 64, reg_no: 30 };

// zero register
pub const XZR_REG: A64Reg = A64Reg { num_bits: 64, reg_no: 31 };

// 64-bit registers
pub const X0: A64Opnd = A64Opnd::Reg(X0_REG);
pub const X1: A64Opnd = A64Opnd::Reg(X1_REG);
pub const X2: A64Opnd = A64Opnd::Reg(X2_REG);
pub const X3: A64Opnd = A64Opnd::Reg(X3_REG);
pub const X4: A64Opnd = A64Opnd::Reg(X4_REG);
pub const X5: A64Opnd = A64Opnd::Reg(X5_REG);
pub const X6: A64Opnd = A64Opnd::Reg(A64Reg { num_bits: 64, reg_no: 6 });
pub const X7: A64Opnd = A64Opnd::Reg(A64Reg { num_bits: 64, reg_no: 7 });
pub const X8: A64Opnd = A64Opnd::Reg(A64Reg { num_bits: 64, reg_no: 8 });
pub const X9: A64Opnd = A64Opnd::Reg(X9_REG);
pub const X10: A64Opnd = A64Opnd::Reg(X10_REG);
pub const X11: A64Opnd = A64Opnd::Reg(X11_REG);
pub const X12: A64Opnd = A64Opnd::Reg(X12_REG);
pub const X13: A64Opnd = A64Opnd::Reg(X13_REG);
pub const X14: A64Opnd = A64Opnd::Reg(X14_REG);
pub const X15: A64Opnd = A64Opnd::Reg(X15_REG);
pub const X16: A64Opnd = A64Opnd::Reg(A64Reg { num_bits: 64, reg_no: 16 });
pub const X17: A64Opnd = A64Opnd::Reg(A64Reg { num_bits: 64, reg_no: 17 });
pub const X18: A64Opnd = A64Opnd::Reg(A64Reg { num_bits: 64, reg_no: 18 });
pub const X19: A64Opnd = A64Opnd::Reg(X19_REG);
pub const X20: A64Opnd = A64Opnd::Reg(X20_REG);
pub const X21: A64Opnd = A64Opnd::Reg(X21_REG);
pub const X22: A64Opnd = A64Opnd::Reg(X22_REG);
pub const X23: A64Opnd = A64Opnd::Reg(A64Reg { num_bits: 64, reg_no: 23 });
pub const X24: A64Opnd = A64Opnd::Reg(A64Reg { num_bits: 64, reg_no: 24 });
pub const X25: A64Opnd = A64Opnd::Reg(A64Reg { num_bits: 64, reg_no: 25 });
pub const X26: A64Opnd = A64Opnd::Reg(A64Reg { num_bits: 64, reg_no: 26 });
pub const X27: A64Opnd = A64Opnd::Reg(A64Reg { num_bits: 64, reg_no: 27 });
pub const X28: A64Opnd = A64Opnd::Reg(A64Reg { num_bits: 64, reg_no: 28 });
pub const X29: A64Opnd = A64Opnd::Reg(A64Reg { num_bits: 64, reg_no: 29 });
pub const X30: A64Opnd = A64Opnd::Reg(X30_REG);
pub const X31: A64Opnd = A64Opnd::Reg(XZR_REG);

// 32-bit registers
pub const W0: A64Opnd = A64Opnd::Reg(A64Reg { num_bits: 32, reg_no: 0 });
pub const W1: A64Opnd = A64Opnd::Reg(A64Reg { num_bits: 32, reg_no: 1 });
pub const W2: A64Opnd = A64Opnd::Reg(A64Reg { num_bits: 32, reg_no: 2 });
pub const W3: A64Opnd = A64Opnd::Reg(A64Reg { num_bits: 32, reg_no: 3 });
pub const W4: A64Opnd = A64Opnd::Reg(A64Reg { num_bits: 32, reg_no: 4 });
pub const W5: A64Opnd = A64Opnd::Reg(A64Reg { num_bits: 32, reg_no: 5 });
pub const W6: A64Opnd = A64Opnd::Reg(A64Reg { num_bits: 32, reg_no: 6 });
pub const W7: A64Opnd = A64Opnd::Reg(A64Reg { num_bits: 32, reg_no: 7 });
pub const W8: A64Opnd = A64Opnd::Reg(A64Reg { num_bits: 32, reg_no: 8 });
pub const W9: A64Opnd = A64Opnd::Reg(A64Reg { num_bits: 32, reg_no: 9 });
pub const W10: A64Opnd = A64Opnd::Reg(A64Reg { num_bits: 32, reg_no: 10 });
pub const W11: A64Opnd = A64Opnd::Reg(A64Reg { num_bits: 32, reg_no: 11 });
pub const W12: A64Opnd = A64Opnd::Reg(A64Reg { num_bits: 32, reg_no: 12 });
pub const W13: A64Opnd = A64Opnd::Reg(A64Reg { num_bits: 32, reg_no: 13 });
pub const W14: A64Opnd = A64Opnd::Reg(A64Reg { num_bits: 32, reg_no: 14 });
pub const W15: A64Opnd = A64Opnd::Reg(A64Reg { num_bits: 32, reg_no: 15 });
pub const W16: A64Opnd = A64Opnd::Reg(A64Reg { num_bits: 32, reg_no: 16 });
pub const W17: A64Opnd = A64Opnd::Reg(A64Reg { num_bits: 32, reg_no: 17 });
pub const W18: A64Opnd = A64Opnd::Reg(A64Reg { num_bits: 32, reg_no: 18 });
pub const W19: A64Opnd = A64Opnd::Reg(A64Reg { num_bits: 32, reg_no: 19 });
pub const W20: A64Opnd = A64Opnd::Reg(A64Reg { num_bits: 32, reg_no: 20 });
pub const W21: A64Opnd = A64Opnd::Reg(A64Reg { num_bits: 32, reg_no: 21 });
pub const W22: A64Opnd = A64Opnd::Reg(A64Reg { num_bits: 32, reg_no: 22 });
pub const W23: A64Opnd = A64Opnd::Reg(A64Reg { num_bits: 32, reg_no: 23 });
pub const W24: A64Opnd = A64Opnd::Reg(A64Reg { num_bits: 32, reg_no: 24 });
pub const W25: A64Opnd = A64Opnd::Reg(A64Reg { num_bits: 32, reg_no: 25 });
pub const W26: A64Opnd = A64Opnd::Reg(A64Reg { num_bits: 32, reg_no: 26 });
pub const W27: A64Opnd = A64Opnd::Reg(A64Reg { num_bits: 32, reg_no: 27 });
pub const W28: A64Opnd = A64Opnd::Reg(A64Reg { num_bits: 32, reg_no: 28 });
pub const W29: A64Opnd = A64Opnd::Reg(A64Reg { num_bits: 32, reg_no: 29 });
pub const W30: A64Opnd = A64Opnd::Reg(A64Reg { num_bits: 32, reg_no: 30 });
pub const W31: A64Opnd = A64Opnd::Reg(A64Reg { num_bits: 32, reg_no: 31 });

// C argument registers
pub const C_ARG_REGS: [A64Opnd; 6] = [X0, X1, X2, X3, X4, X5];
pub const C_ARG_REGREGS: [A64Reg; 6] = [X0_REG, X1_REG, X2_REG, X3_REG, X4_REG, X5_REG];

impl fmt::Display for A64Reg {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match A64Opnd::Reg(*self) {
            X0  => write!(f, "x0"),
            X1  => write!(f, "x1"),
            X2  => write!(f, "x2"),
            X3  => write!(f, "x3"),
            X4  => write!(f, "x4"),
            X5  => write!(f, "x5"),
            X6  => write!(f, "x6"),
            X7  => write!(f, "x7"),
            X8  => write!(f, "x8"),
            X9  => write!(f, "x9"),
            X10 => write!(f, "x10"),
            X11 => write!(f, "x11"),
            X12 => write!(f, "x12"),
            X13 => write!(f, "x13"),
            X14 => write!(f, "x14"),
            X15 => write!(f, "x15"),
            X16 => write!(f, "x16"),
            X17 => write!(f, "x17"),
            X18 => write!(f, "x18"),
            X19 => write!(f, "x19"),
            X20 => write!(f, "x20"),
            X21 => write!(f, "x21"),
            X22 => write!(f, "x22"),
            X23 => write!(f, "x23"),
            X24 => write!(f, "x24"),
            X25 => write!(f, "x25"),
            X26 => write!(f, "x26"),
            X27 => write!(f, "x27"),
            X28 => write!(f, "x28"),
            X29 => write!(f, "x29"),
            X30 => write!(f, "x30"),
            X31 => write!(f, "x31"),
            W0  => write!(f, "w0"),
            W1  => write!(f, "w1"),
            W2  => write!(f, "w2"),
            W3  => write!(f, "w3"),
            W4  => write!(f, "w4"),
            W5  => write!(f, "w5"),
            W6  => write!(f, "w6"),
            W7  => write!(f, "w7"),
            W8  => write!(f, "w8"),
            W9  => write!(f, "w9"),
            W10 => write!(f, "w10"),
            W11 => write!(f, "w11"),
            W12 => write!(f, "w12"),
            W13 => write!(f, "w13"),
            W14 => write!(f, "w14"),
            W15 => write!(f, "w15"),
            W16 => write!(f, "w16"),
            W17 => write!(f, "w17"),
            W18 => write!(f, "w18"),
            W19 => write!(f, "w19"),
            W20 => write!(f, "w20"),
            W21 => write!(f, "w21"),
            W22 => write!(f, "w22"),
            W23 => write!(f, "w23"),
            W24 => write!(f, "w24"),
            W25 => write!(f, "w25"),
            W26 => write!(f, "w26"),
            W27 => write!(f, "w27"),
            W28 => write!(f, "w28"),
            W29 => write!(f, "w29"),
            W30 => write!(f, "w30"),
            W31 => write!(f, "w31"),
            _ => write!(f, "{self:?}"),
        }
    }
}
