#pragma once

#include "../common/Port.h"
#include "Sound.h"

extern const uint32_t objTilesAddress[3];

extern bool stopState;
extern bool holdState;
extern int cpuNextEvent;
extern bool cpuDmaHack;
extern uint32_t cpuDmaLast;
extern bool timer0On;
extern int timer0Ticks;
extern int timer0ClockReload;
extern bool timer1On;
extern int timer1Ticks;
extern int timer1ClockReload;
extern bool timer2On;
extern int timer2Ticks;
extern int timer2ClockReload;
extern bool timer3On;
extern int timer3Ticks;
extern int timer3ClockReload;
extern int cpuTotalTicks;

inline uint8_t CPUReadByteQuick(uint32_t addr) { return map[addr >> 24].address[addr & map[addr >> 24].mask]; }

inline uint16_t CPUReadHalfWordQuick(uint32_t addr) { return READ16LE(&map[addr >> 24].address[addr & map[addr >> 24].mask]); }

inline uint32_t CPUReadMemoryQuick(uint32_t addr) { return READ32LE(&map[addr >> 24].address[addr & map[addr >> 24].mask]); }

inline uint32_t CPUReadMemory(uint32_t address)
{
	uint32_t value;
	uint32_t oldAddress = address;

	if (address & 3)
		address &= ~0x03;

	switch (address >> 24)
	{
		case 0:
			if (reg[15].I >> 24)
			{
				if (address < 0x4000)
					value = READ32LE(&biosProtected[0]);
				else
					goto unreadable;
			}
			else
				value = READ32LE(&bios[address & 0x3FFC]);
			break;
		case 2:
			value = READ32LE(&workRAM[address & 0x3FFFC]);
			break;
		case 3:
			value = READ32LE(&internalRAM[address & 0x7ffC]);
			break;
		case 4:
			if (address < 0x4000400 && ioReadable[address & 0x3fc])
			{
				if (ioReadable[(address & 0x3fc) + 2])
					value = READ32LE(&ioMem[address & 0x3fC]);
				else
					value = READ16LE(&ioMem[address & 0x3fc]);
			}
			else
				goto unreadable;
			break;
		case 5:
			value = READ32LE(&paletteRAM[address & 0x3fC]);
			break;
		case 6:
			address &= 0x1fffc;
			if ((DISPCNT & 7) > 2 && (address & 0x1C000) == 0x18000)
			{
				value = 0;
				break;
			}
			if ((address & 0x18000) == 0x18000)
				address &= 0x17fff;
			value = READ32LE(&vram[address]);
			break;
		case 7:
			value = READ32LE(&oam[address & 0x3FC]);
			break;
		case 8:
		case 9:
		case 10:
		case 11:
		case 12:
			value = READ32LE(&rom[address & 0x1FFFFFC]);
			break;
		case 13:
		case 14:
		case 15:
			value = 0;
			break;
		// default
		default:
		unreadable:
			if (cpuDmaHack)
				value = cpuDmaLast;
			else
			{
				if (armState)
					return CPUReadMemoryQuick(reg[15].I);
				else
					return CPUReadHalfWordQuick(reg[15].I) | CPUReadHalfWordQuick(reg[15].I) << 16;
			}
	}

	if (oldAddress & 3)
	{
		int shift = (oldAddress & 3) << 3;
		value = (value >> shift) | (value << (32 - shift));
	}

	return value;
}

inline uint32_t CPUReadHalfWord(uint32_t address)
{
	uint32_t value;
	uint32_t oldAddress = address;

	if (address & 1)
		address &= ~0x01;

	switch (address >> 24)
	{
		case 0:
			if (reg[15].I >> 24)
			{
				if (address < 0x4000)
					value = READ16LE(&biosProtected[address & 2]);
				else
					goto unreadable;
			}
			else
				value = READ16LE(&bios[address & 0x3FFE]);
			break;
		case 2:
			value = READ16LE(&workRAM[address & 0x3FFFE]);
			break;
		case 3:
			value = READ16LE(&internalRAM[address & 0x7ffe]);
			break;
		case 4:
			if (address < 0x4000400 && ioReadable[address & 0x3fe])
			{
				value = READ16LE(&ioMem[address & 0x3fe]);
				if ((address & 0x3fe) > 0xFF && (address & 0x3fe) < 0x10E)
				{
					if ((address & 0x3fe) == 0x100 && timer0On)
						value = 0xFFFF - ((timer0Ticks - cpuTotalTicks) >> timer0ClockReload);
					else if ((address & 0x3fe) == 0x104 && timer1On && !(TM1CNT & 4))
						value = 0xFFFF - ((timer1Ticks - cpuTotalTicks) >> timer1ClockReload);
					else if ((address & 0x3fe) == 0x108 && timer2On && !(TM2CNT & 4))
						value = 0xFFFF - ((timer2Ticks - cpuTotalTicks) >> timer2ClockReload);
					else if ((address & 0x3fe) == 0x10C && timer3On && !(TM3CNT & 4))
						value = 0xFFFF - ((timer3Ticks - cpuTotalTicks) >> timer3ClockReload);
				}
			}
			else if (address < 0x4000400 && ioReadable[address & 0x3fc])
				value = 0;
			else
				goto unreadable;
			break;
		case 5:
			value = READ16LE(&paletteRAM[address & 0x3fe]);
			break;
		case 6:
			address &= 0x1fffe;
			if ((DISPCNT & 7) > 2 && (address & 0x1C000) == 0x18000)
			{
				value = 0;
				break;
			}
			if ((address & 0x18000) == 0x18000)
				address &= 0x17fff;
			value = READ16LE(&vram[address]);
			break;
		case 7:
			value = READ16LE(&oam[address & 0x3fe]);
			break;
		case 8:
		case 9:
		case 10:
		case 11:
		case 12:
			if (address == 0x80000c4 || address == 0x80000c6 || address == 0x80000c8)
				value = 0;
			else
				value = READ16LE(&rom[address & 0x1FFFFFE]);
			break;
		case 13:
		case 14:
		case 15:
			value = 0;
			break;
		// default
		default:
		unreadable:
			if (cpuDmaHack)
				value = cpuDmaLast & 0xFFFF;
			else
			{
				if (armState)
					value = CPUReadHalfWordQuick(reg[15].I + (address & 2));
				else
					value = CPUReadHalfWordQuick(reg[15].I);
			}
			return value;
	}

	if (oldAddress & 1)
		value = (value >> 8) | (value << 24);

	return value;
}

inline int16_t CPUReadHalfWordSigned(uint32_t address)
{
	return static_cast<int16_t>(CPUReadHalfWord(address));
}

inline uint8_t CPUReadByte(uint32_t address)
{
	switch (address >> 24)
	{
		case 0:
			if (reg[15].I >> 24)
			{
				if (address < 0x4000)
					return biosProtected[address & 3];
				else
					break;
			}
			return bios[address & 0x3FFF];
		case 2:
			return workRAM[address & 0x3FFFF];
		case 3:
			return internalRAM[address & 0x7fff];
		case 4:
			if (address < 0x4000400 && ioReadable[address & 0x3ff])
				return ioMem[address & 0x3ff];
			else
				break;
		case 5:
			return paletteRAM[address & 0x3ff];
		case 6:
			address &= 0x1ffff;
			if ((DISPCNT & 7) > 2 && (address & 0x1C000) == 0x18000)
				return 0;
			if ((address & 0x18000) == 0x18000)
				address &= 0x17fff;
			return vram[address];
		case 7:
			return oam[address & 0x3ff];
		case 8:
		case 9:
		case 10:
		case 11:
		case 12:
			return rom[address & 0x1FFFFFF];
		case 13:
		case 14:
		case 15:
			return 0;
	}
	if (cpuDmaHack)
		return cpuDmaLast & 0xFF;
	else
	{
		if (armState)
			return CPUReadByteQuick(reg[15].I + (address & 3));
		else
			return CPUReadByteQuick(reg[15].I + (address & 1));
	}
}

inline void CPUWriteMemory(uint32_t address, uint32_t value)
{
	address &= 0xFFFFFFFC;

	switch (address >> 24)
	{
		case 0x02:
			WRITE32LE(&workRAM[address & 0x3FFFC], value);
			break;
		case 0x03:
			WRITE32LE(&internalRAM[address & 0x7ffC], value);
			break;
		case 0x04:
			if (address < 0x4000400)
			{
				CPUUpdateRegister(address & 0x3FC, value & 0xFFFF);
				CPUUpdateRegister((address & 0x3FC) + 2, value >> 16);
			}
			break;
		case 0x05:
			WRITE32LE(&paletteRAM[address & 0x3FC], value);
			break;
		case 0x06:
			address &= 0x1fffc;
			if ((DISPCNT & 7) > 2 && (address & 0x1C000) == 0x18000)
				return;
			if ((address & 0x18000) == 0x18000)
				address &= 0x17fff;

			WRITE32LE(&vram[address], value);
			break;
		case 0x07:
			WRITE32LE(&oam[address & 0x3fc], value);
	}
}

inline void CPUWriteHalfWord(uint32_t address, uint16_t value)
{
	address &= 0xFFFFFFFE;

	switch (address >> 24)
	{
		case 2:
			WRITE16LE(&workRAM[address & 0x3FFFE], value);
			break;
		case 3:
			WRITE16LE(&internalRAM[address & 0x7ffe], value);
			break;
		case 4:
			if (address < 0x4000400)
				CPUUpdateRegister(address & 0x3fe, value);
			break;
		case 5:
			WRITE16LE(&paletteRAM[address & 0x3fe], value);
			break;
		case 6:
			address &= 0x1fffe;
			if ((DISPCNT & 7) > 2 && (address & 0x1C000) == 0x18000)
				return;
			if ((address & 0x18000) == 0x18000)
				address &= 0x17fff;
			WRITE16LE(&vram[address], value);
			break;
		case 7:
			WRITE16LE(&oam[address & 0x3fe], value);
	}
}

inline void CPUWriteByte(uint32_t address, uint8_t b)
{
	switch (address >> 24)
	{
		case 2:
			workRAM[address & 0x3FFFF] = b;
			break;
		case 3:
			internalRAM[address & 0x7fff] = b;
			break;
		case 4:
			if (address < 0x4000400)
			{
				switch (address & 0x3FF)
				{
					case 0x60:
					case 0x61:
					case 0x62:
					case 0x63:
					case 0x64:
					case 0x65:
					case 0x68:
					case 0x69:
					case 0x6c:
					case 0x6d:
					case 0x70:
					case 0x71:
					case 0x72:
					case 0x73:
					case 0x74:
					case 0x75:
					case 0x78:
					case 0x79:
					case 0x7c:
					case 0x7d:
					case 0x80:
					case 0x81:
					case 0x84:
					case 0x85:
					case 0x90:
					case 0x91:
					case 0x92:
					case 0x93:
					case 0x94:
					case 0x95:
					case 0x96:
					case 0x97:
					case 0x98:
					case 0x99:
					case 0x9a:
					case 0x9b:
					case 0x9c:
					case 0x9d:
					case 0x9e:
					case 0x9f:
						soundEvent(address & 0xFF, b);
						break;
					case 0x301: // HALTCNT, undocumented
						if (b == 0x80)
							stopState = true;
						holdState = true;
						cpuNextEvent = cpuTotalTicks;
						break;
					default: // every other register
						uint32_t lowerBits = address & 0x3fe;
						if (address & 1)
							CPUUpdateRegister(lowerBits, (READ16LE(&ioMem[lowerBits]) & 0x00FF) | (b << 8));
						else
							CPUUpdateRegister(lowerBits, (READ16LE(&ioMem[lowerBits]) & 0xFF00) | b);
				}
			}
			break;
		case 5:
			// no need to switch
			*reinterpret_cast<uint16_t *>(&paletteRAM[address & 0x3FE]) = (b << 8) | b;
			break;
		case 6:
			address &= 0x1fffe;
			if ((DISPCNT & 7) > 2 && (address & 0x1C000) == 0x18000)
				return;
			if ((address & 0x18000) == 0x18000)
				address &= 0x17fff;

			// no need to switch
			// byte writes to OBJ VRAM are ignored
			if (address < objTilesAddress[((DISPCNT & 7) + 1) >> 2])
				*reinterpret_cast<uint16_t *>(&vram[address]) = (b << 8) | b;
			break;
		case 7:
			// no need to switch
			// byte writes to OAM are ignored
			//*reinterpret_cast<uint16_t *>(&oam[address & 0x3FE]) = (b << 8) | b;
			break;
	}
}