use strum::{Display, EnumString};

use crate::prelude::Register;

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Location {
    offset: u16,
    register: Option<Register>,
    label: Option<String>,
}

impl Location {
    pub fn new(offset: u16, register: Option<Register>, label: Option<String>) -> Self {
        Location {
            offset,
            register,
            label,
        }
    }

    // need implementation to intelligently figure out if we need to load immediates
    // so if the label is a const and > u16::max, we do Lui $label temp, then use temp as an offset
    // if a register is being used already we have to add the offset to that.
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ImmConst {
    Label(String),
    Imm16(u16),
    Imm32(u32),
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumString, Display)]
#[strum(ascii_case_insensitive)]
pub enum AsmOpcode {
    Nop,

    Mov,
    CMov,

    Ldb,
    Ldbs,
    Ldh,
    Ldhs,
    Ldw,

    Stb,
    Sth,
    Stw,

    Lli,
    Lui,
    Lwi,

    Jmp,
    Jez,
    Jnz,
    Jic,
    Jnc,

    Ieq,
    Ine,
    Igt,
    Ige,
    Ilt,
    Ile,

    Shl,
    Shr,
    Add,
    Sub,
    AddI,
    SubI,

    And,
    Or,
    Not,
    Xor,
    Nand,
    Nor,
    Xnor,

    Int,
    IRet,
    Hlt,

    // Function instructions
    Call,
    Ret,

    // Stack ops
    Push,
    Pop,

    // data directives
    Db,
    Dh,
    Dw,

    // data reservations
    Resb,
    Resh,
    Resw,

    // Function pseudo-instructions
    Func,
    Return,

    // include directive
    Include,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AsmInstruction {
    Nop,

    // move
    Mov {
        src: Register,
        dest: Register,
    },
    CMov {
        src: Register,
        dest: Register,
        condition: Register,
    },

    // load
    Ldb {
        addr: Location,
        dest: Register,
    },
    Ldbs {
        addr: Location,
        dest: Register,
    },
    Ldh {
        addr: Location,
        dest: Register,
    },
    Ldhs {
        addr: Location,
        dest: Register,
    },
    Ldw {
        addr: Location,
        dest: Register,
    },

    // store
    Stb {
        src: Register,
        addr: Location,
    },
    Sth {
        src: Register,
        addr: Location,
    },
    Stw {
        src: Register,
        addr: Location,
    },

    // load immediate
    Lli {
        dest: Register,
        value: ImmConst,
    },
    Lui {
        dest: Register,
        value: ImmConst,
    },
    Lwi {
        dest: Register,
        value: ImmConst,
    },

    // comparison
    Ieq {
        src1: Register,
        src2: Register,
        dest: Register,
    },
    Ine {
        src1: Register,
        src2: Register,
        dest: Register,
    },
    Ilt {
        src1: Register,
        src2: Register,
        dest: Register,
    },
    Ile {
        src1: Register,
        src2: Register,
        dest: Register,
    },
    Igt {
        src1: Register,
        src2: Register,
        dest: Register,
    },
    Ige {
        src1: Register,
        src2: Register,
        dest: Register,
    },

    // jump
    Jmp {
        dest: Location,
    },
    Jez {
        dest: Location,
        cond: Register,
    },
    Jnz {
        dest: Location,
        cond: Register,
    },
    Jic {
        dest: Location,
    },
    Jnc {
        dest: Location,
    },

    // bitwise
    And {
        src1: Register,
        src2: Register,
        dest: Option<Register>,
    },
    Nand {
        src1: Register,
        src2: Register,
        dest: Option<Register>,
    },
    Or {
        src1: Register,
        src2: Register,
        dest: Option<Register>,
    },
    Nor {
        src1: Register,
        src2: Register,
        dest: Register,
    },
    Xor {
        src1: Register,
        src2: Register,
        dest: Option<Register>,
    },
    Xnor {
        src1: Register,
        src2: Register,
        dest: Option<Register>,
    },
    Not {
        src: Register,
        dest: Option<Register>,
    },

    // arithmetic
    Add {
        src1: Register,
        src2: Register,
        dest: Option<Register>,
    },
    Sub {
        src1: Register,
        src2: Register,
        dest: Option<Register>,
    },
    Shl {
        src: Register,
        shamt_r: Option<Register>,
        shamt_i: Option<ImmConst>,
        dest: Option<Register>,
    },
    Shr {
        src: Register,
        shamt_r: Option<Register>,
        shamt_i: Option<ImmConst>,
        dest: Option<Register>,
    },
    Addi {
        src: Register,
        value: ImmConst,
        dest: Option<Register>,
    },
    Subi {
        src: Register,
        value: ImmConst,
        dest: Option<Register>,
    },

    // utility
    Push {
        src: Register,
    },
    Pop {
        dest: Register,
    },
    Call {
        dest: Location,
    },
    Ret,

    // system
    Int {
        code: ImmConst,
    },
    IRet,
    Hlt,
}
