from functools import lru_cache
from typing import Any

from src.arch.z80.backend.exception import InvalidICError

__all__ = ("ICInstruction",)


# HINT: Do not use Enums here. They cannot be subclassed
class ICInstruction:
    ADDU8 = "addu8"
    ADDI8 = "addi8"
    ADDI16 = "addi16"
    ADDU16 = "addu16"
    ADDI32 = "addi32"
    ADDU32 = "addu32"
    ADDF16 = "addf16"
    ADDF = "addf"
    ADDSTR = "addstr"
    DATA = "data"
    SUBI8 = "subi8"
    SUBU8 = "subu8"
    SUBI16 = "subi16"
    SUBU16 = "subu16"
    SUBI32 = "subi32"
    SUBU32 = "subu32"
    SUBF16 = "subf16"
    SUBF = "subf"
    MULI8 = "muli8"
    MULU8 = "mulu8"
    MULI16 = "muli16"
    MULU16 = "mulu16"
    MULI32 = "muli32"
    MULU32 = "mulu32"
    MULF16 = "mulf16"
    MULF = "mulf"
    DIVU8 = "divu8"
    DIVI8 = "divi8"
    DIVU16 = "divu16"
    DIVI16 = "divi16"
    DIVU32 = "divu32"
    DIVI32 = "divi32"
    DIVF16 = "divf16"
    DIVF = "divf"
    POWF = "powf"
    MODU8 = "modu8"
    MODI8 = "modi8"
    MODU16 = "modu16"
    MODI16 = "modi16"
    MODU32 = "modu32"
    MODI32 = "modi32"
    MODF16 = "modf16"
    MODF = "modf"
    SHRU8 = "shru8"
    SHRI8 = "shri8"
    SHLU8 = "shlu8"
    SHLI8 = "shli8"
    SHRU16 = "shru16"
    SHRI16 = "shri16"
    SHLU16 = "shlu16"
    SHLI16 = "shli16"
    SHRU32 = "shru32"
    SHRI32 = "shri32"
    SHLU32 = "shlu32"
    SHLI32 = "shli32"
    LTU8 = "ltu8"
    LTI8 = "lti8"
    LTU16 = "ltu16"
    LTI16 = "lti16"
    LTU32 = "ltu32"
    LTI32 = "lti32"
    LTF16 = "ltf16"
    LTF = "ltf"
    LTSTR = "ltstr"
    GTU8 = "gtu8"
    GTI8 = "gti8"
    GTU16 = "gtu16"
    GTI16 = "gti16"
    GTU32 = "gtu32"
    GTI32 = "gti32"
    GTF16 = "gtf16"
    GTF = "gtf"
    GTSTR = "gtstr"
    LEU8 = "leu8"
    LEI8 = "lei8"
    LEU16 = "leu16"
    LEI16 = "lei16"
    LEU32 = "leu32"
    LEI32 = "lei32"
    LEF16 = "lef16"
    LEF = "lef"
    LESTR = "lestr"
    GEU8 = "geu8"
    GEI8 = "gei8"
    GEU16 = "geu16"
    GEI16 = "gei16"
    GEU32 = "geu32"
    GEI32 = "gei32"
    GEF16 = "gef16"
    GEF = "gef"
    GESTR = "gestr"
    EQU8 = "equ8"
    EQI8 = "eqi8"
    EQU16 = "equ16"
    EQI16 = "eqi16"
    EQU32 = "equ32"
    EQI32 = "eqi32"
    EQF16 = "eqf16"
    EQF = "eqf"
    EQSTR = "eqstr"
    NEU8 = "neu8"
    NEI8 = "nei8"
    NEU16 = "neu16"
    NEI16 = "nei16"
    NEU32 = "neu32"
    NEI32 = "nei32"
    NEF16 = "nef16"
    NEF = "nef"
    NESTR = "nestr"
    ABSI8 = "absi8"
    ABSI16 = "absi16"
    ABSI32 = "absi32"
    ABSF16 = "absf16"
    ABSF = "absf"
    NEGU8 = "negu8"
    NEGI8 = "negi8"
    NEGU16 = "negu16"
    NEGI16 = "negi16"
    NEGU32 = "negu32"
    NEGI32 = "negi32"
    NEGF16 = "negf16"
    NEGF = "negf"
    ANDU8 = "andu8"
    ANDI8 = "andi8"
    ANDU16 = "andu16"
    ANDI16 = "andi16"
    ANDU32 = "andu32"
    ANDI32 = "andi32"
    ANDF16 = "andf16"
    ANDF = "andf"
    ORU8 = "oru8"
    ORI8 = "ori8"
    ORU16 = "oru16"
    ORI16 = "ori16"
    ORU32 = "oru32"
    ORI32 = "ori32"
    ORF16 = "orf16"
    ORF = "orf"
    XORU8 = "xoru8"
    XORI8 = "xori8"
    XORU16 = "xoru16"
    XORI16 = "xori16"
    XORU32 = "xoru32"
    XORI32 = "xori32"
    XORF16 = "xorf16"
    XORF = "xorf"
    NOTU8 = "notu8"
    NOTI8 = "noti8"
    NOTU16 = "notu16"
    NOTI16 = "noti16"
    NOTU32 = "notu32"
    NOTI32 = "noti32"
    NOTF16 = "notf16"
    NOTF = "notf"
    JUMP = "jump"
    LENSTR = "lenstr"
    JZEROI8 = "jzeroi8"
    JZEROU8 = "jzerou8"
    JZEROI16 = "jzeroi16"
    JZEROU16 = "jzerou16"
    JZEROI32 = "jzeroi32"
    JZEROU32 = "jzerou32"
    JZEROF16 = "jzerof16"
    JZEROF = "jzerof"
    JZEROSTR = "jzerostr"
    JNZEROI8 = "jnzeroi8"
    JNZEROU8 = "jnzerou8"
    JNZEROI16 = "jnzeroi16"
    JNZEROU16 = "jnzerou16"
    JNZEROI32 = "jnzeroi32"
    JNZEROU32 = "jnzerou32"
    JNZEROF16 = "jnzerof16"
    JNZEROF = "jnzerof"
    JNZEROSTR = "jnzerostr"
    JGEZEROI8 = "jgezeroi8"
    JGEZEROU8 = "jgezerou8"
    JGEZEROI16 = "jgezeroi16"
    JGEZEROU16 = "jgezerou16"
    JGEZEROI32 = "jgezeroi32"
    JGEZEROU32 = "jgezerou32"
    JGEZEROF16 = "jgezerof16"
    JGEZEROF = "jgezerof"
    PARAMU8 = "paramu8"
    PARAMI8 = "parami8"
    PARAMU16 = "paramu16"
    PARAMI16 = "parami16"
    PARAMU32 = "paramu32"
    PARAMI32 = "parami32"
    PARAMF16 = "paramf16"
    PARAMF = "paramf"
    PARAMSTR = "paramstr"
    FPARAMU8 = "fparamu8"
    FPARAMI8 = "fparami8"
    FPARAMU16 = "fparamu16"
    FPARAMI16 = "fparami16"
    FPARAMU32 = "fparamu32"
    FPARAMI32 = "fparami32"
    FPARAMF16 = "fparamf16"
    FPARAMF = "fparamf"
    FPARAMSTR = "fparamstr"
    CALL = "call"
    RET = "ret"
    RETI8 = "reti8"
    RETU8 = "retu8"
    RETI16 = "reti16"
    RETU16 = "retu16"
    RETI32 = "reti32"
    RETU32 = "retu32"
    RETF16 = "retf16"
    RETF = "retf"
    RETSTR = "retstr"
    LEAVE = "leave"
    ENTER = "enter"
    ORG = "org"
    END = "end"
    LABEL = "label"
    DEFLABEL = "deflabel"
    OUT = "out"
    IN = "in"
    INLINE = "inline"
    CAST = "cast"
    # TYPECAST
    STOREI8 = "storei8"
    STOREU8 = "storeu8"
    STOREI16 = "storei16"
    STOREU16 = "storeu16"
    STOREI32 = "storei32"
    STOREU32 = "storeu32"
    STOREF16 = "storef16"
    STOREF = "storef"
    STORESTR = "storestr"
    ASTOREI8 = "astorei8"
    ASTOREU8 = "astoreu8"
    ASTOREI16 = "astorei16"
    ASTOREU16 = "astoreu16"
    ASTOREI32 = "astorei32"
    ASTOREU32 = "astoreu32"
    ASTOREF16 = "astoref16"
    ASTOREF = "astoref"
    ASTORESTR = "astorestr"
    # ARRAY STORE STR1 <-- STR2
    LOADI8 = "loadi8"
    LOADU8 = "loadu8"
    LOADI16 = "loadi16"
    LOADU16 = "loadu16"
    LOADI32 = "loadi32"
    LOADU32 = "loadu32"
    LOADF16 = "loadf16"
    LOADF = "loadf"
    LOADSTR = "loadstr"
    ALOADI8 = "aloadi8"
    ALOADU8 = "aloadu8"
    ALOADI16 = "aloadi16"
    ALOADU16 = "aloadu16"
    ALOADI32 = "aloadi32"
    ALOADU32 = "aloadu32"
    ALOADF16 = "aloadf16"
    ALOADF = "aloadf"
    ALOADSTR = "aloadstr"
    PSTOREI8 = "pstorei8"
    PSTOREU8 = "pstoreu8"
    PSTOREI16 = "pstorei16"
    PSTOREU16 = "pstoreu16"
    PSTOREI32 = "pstorei32"
    PSTOREU32 = "pstoreu32"
    PSTOREF16 = "pstoref16"
    PSTOREF = "pstoref"
    PSTORESTR = "pstorestr"
    PASTOREI8 = "pastorei8"
    # PARAM ARRAY STORE I, nnnn, X  -> Stores X at position N (Type of X determines X size)
    PASTOREU8 = "pastoreu8"
    # PARAM ARRAY STORE I, nnnn, X  -> Stores X at position N (Type of X determines X size)
    PASTOREI16 = "pastorei16"
    # PARAM ARRAY STORE I, nnnn, X  -> Stores X at position N (Type of X determines X size)
    PASTOREU16 = "pastoreu16"
    # PARAM ARRAY STORE I, nnnn, X  -> Stores X at position N (Type of X determines X size)
    PASTOREI32 = "pastorei32"
    # PARAM ARRAY STORE I, nnnn, X  -> Stores X at position N (Type of X determines X size)
    PASTOREU32 = "pastoreu32"
    # PARAM ARRAY STORE I, nnnn, X  -> Stores X at position N (Type of X determines X size)
    PASTOREF16 = "pastoref16"
    # PARAM ARRAY STORE I, nnnn, X  -> Stores X at position N (Type of X determines X size)
    PASTOREF = "pastoref"
    PASTORESTR = "pastorestr"
    # PARAM ARRAY STORE I, nnnn, X  -> Stores X at position N (Type of X determines X size)
    PLOADI8 = "ploadi8"
    PLOADU8 = "ploadu8"
    PLOADI16 = "ploadi16"
    PLOADU16 = "ploadu16"
    PLOADI32 = "ploadi32"
    PLOADU32 = "ploadu32"
    PLOADF16 = "ploadf16"
    PLOADF = "ploadf"
    PLOADSTR = "ploadstr"
    PADDR = "paddr"
    AADDR = "aaddr"
    PAADDR = "paaddr"
    PALOADI8 = "paloadi8"
    PALOADU8 = "paloadu8"
    PALOADI16 = "paloadi16"
    PALOADU16 = "paloadu16"
    PALOADI32 = "paloadi32"
    PALOADU32 = "paloadu32"
    PALOADF16 = "paloadf16"
    PALOADF = "paloadf"
    PALOADSTR = "paloadstr"
    FPLOADSTR = "fploadstr"
    EXCHG = "exchg"
    NOP = "nop"
    VAR = "var"
    VARX = "varx"
    VARD = "vard"
    LVARX = "lvarx"
    LVARD = "lvard"
    LARRD = "larrd"
    MEMCOPY = "memcopy"
    BANDU8 = "bandu8"
    BANDI8 = "bandi8"
    BORU8 = "boru8"
    BORI8 = "bori8"
    BXORU8 = "bxoru8"
    BXORI8 = "bxori8"
    BNOTI8 = "bnoti8"
    BNOTU8 = "bnotu8"
    BANDU16 = "bandu16"
    BANDI16 = "bandi16"
    BORU16 = "boru16"
    BORI16 = "bori16"
    BXORU16 = "bxoru16"
    BXORI16 = "bxori16"
    BNOTU16 = "bnotu16"
    BNOTI16 = "bnoti16"
    BANDU32 = "bandu32"
    BANDI32 = "bandi32"
    BORU32 = "boru32"
    BORI32 = "bori32"
    BXORU32 = "bxoru32"
    BXORI32 = "bxori32"
    BNOTU32 = "bnotu32"
    BNOTI32 = "bnoti32"

    @classmethod
    def check(cls, other: Any) -> None:
        """Checks if the given instruction is valid."""
        if not cls.is_valid(other):
            raise InvalidICError(other)

    @classmethod
    def is_valid(cls, other: Any) -> bool:
        """Checks if the given instruction is valid."""
        return other in cls.values()

    @classmethod
    @lru_cache
    def values(cls) -> set[str]:
        """Returns a list of all the values."""
        return set(getattr(cls, x) for x in dir(cls) if x == x.upper())
