#ifndef ARMINSTRINFO_H
#define ARMINSTRINFO_H

#include "types.h"

namespace ARMInstrInfo
{

// Instruction kinds, for faster dispatch

#define ak_ALU(n) \
    ak_##n##_REG_LSL_IMM, \
    ak_##n##_REG_LSR_IMM, \
    ak_##n##_REG_ASR_IMM, \
    ak_##n##_REG_ROR_IMM, \
    \
    ak_##n##_REG_LSL_REG, \
    ak_##n##_REG_LSR_REG, \
    ak_##n##_REG_ASR_REG, \
    ak_##n##_REG_ROR_REG, \
    \
    ak_##n##_IMM, \
    \
    ak_##n##_REG_LSL_IMM_S, \
    ak_##n##_REG_LSR_IMM_S, \
    ak_##n##_REG_ASR_IMM_S, \
    ak_##n##_REG_ROR_IMM_S, \
    \
    ak_##n##_REG_LSL_REG_S, \
    ak_##n##_REG_LSR_REG_S, \
    ak_##n##_REG_ASR_REG_S, \
    ak_##n##_REG_ROR_REG_S, \
    \
    ak_##n##_IMM_S \

#define ak_Test(n) \
    ak_##n##_REG_LSL_IMM, \
    ak_##n##_REG_LSR_IMM, \
    ak_##n##_REG_ASR_IMM, \
    ak_##n##_REG_ROR_IMM, \
    \
    ak_##n##_REG_LSL_REG, \
    ak_##n##_REG_LSR_REG, \
    ak_##n##_REG_ASR_REG, \
    ak_##n##_REG_ROR_REG, \
    \
    ak_##n##_IMM

#define ak_WB_LDRSTR(n) \
    ak_##n##_REG_LSL, \
    ak_##n##_REG_LSR, \
    ak_##n##_REG_ASR, \
    ak_##n##_REG_ROR, \
    \
    ak_##n##_IMM, \
    \
    ak_##n##_POST_REG_LSL, \
    ak_##n##_POST_REG_LSR, \
    ak_##n##_POST_REG_ASR, \
    ak_##n##_POST_REG_ROR, \
    \
    ak_##n##_POST_IMM

#define ak_HD_LDRSTR(n) \
    ak_##n##_REG, \
    ak_##n##_IMM, \
    \
    ak_##n##_POST_REG, \
    ak_##n##_POST_IMM

enum
{
    ak_ALU(AND),
    ak_ALU(EOR),
    ak_ALU(SUB),
    ak_ALU(RSB),
    ak_ALU(ADD),
    ak_ALU(ADC),
    ak_ALU(SBC),
    ak_ALU(RSC),
    ak_ALU(ORR),
    ak_ALU(MOV),
    ak_ALU(BIC),
    ak_ALU(MVN),

    ak_Test(TST),
    ak_Test(TEQ),
    ak_Test(CMP),
    ak_Test(CMN),

    ak_MUL,
    ak_MLA,
    ak_UMULL,
    ak_UMLAL,
    ak_SMULL,
    ak_SMLAL,
    ak_SMLAxy,
    ak_SMLAWy,
    ak_SMULWy,
    ak_SMLALxy,
    ak_SMULxy,

    ak_CLZ,

    ak_QADD,
    ak_QSUB,
    ak_QDADD,
    ak_QDSUB,

    ak_WB_LDRSTR(STR),
    ak_WB_LDRSTR(STRB),
    ak_WB_LDRSTR(LDR),
    ak_WB_LDRSTR(LDRB),

    ak_HD_LDRSTR(STRH),
    ak_HD_LDRSTR(LDRD),
    ak_HD_LDRSTR(STRD),
    ak_HD_LDRSTR(LDRH),
    ak_HD_LDRSTR(LDRSB),
    ak_HD_LDRSTR(LDRSH),

    ak_SWP,
    ak_SWPB,

    ak_LDM,
    ak_STM,

    ak_B,
    ak_BL,
    ak_BLX_IMM,
    ak_BX,
    ak_BLX_REG,

    ak_UNK,
    ak_MSR_IMM,
    ak_MSR_REG,
    ak_MRS,
    ak_MCR,
    ak_MRC,
    ak_SVC,

    ak_Nop,

    ak_Count,

    tk_LSL_IMM = 0,
    tk_LSR_IMM,
    tk_ASR_IMM,

    tk_ADD_REG_,
    tk_SUB_REG_,
    tk_ADD_IMM_,
    tk_SUB_IMM_,

    tk_MOV_IMM,
    tk_CMP_IMM,
    tk_ADD_IMM,
    tk_SUB_IMM,

    tk_AND_REG,
    tk_EOR_REG,
    tk_LSL_REG,
    tk_LSR_REG,
    tk_ASR_REG,
    tk_ADC_REG,
    tk_SBC_REG,
    tk_ROR_REG,
    tk_TST_REG,
    tk_NEG_REG,
    tk_CMP_REG,
    tk_CMN_REG,
    tk_ORR_REG,
    tk_MUL_REG,
    tk_BIC_REG,
    tk_MVN_REG,

    tk_ADD_HIREG,
    tk_CMP_HIREG,
    tk_MOV_HIREG,

    tk_ADD_PCREL,
    tk_ADD_SPREL,
    tk_ADD_SP,

    tk_LDR_PCREL,
    tk_STR_REG,
    tk_STRB_REG,
    tk_LDR_REG,
    tk_LDRB_REG,
    tk_STRH_REG,
    tk_LDRSB_REG,
    tk_LDRH_REG,
    tk_LDRSH_REG,
    tk_STR_IMM,
    tk_LDR_IMM,
    tk_STRB_IMM,
    tk_LDRB_IMM,
    tk_STRH_IMM,
    tk_LDRH_IMM,
    tk_STR_SPREL,
    tk_LDR_SPREL,

    tk_PUSH,
    tk_POP,
    tk_LDMIA,
    tk_STMIA,
    
    tk_BCOND,
    tk_BX,
    tk_BLX_REG,
    tk_B,
    tk_BL_LONG_1,
    tk_BL_LONG_2,
    tk_UNK,
    tk_SVC,

    // not a real instruction
    tk_BL_LONG,

    tk_Count
};

enum
{
    flag_N = 1 << 3,
    flag_Z = 1 << 2,
    flag_C = 1 << 1,
    flag_V = 1 << 0,
};

enum
{
    special_NotSpecialAtAll = 0,
    special_WriteMem,
    special_LoadMem,
    special_WaitForInterrupt,
    special_LoadLiteral
};

struct Info
{
    u16 DstRegs, SrcRegs, NotStrictlyNeeded;
    u16 Kind;

    u8 SpecialKind;

    u8 ReadFlags;
    // lower 4 bits - set always
    // upper 4 bits - might set flag
    u8 WriteFlags;

    bool EndBlock;
    bool Branches() const
    {
        return DstRegs & (1 << 15);
    }
};

Info Decode(bool thumb, u32 num, u32 instr);

}

#endif