
#include "m740.hpp"

// 740 addressing modes :
typedef enum {
    A_IMM,            // immediate
    A_ACC,            // accumulator
    A_ZP,             // zero page
    A_ZPX,            // zero page X
    A_ZPY,            // zero page Y
    A_ABS,            // absolute
    A_ABSX,           // absolute X
    A_ABSY,           // absolute Y
    A_IMPL,           // implied
    A_REL,            // relative
    A_INDX,           // indirect X
    A_INDY,           // indirect Y
    A_INDABS,         // indirect absolute
    A_ZPIND,          // zero page indirect
    A_SP,             // special page
    A_ZPB,            // zero page bit
    A_ACCB,           // accumulator bit
    A_ACCBREL,        // accumulator bit relative
    A_ZPBREL          // zero page bit relative
} m740_addr_mode_t;

struct opcode {
    uint16 insn;                  // instruction ID
    uchar code;                   // operation code, one byte
    m740_addr_mode_t addr;        // addressing mode
};

// 740 operation codes :
static const struct opcode opcodes[] = {
    { m740_adc,        0x69,        A_IMM           },
    { m740_adc,        0x65,        A_ZP            },
    { m740_adc,        0x75,        A_ZPX           },
    { m740_adc,        0x6D,        A_ABS           },
    { m740_adc,        0x7D,        A_ABSX          },
    { m740_adc,        0x79,        A_ABSY          },
    { m740_adc,        0x61,        A_INDX          },
    { m740_adc,        0x71,        A_INDY          },
    { m740_and,        0x29,        A_IMM           },
    { m740_and,        0x25,        A_ZP            },
    { m740_and,        0x35,        A_ZPX           },
    { m740_and,        0x2D,        A_ABS           },
    { m740_and,        0x3D,        A_ABSX          },
    { m740_and,        0x39,        A_ABSY          },
    { m740_and,        0x21,        A_INDX          },
    { m740_and,        0x31,        A_INDY          },
    { m740_asl,        0x0A,        A_ACC           },
    { m740_asl,        0x06,        A_ZP            },
    { m740_asl,        0x16,        A_ZPX           },
    { m740_asl,        0x0E,        A_ABS           },
    { m740_asl,        0x1E,        A_ABSX          },
    { m740_bcc,        0x90,        A_REL           },
    { m740_bcs,        0xB0,        A_REL           },
    { m740_beq,        0xF0,        A_REL           },
    { m740_bit,        0x24,        A_ZP            },
    { m740_bit,        0x2C,        A_ABS           },
    { m740_bmi,        0x30,        A_REL           },
    { m740_bne,        0xD0,        A_REL           },
    { m740_bpl,        0x10,        A_REL           },
    { m740_bra,        0x80,        A_REL           },
    { m740_brk,        0x00,        A_IMPL          },
    { m740_bvc,        0x50,        A_REL           },
    { m740_bvs,        0x70,        A_REL           },
    { m740_clc,        0x18,        A_IMPL          },
    { m740_cld,        0xD8,        A_IMPL          },
    { m740_cli,        0x58,        A_IMPL          },
    { m740_clt,        0x12,        A_IMPL          },
    { m740_clv,        0xB8,        A_IMPL          },
    { m740_cmp,        0xC9,        A_IMM           },
    { m740_cmp,        0xC5,        A_ZP            },
    { m740_cmp,        0xD5,        A_ZPX           },
    { m740_cmp,        0xCD,        A_ABS           },
    { m740_cmp,        0xDD,        A_ABSX          },
    { m740_cmp,        0xD9,        A_ABSY          },
    { m740_cmp,        0xC1,        A_INDX          },
    { m740_cmp,        0xD1,        A_INDY          },
    { m740_com,        0x44,        A_ZP            },
    { m740_cpx,        0xE0,        A_IMM           },
    { m740_cpx,        0xE4,        A_ZP            },
    { m740_cpx,        0xEC,        A_ABS           },
    { m740_cpy,        0xC0,        A_IMM           },
    { m740_cpy,        0xC4,        A_ZP            },
    { m740_cpy,        0xCC,        A_ABS           },
    { m740_dec,        0x1A,        A_ACC           },
    { m740_dec,        0xC6,        A_ZP            },
    { m740_dec,        0xD6,        A_ZPX           },
    { m740_dec,        0xCE,        A_ABS           },
    { m740_dec,        0xDE,        A_ABSX          },
    { m740_dex,        0xCA,        A_IMPL          },
    { m740_dey,        0x88,        A_IMPL          },
    { m740_div,        0xE2,        A_ZPX           },
    { m740_eor,        0x49,        A_IMM           },
    { m740_eor,        0x45,        A_ZP            },
    { m740_eor,        0x55,        A_ZPX           },
    { m740_eor,        0x4D,        A_ABS           },
    { m740_eor,        0x5D,        A_ABSX          },
    { m740_eor,        0x59,        A_ABSY          },
    { m740_eor,        0x41,        A_INDX          },
    { m740_eor,        0x51,        A_INDY          },
    { m740_inc,        0x3A,        A_ACC           },
    { m740_inc,        0xE6,        A_ZP            },
    { m740_inc,        0xF6,        A_ZPX           },
    { m740_inc,        0xEE,        A_ABS           },
    { m740_inc,        0xFE,        A_ABSX          },
    { m740_inx,        0xE8,        A_IMPL          },
    { m740_iny,        0xC8,        A_IMPL          },
    { m740_jmp,        0x4C,        A_ABS           },
    { m740_jmp,        0x6C,        A_INDABS        },
    { m740_jmp,        0xB2,        A_ZPIND         },
    { m740_jsr,        0x20,        A_ABS           },
    { m740_jsr,        0x22,        A_SP            },
    { m740_jsr,        0x02,        A_ZPIND         },
    { m740_lda,        0xA9,        A_IMM           },
    { m740_lda,        0xA5,        A_ZP            },
    { m740_lda,        0xB5,        A_ZPX           },
    { m740_lda,        0xAD,        A_ABS           },
    { m740_lda,        0xBD,        A_ABSX          },
    { m740_lda,        0xB9,        A_ABSY          },
    { m740_lda,        0xA1,        A_INDX          },
    { m740_lda,        0xB1,        A_INDY          },
    { m740_ldm,        0x3C,        A_ZP            },
    { m740_ldx,        0xA2,        A_IMM           },
    { m740_ldx,        0xA6,        A_ZP            },
    { m740_ldx,        0xB6,        A_ZPY           },
    { m740_ldx,        0xAE,        A_ABS           },
    { m740_ldx,        0xBE,        A_ABSY          },
    { m740_ldy,        0xA0,        A_IMM           },
    { m740_ldy,        0xA4,        A_ZP            },
    { m740_ldy,        0xB4,        A_ZPX           },
    { m740_ldy,        0xAC,        A_ABS           },
    { m740_ldy,        0xBC,        A_ABSX          },
    { m740_lsr,        0x4A,        A_ACC           },
    { m740_lsr,        0x46,        A_ZP            },
    { m740_lsr,        0x56,        A_ZPX           },
    { m740_lsr,        0x4E,        A_ABS           },
    { m740_lsr,        0x5E,        A_ABSX          },
    { m740_mul,        0x62,        A_ZPX           },
    { m740_nop,        0xEA,        A_IMPL          },
    { m740_ora,        0x09,        A_IMM           },
    { m740_ora,        0x05,        A_ZP            },
    { m740_ora,        0x15,        A_ZPX           },
    { m740_ora,        0x0D,        A_ABS           },
    { m740_ora,        0x1D,        A_ABSX          },
    { m740_ora,        0x19,        A_ABSY          },
    { m740_ora,        0x01,        A_INDX          },
    { m740_ora,        0x11,        A_INDY          },
    { m740_pha,        0x48,        A_IMPL          },
    { m740_php,        0x08,        A_IMPL          },
    { m740_pla,        0x68,        A_IMPL          },
    { m740_plp,        0x28,        A_IMPL          },
    { m740_rol,        0x2A,        A_ACC           },
    { m740_rol,        0x26,        A_ZP            },
    { m740_rol,        0x36,        A_ZPX           },
    { m740_rol,        0x2E,        A_ABS           },
    { m740_rol,        0x3E,        A_ABSX          },
    { m740_ror,        0x6A,        A_ACC           },
    { m740_ror,        0x66,        A_ZP            },
    { m740_ror,        0x76,        A_ZPX           },
    { m740_ror,        0x6E,        A_ABS           },
    { m740_ror,        0x7E,        A_ABSX          },
    { m740_rrf,        0x82,        A_ZP            },
    { m740_rti,        0x40,        A_IMPL          },
    { m740_rts,        0x60,        A_IMPL          },
    { m740_sbc,        0xE9,        A_IMM           },
    { m740_sbc,        0xE5,        A_ZP            },
    { m740_sbc,        0xF5,        A_ZPX           },
    { m740_sbc,        0xED,        A_ABS           },
    { m740_sbc,        0xFD,        A_ABSX          },
    { m740_sbc,        0xF9,        A_ABSY          },
    { m740_sbc,        0xE1,        A_INDX          },
    { m740_sbc,        0xF1,        A_INDY          },
    { m740_sec,        0x38,        A_IMPL          },
    { m740_sed,        0xF8,        A_IMPL          },
    { m740_sei,        0x78,        A_IMPL          },
    { m740_set,        0x32,        A_IMPL          },
    { m740_sta,        0x85,        A_ZP            },
    { m740_sta,        0x95,        A_ZPX           },
    { m740_sta,        0x8D,        A_ABS           },
    { m740_sta,        0x9D,        A_ABSX          },
    { m740_sta,        0x99,        A_ABSY          },
    { m740_sta,        0x81,        A_INDX          },
    { m740_sta,        0x91,        A_INDY          },
    { m740_stp,        0x42,        A_IMPL          },
    { m740_stx,        0x86,        A_ZP            },
    { m740_stx,        0x96,        A_ZPY           },
    { m740_stx,        0x8E,        A_ABS           },
    { m740_sty,        0x84,        A_ZP            },
    { m740_sty,        0x94,        A_ZPX           },
    { m740_sty,        0x8C,        A_ABS           },
    { m740_tax,        0xAA,        A_IMPL          },
    { m740_tay,        0xA8,        A_IMPL          },
    { m740_tst,        0x64,        A_ZP            },
    { m740_tsx,        0xBA,        A_IMPL          },
    { m740_txa,        0x8A,        A_IMPL          },
    { m740_txs,        0x9A,        A_IMPL          },
    { m740_tya,        0x98,        A_IMPL          },
    { m740_wit,        0xC2,        A_IMPL          }
};

struct opcode_flag {
    uint16 insn;
    uchar  flags;
#define MEM_R    OP_ADDR_R    // read access
#define MEM_W    OP_ADDR_W    // write access
};

static const struct opcode_flag opcodes_flags[] = {
    { m740_adc,     MEM_R    },
    { m740_and,     MEM_R    },
    { m740_asl,     MEM_W    },
    { m740_bbc,     MEM_R    },
    { m740_bbs,     MEM_R    },
    { m740_bit,     MEM_R    },
    { m740_clb,     MEM_W    },
    { m740_cmp,     MEM_R    },
    { m740_com,     MEM_W    },
    { m740_cpx,     MEM_R    },
    { m740_cpy,     MEM_R    },
    { m740_dec,     MEM_W    },
    { m740_eor,     MEM_R    },
    { m740_inc,     MEM_W    },
    { m740_jmp,     MEM_R    },
    { m740_jsr,     MEM_R    },
    { m740_lda,     MEM_R    },
    { m740_ldm,     MEM_W    },
    { m740_ldx,     MEM_R    },
    { m740_ldy,     MEM_R    },
    { m740_lsr,     MEM_W    },
    { m740_ora,     MEM_R    },
    { m740_rol,     MEM_W    },
    { m740_ror,     MEM_W    },
    { m740_sbc,     MEM_R    },
    { m740_seb,     MEM_W    },
    { m740_sta,     MEM_W    },
    { m740_stx,     MEM_W    },
    { m740_sty,     MEM_W    },
    { m740_tst,     MEM_R    },
    { m740_rrf,     MEM_W    }
};

// fill operand as a register
inline static void set_op_reg(op_t &op, uint16 reg) {
    op.type = o_reg;
    op.reg = reg;
    op.dtyp = dt_word; // XXX not sure
}

// some shortcuts to make our live easier
#define set_op_acc(x)    set_op_reg(x, rA)
#define set_op_x(x)        set_op_reg(x, rX)
#define set_op_y(x)        set_op_reg(x, rY)

// fill operand as a code address
inline static void set_op_addr(op_t &op, ea_t addr) {
    op.type = o_near;
    op.addr = addr;
    op.dtyp = dt_code;
}

// fill operand as a displacement between a memory address and a register contents
inline static void set_op_displ(int addr, uint16 reg, char d_typ = dt_byte) {
    cmd.Op1.type = o_displ;
    cmd.Op1.addr = addr;
    cmd.Op1.reg = reg;
    cmd.Op1.dtyp = d_typ;
}

// fill operand as a data address
inline static void set_op_mem(op_t &op, int addr, const uchar flags = 0, char d_typ = dt_byte) {
    op.type = o_mem;
    op.addr = addr;
    op.dtyp = d_typ;
    op.specflag1 = flags;
}

// fill operand as an immediate value
inline static void set_op_imm(op_t &op, int imm) {
    op.type = o_imm;
    op.value = imm;
    op.dtyp = dt_byte;
}

// fill the cmd structure according to the addressing mode of the
// current analyzed instruction
static void fill_cmd(m740_addr_mode_t addr, const uchar flags = 0) {

    switch ( addr ) {
        case A_IMM:            // immediate
            set_op_imm(cmd.Op1, ua_next_byte());
            break;

        case A_ACC:            // accumulator
            set_op_acc(cmd.Op1);
            break;

        case A_ZP:            // zero page
            if ( cmd.itype == m740_ldm ) {    // special case
                set_op_imm(cmd.Op1, ua_next_byte());
                set_op_mem(cmd.Op2, ua_next_byte(), flags);
            }
            else
                set_op_mem(cmd.Op1, ua_next_byte(), flags);
            break;

        case A_ZPX:            // zero page X
            set_op_displ(ua_next_byte(), rX);
            cmd.auxpref |= INSN_DISPL_ZPX;
            break;

        case A_ZPY:            // zero page Y
            set_op_displ(ua_next_byte(), rY);
            cmd.auxpref |= INSN_DISPL_ZPY;
            break;

        case A_ABS:            // absolute
            if ( cmd.itype == m740_jmp || cmd.itype == m740_jsr ) {
                set_op_addr(cmd.Op1, ua_next_word());
            }
            else {
                set_op_mem(cmd.Op1, ua_next_word(), flags);
            }
            break;

        case A_ABSX:        // absolute X
            set_op_displ(ua_next_word(), rX);
            cmd.auxpref |= INSN_DISPL_ABSX;
            break;

        case A_ABSY:        // absolute Y
            set_op_displ(ua_next_word(), rY);
            cmd.auxpref |= INSN_DISPL_ABSY;
            break;

        case A_IMPL:        // implied
            // nothing to do..
            break;

        case A_REL:            // relative
            set_op_addr(cmd.Op1, (signed char) ua_next_byte() + cmd.ea + 2);
            break;

        case A_INDX:        // indirect X
            set_op_displ(ua_next_byte(), rX, dt_word);
            cmd.auxpref |= INSN_DISPL_INDX;
            break;

        case A_INDY:        // indirect Y
            set_op_displ(ua_next_byte(), rY, dt_word);
            cmd.auxpref |= INSN_DISPL_INDY;
            break;

        case A_INDABS:        // indirect absolute
            set_op_mem(cmd.Op1, ua_next_word(), flags, dt_word);
            cmd.Op1.specflag1 |= OP_ADDR_IND;
            break;

        case A_ZPIND:        // zero page indirect
            set_op_mem(cmd.Op1, ua_next_byte(), 0, dt_word);
            cmd.Op1.specflag1 |= OP_ADDR_IND;
            break;

        case A_SP:            // special page
            set_op_addr(cmd.Op1, ua_next_byte() | 0xFF00);
            cmd.Op1.specflag1 |= OP_ADDR_SP;
            break;

        case A_ZPB:            // zero page bit
            set_op_mem(cmd.Op2, ua_next_byte(), flags, dt_word);
            break;

        case A_ACCB:        // accumulator bit
            set_op_acc(cmd.Op2);
            break;

        case A_ACCBREL:        // accumulator bit relative
            set_op_acc(cmd.Op2);
            set_op_addr(cmd.Op3, (signed char) ua_next_byte() + cmd.ea + 2);
            break;

        case A_ZPBREL:        // zero page bit relative
            set_op_mem(cmd.Op2, ua_next_byte(), flags);
            set_op_addr(cmd.Op3, (signed char) ua_next_byte() + cmd.ea + 3);
            break;

        default:
            INTERR(10025);
    }
}

// try to find an opcode in our table from the fetched byte
static const struct opcode * get_opcode(int byte) {
    for (int i = 0; i < qnumber(opcodes); i++) {
        if ( opcodes[i].code != byte )
            continue;

        return &opcodes[i];
    }
    return NULL;
}

static uchar get_opcode_flags(const uint16 insn) {
    for (int i = 0; i < qnumber(opcodes_flags); i++) {
        if ( opcodes_flags[i].insn != insn )
            continue;

        return opcodes_flags[i].flags;
    }
    return 0;
}

// detect special instructions, whose we can't detect using the table and the
// get_opcode() routine
static bool ana_special(int byte) {
    bool special = false;

    const struct {
        uint16 insn;                  // instruction ID
        uchar  val;                   // (20i + val)
        m740_addr_mode_t addr;        // which addressing mode ?
    }
    specials[] = {
        { m740_bbc,        0x13,    A_ACCBREL   },
        { m740_bbc,        0x17,    A_ZPBREL    },
        { m740_bbs,        0x03,    A_ACCBREL   },
        { m740_bbs,        0x07,    A_ZPBREL    },
        { m740_clb,        0x1B,    A_ACCB      },
        { m740_clb,        0x1F,    A_ZPB       },
        { m740_seb,        0x0B,    A_ACCB      },
        { m740_seb,        0x0F,    A_ZPB       }
    };

    for (int i = 0; i < qnumber(specials); i++) {
        int t = (uchar) byte - specials[i].val;
        if ( (t % 0x20) != 0 )
            continue;

        cmd.itype = specials[i].insn;

        set_op_imm(cmd.Op1, t / 0x20);
        cmd.Op1.specflag1 |= OP_IMM_BIT;
        fill_cmd(specials[i].addr, get_opcode_flags(specials[i].insn));
        special = true;
        break;
    }

    return special;
}

// analyze an instruction
int idaapi ana(void) {
    bool special;
    int byte;

    byte = ua_next_byte();
    special = ana_special(byte);

    if ( !special ) {

        const struct opcode *op = get_opcode(byte);
        if ( op == NULL )        // unmatched insn
            return 0;

        cmd.itype = op->insn;
        fill_cmd(op->addr, get_opcode_flags(op->insn));
    }

    return cmd.size;
}
