#include "global.h"

#define GPU_REG_BUF_SIZE 0x60

#define GPU_REG_BUF(offset) (*(u16 *)(&sGpuRegBuffer[offset]))
#define GPU_REG(offset) (*(vu16 *)(REG_BASE + offset))

#define EMPTY_SLOT 0xFF

static u8 sGpuRegBuffer[GPU_REG_BUF_SIZE];
static u8 sGpuRegWaitingList[GPU_REG_BUF_SIZE];
static volatile bool8 sGpuRegBufferLocked;
static volatile bool8 sShouldSyncRegIE;
static vu16 sRegIE;

static void CopyBufferedValueToGpuReg(u8 regOffset);
static void SyncRegIE(void);
static void UpdateRegDispstatIntrBits(u16 regIE);

void InitGpuRegManager(void)
{
	s32 i;

	for (i = 0; i < GPU_REG_BUF_SIZE; i++)
    {
		sGpuRegBuffer[i] = 0;
		sGpuRegWaitingList[i] = EMPTY_SLOT;
	}

	sGpuRegBufferLocked = FALSE;
	sShouldSyncRegIE = FALSE;
	sRegIE = 0;
}

static void CopyBufferedValueToGpuReg(u8 regOffset)
{
	if (regOffset == REG_OFFSET_DISPSTAT)
    {
		REG_DISPSTAT &= ~(DISPSTAT_HBLANK_INTR | DISPSTAT_VBLANK_INTR);
		REG_DISPSTAT |= GPU_REG_BUF(REG_OFFSET_DISPSTAT);
	}
	else
    {
		GPU_REG(regOffset) = GPU_REG_BUF(regOffset);
	}
}

void CopyBufferedValuesToGpuRegs(void)
{
	if (!sGpuRegBufferLocked)
    {
		s32 i;

		for (i = 0; i < GPU_REG_BUF_SIZE; i++)
        {
			u8 regOffset = sGpuRegWaitingList[i];
			if (regOffset == EMPTY_SLOT)
				return;
			CopyBufferedValueToGpuReg(regOffset);
			sGpuRegWaitingList[i] = EMPTY_SLOT;
		}
	}
}

void SetGpuReg(u8 regOffset, u16 value)
{
	if (regOffset < GPU_REG_BUF_SIZE)
	{
		u16 vcount;

		GPU_REG_BUF(regOffset) = value;
		vcount = REG_VCOUNT & 0xFF;

		if ((vcount >= 161 && vcount <= 225)
		 || (REG_DISPCNT & DISPCNT_FORCED_BLANK)) {
			CopyBufferedValueToGpuReg(regOffset);
		} else {
			s32 i;

			sGpuRegBufferLocked = TRUE;

			for (i = 0; i < GPU_REG_BUF_SIZE && sGpuRegWaitingList[i] != EMPTY_SLOT; i++) {
				if (sGpuRegWaitingList[i] == regOffset) {
					sGpuRegBufferLocked = FALSE;
					return;
				}
			}

			sGpuRegWaitingList[i] = regOffset;
			sGpuRegBufferLocked = FALSE;
		}
	}
}

u16 GetGpuReg(u8 regOffset)
{
	if (regOffset == REG_OFFSET_DISPSTAT)
		return REG_DISPSTAT;

	if (regOffset == REG_OFFSET_VCOUNT)
		return REG_VCOUNT;

	return GPU_REG_BUF(regOffset);
}

void SetGpuRegBits(u8 regOffset, u16 mask)
{
	u16 regValue = GPU_REG_BUF(regOffset);
	SetGpuReg(regOffset, regValue | mask);
}

void ClearGpuRegBits(u8 regOffset, u16 mask)
{
	u16 regValue = GPU_REG_BUF(regOffset);
	SetGpuReg(regOffset, regValue & ~mask);
}

static void SyncRegIE(void)
{
	if (sShouldSyncRegIE) {
		u16 temp = REG_IME;
		REG_IME = 0;
		REG_IE = sRegIE;
		REG_IME = temp;
		sShouldSyncRegIE = FALSE;
	}
}

void EnableInterrupts(u16 mask)
{
	sRegIE |= mask;
	sShouldSyncRegIE = TRUE;
	SyncRegIE();
	UpdateRegDispstatIntrBits(sRegIE);
}

void DisableInterrupts(u16 mask)
{
	sRegIE &= ~mask;
	sShouldSyncRegIE = TRUE;
	SyncRegIE();
	UpdateRegDispstatIntrBits(sRegIE);
}

static void UpdateRegDispstatIntrBits(u16 regIE)
{
	u16 oldValue = GetGpuReg(REG_OFFSET_DISPSTAT) & (DISPSTAT_HBLANK_INTR | DISPSTAT_VBLANK_INTR);
	u16 newValue = 0;

	if (regIE & INTR_FLAG_VBLANK)
		newValue |= DISPSTAT_VBLANK_INTR;

	if (regIE & INTR_FLAG_HBLANK)
		newValue |= DISPSTAT_HBLANK_INTR;

	if (oldValue != newValue)
		SetGpuReg(REG_OFFSET_DISPSTAT, newValue);
}
