#pragma once
#include "Emu/CPU/CPUDecoder.h"
#include "ARMv7Opcodes.h"


class ARMv7Decoder : public CPUDecoder
{
	ARMv7Opcodes& m_op;
	u8 m_last_instr_size;

public:
	ARMv7Decoder(ARMv7Opcodes& op) : m_op(op)
	{
	}

	u32 branchTarget(u32 imm)
	{
		return imm << 1;
	}

	virtual u8 DecodeMemory(const u64 address)
	{
		using namespace ARMv7_opcodes;
		const u16 code0 = Memory.Read16(address);
		const u16 code1 = Memory.Read16(address + 2);

		switch(code0 >> 12) //15 - 12
		{
		case T1_CBZ:
			switch((code0 >> 10) & 0x1)
			{
			case 0:
				switch((code0 >> 8) & 0x1)
				{
				case 1:
					m_op.CBZ((code0 >> 11) & 0x1, branchTarget((((code0 >> 9) & 0x1) << 5) | (code0 >> 3) & 0x1f), code0 & 0x7, 2);
				return 2;
				}
			break;
			}
		break;

		case T1_B:
			m_op.B((code0 >> 8) & 0xf, branchTarget(code0 & 0xff), 2);
		return 2;
		}

		switch(code0 >> 11) //15 - 11
		{
		case T2_B:
			m_op.B(0xf, branchTarget(code0 & 0xfff), 2);
		return 2;

		case T3_B:
		{
			u8 S = (code0 >> 10) & 0x1;
			u8 J1 = (code1 >> 13) & 0x1;
			u8 J2 = (code1 >> 11) & 0x1;
			u8 I1 = 1 - (J1 ^ S);
			u8 I2 = 1 - (J2 ^ S);
			u16 imm11 = code1 & 0x7ff;
			u32 imm32;

			switch(code1 >> 14)
			{
			case 2: //B
			{
				u8 cond;
				switch((code1 >> 12) & 0x1)
				{
				case 0:
				{
					cond = (code0 >> 6) & 0xf;
					u32 imm6 = code0 & 0x3f;
					imm32 = sign<19, u32>((S << 19) | (I1 << 18) | (I2 << 17) | (imm6 << 11) | imm11);
				}
				break;

				case 1:
					cond = 0xf;
					u32 imm10 = code0 & 0x7ff;
					imm32 = sign<23, u32>((S << 23) | (I1 << 22) | (I2 << 21) | (imm10 << 11) | imm11);
				break;
				}

				m_op.B(cond, branchTarget(imm32), 4);
			}
			return 4;

			case 3: //BL
				switch((code1 >> 12) & 0x1)
				{
				case 0:
					break;

				case 1:
					u32 imm10 = code0 & 0x7ff;
					imm32 = sign<23, u32>((S << 23) | (I1 << 22) | (I2 << 21) | (imm10 << 11) | imm11);
					m_op.BL(branchTarget(imm32), 4);
				return 4;
				}
			break;
			}
		}
		break;
		}

		switch(code0 >> 9)
		{
		case T1_PUSH:
			m_op.PUSH((((code0 >> 8) & 0x1) << 14) | (code0 & 0xff));
		return 2;

		case T1_POP:
			m_op.POP((((code0 >> 8) & 0x1) << 15) | (code0 & 0xff));
		return 2;
		}

		switch(code0)
		{
		case T2_PUSH:
			m_op.PUSH(code1);
		return 4;

		case T2_POP:
			m_op.POP(code1);
		return 4;

		case T1_NOP:
			m_op.NOP();
		return 2;
		}

		m_op.UNK(code0, code1);
		return 2;
	}
};
