/* Copyright (c) 2013-2014 Jeffrey Pfau
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef ARM_DECODER_H
#define ARM_DECODER_H

#include <mgba-util/common.h>

CXX_GUARD_START

#include <mgba/internal/arm/arm.h>

// Bit 0: a register is involved with this operand
// Bit 1: an immediate is invovled with this operand
// Bit 2: a memory access is invovled with this operand
// Bit 3: the destination of this operand is affected by this opcode
// Bit 4: this operand is shifted by a register
// Bit 5: this operand is shifted by an immediate
#define ARM_OPERAND_NONE                0x00000000
#define ARM_OPERAND_REGISTER_1          0x00000001
#define ARM_OPERAND_IMMEDIATE_1         0x00000002
#define ARM_OPERAND_MEMORY_1            0x00000004
#define ARM_OPERAND_AFFECTED_1          0x00000008
#define ARM_OPERAND_SHIFT_REGISTER_1    0x00000010
#define ARM_OPERAND_SHIFT_IMMEDIATE_1   0x00000020
#define ARM_OPERAND_1                   0x000000FF

#define ARM_OPERAND_REGISTER_2          0x00000100
#define ARM_OPERAND_IMMEDIATE_2         0x00000200
#define ARM_OPERAND_MEMORY_2            0x00000400
#define ARM_OPERAND_AFFECTED_2          0x00000800
#define ARM_OPERAND_SHIFT_REGISTER_2    0x00001000
#define ARM_OPERAND_SHIFT_IMMEDIATE_2   0x00002000
#define ARM_OPERAND_2                   0x0000FF00

#define ARM_OPERAND_REGISTER_3          0x00010000
#define ARM_OPERAND_IMMEDIATE_3         0x00020000
#define ARM_OPERAND_MEMORY_3            0x00040000
#define ARM_OPERAND_AFFECTED_3          0x00080000
#define ARM_OPERAND_SHIFT_REGISTER_3    0x00100000
#define ARM_OPERAND_SHIFT_IMMEDIATE_3   0x00200000
#define ARM_OPERAND_3                   0x00FF0000

#define ARM_OPERAND_REGISTER_4          0x01000000
#define ARM_OPERAND_IMMEDIATE_4         0x02000000
#define ARM_OPERAND_MEMORY_4            0x04000000
#define ARM_OPERAND_AFFECTED_4          0x08000000
#define ARM_OPERAND_SHIFT_REGISTER_4    0x10000000
#define ARM_OPERAND_SHIFT_IMMEDIATE_4   0x20000000
#define ARM_OPERAND_4                   0xFF000000

#define ARM_OPERAND_MEMORY (ARM_OPERAND_MEMORY_1 | ARM_OPERAND_MEMORY_2 | ARM_OPERAND_MEMORY_3 | ARM_OPERAND_MEMORY_4)

#define ARM_MEMORY_REGISTER_BASE     0x0001
#define ARM_MEMORY_IMMEDIATE_OFFSET  0x0002
#define ARM_MEMORY_REGISTER_OFFSET   0x0004
#define ARM_MEMORY_SHIFTED_OFFSET    0x0008
#define ARM_MEMORY_PRE_INCREMENT     0x0010
#define ARM_MEMORY_POST_INCREMENT    0x0020
#define ARM_MEMORY_OFFSET_SUBTRACT   0x0040
#define ARM_MEMORY_WRITEBACK         0x0080
#define ARM_MEMORY_DECREMENT_AFTER   0x0000
#define ARM_MEMORY_INCREMENT_AFTER   0x0100
#define ARM_MEMORY_DECREMENT_BEFORE  0x0200
#define ARM_MEMORY_INCREMENT_BEFORE  0x0300
#define ARM_MEMORY_SPSR_SWAP         0x0400

#define ARM_PSR_C 1
#define ARM_PSR_X 2
#define ARM_PSR_S 4
#define ARM_PSR_F 8
#define ARM_PSR_MASK 0xF

#define MEMORY_FORMAT_TO_DIRECTION(F) (((F) >> 8) & 0x3)

enum ARMCondition {
	ARM_CONDITION_EQ = 0x0,
	ARM_CONDITION_NE = 0x1,
	ARM_CONDITION_CS = 0x2,
	ARM_CONDITION_CC = 0x3,
	ARM_CONDITION_MI = 0x4,
	ARM_CONDITION_PL = 0x5,
	ARM_CONDITION_VS = 0x6,
	ARM_CONDITION_VC = 0x7,
	ARM_CONDITION_HI = 0x8,
	ARM_CONDITION_LS = 0x9,
	ARM_CONDITION_GE = 0xA,
	ARM_CONDITION_LT = 0xB,
	ARM_CONDITION_GT = 0xC,
	ARM_CONDITION_LE = 0xD,
	ARM_CONDITION_AL = 0xE,
	ARM_CONDITION_NV = 0xF
};

enum ARMShifterOperation {
	ARM_SHIFT_NONE = 0,
	ARM_SHIFT_LSL,
	ARM_SHIFT_LSR,
	ARM_SHIFT_ASR,
	ARM_SHIFT_ROR,
	ARM_SHIFT_RRX
};

union ARMOperand {
	struct {
		uint8_t reg;
		uint8_t shifterOp;
		union {
			uint8_t shifterReg;
			uint8_t shifterImm;
			uint8_t psrBits;
		};
	};
	int32_t immediate;
};

enum ARMMemoryAccessType {
	ARM_ACCESS_WORD = 4,
	ARM_ACCESS_HALFWORD = 2,
	ARM_ACCESS_SIGNED_HALFWORD = 10,
	ARM_ACCESS_BYTE = 1,
	ARM_ACCESS_SIGNED_BYTE = 9,
	ARM_ACCESS_TRANSLATED_WORD = 20,
	ARM_ACCESS_TRANSLATED_BYTE = 17
};

enum ARMBranchType {
	ARM_BRANCH_NONE = 0,
	ARM_BRANCH = 1,
	ARM_BRANCH_INDIRECT = 2,
	ARM_BRANCH_LINKED = 4
};

struct ARMMemoryAccess {
	uint8_t baseReg;
	uint8_t width;
	uint16_t format;
	union ARMOperand offset;
};

enum ARMMnemonic {
	ARM_MN_ILL = 0,
	ARM_MN_ADC,
	ARM_MN_ADD,
	ARM_MN_AND,
	ARM_MN_ASR,
	ARM_MN_B,
	ARM_MN_BIC,
	ARM_MN_BKPT,
	ARM_MN_BL,
	ARM_MN_BX,
	ARM_MN_CMN,
	ARM_MN_CMP,
	ARM_MN_EOR,
	ARM_MN_LDM,
	ARM_MN_LDR,
	ARM_MN_LSL,
	ARM_MN_LSR,
	ARM_MN_MLA,
	ARM_MN_MOV,
	ARM_MN_MRS,
	ARM_MN_MSR,
	ARM_MN_MUL,
	ARM_MN_MVN,
	ARM_MN_NEG,
	ARM_MN_ORR,
	ARM_MN_ROR,
	ARM_MN_RSB,
	ARM_MN_RSC,
	ARM_MN_SBC,
	ARM_MN_SMLAL,
	ARM_MN_SMULL,
	ARM_MN_STM,
	ARM_MN_STR,
	ARM_MN_SUB,
	ARM_MN_SWI,
	ARM_MN_SWP,
	ARM_MN_TEQ,
	ARM_MN_TST,
	ARM_MN_UMLAL,
	ARM_MN_UMULL,

	ARM_MN_MAX
};

enum {
	ARM_CPSR = 16,
	ARM_SPSR = 17
};

struct ARMInstructionInfo {
	uint32_t opcode;
	union ARMOperand op1;
	union ARMOperand op2;
	union ARMOperand op3;
	union ARMOperand op4;
	struct ARMMemoryAccess memory;
	int operandFormat;
	unsigned execMode : 1;
	bool traps : 1;
	bool affectsCPSR : 1;
	unsigned branchType : 3;
	unsigned condition : 4;
	unsigned mnemonic : 6;
	unsigned iCycles : 3;
	unsigned cCycles : 4;
	unsigned sInstructionCycles : 4;
	unsigned nInstructionCycles : 4;
	unsigned sDataCycles : 10;
	unsigned nDataCycles : 10;
};

void ARMDecodeARM(uint32_t opcode, struct ARMInstructionInfo* info);
void ARMDecodeThumb(uint16_t opcode, struct ARMInstructionInfo* info);
bool ARMDecodeThumbCombine(struct ARMInstructionInfo* info1, struct ARMInstructionInfo* info2,
                           struct ARMInstructionInfo* out);
int ARMDisassemble(struct ARMInstructionInfo* info, uint32_t pc, char* buffer, int blen);

CXX_GUARD_END

#endif
