/*
 * Copyright 2016 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
 */

#pragma once

#include <ox/std/array.hpp>
#include <ox/std/units.hpp>

/////////////////////////////////////////////////////////////////
// Interrupt Handler

using InterruptHandler = void(*)();
#define REG_ISR (*reinterpret_cast<InterruptHandler*>(0x0300'7FFC))
#define REG_IE  (*reinterpret_cast<volatile uint16_t*>(0x0400'0200))
#define REG_IF  (*reinterpret_cast<volatile uint16_t*>(0x0400'0202))
#define REG_IME (*reinterpret_cast<volatile uint16_t*>(0x0400'0208))

/////////////////////////////////////////////////////////////////
// Display Registers

#define REG_DISPCTL  (*reinterpret_cast<volatile uint32_t*>(0x0400'0000))
#define REG_DISPSTAT (*reinterpret_cast<volatile uint32_t*>(0x0400'0004))
#define REG_VCOUNT   (*reinterpret_cast<volatile uint32_t*>(0x0400'0006))

/////////////////////////////////////////////////////////////////
// Timers

#define REG_TIMER0    (*reinterpret_cast<volatile uint16_t*>(0x0400'0100))
#define REG_TIMER0CTL (*reinterpret_cast<volatile uint16_t*>(0x0400'0102))

#define REG_TIMER1    (*reinterpret_cast<volatile uint16_t*>(0x0400'0104))
#define REG_TIMER1CTL (*reinterpret_cast<volatile uint16_t*>(0x0400'0106))

#define REG_TIMER2    (*reinterpret_cast<volatile uint16_t*>(0x0400'0108))
#define REG_TIMER2CTL (*reinterpret_cast<volatile uint16_t*>(0x0400'010a))

#define REG_TIMER3    (*reinterpret_cast<volatile uint16_t*>(0x0400'010c))
#define REG_TIMER3CTL (*reinterpret_cast<volatile uint16_t*>(0x0400'010e))

/////////////////////////////////////////////////////////////////
// background registers

// background control registers
using BgCtl = uint16_t;
#define REG_BG0CTL (*reinterpret_cast<volatile BgCtl*>(0x0400'0008))
#define REG_BG1CTL (*reinterpret_cast<volatile BgCtl*>(0x0400'000a))
#define REG_BG2CTL (*reinterpret_cast<volatile BgCtl*>(0x0400'000c))
#define REG_BG3CTL (*reinterpret_cast<volatile BgCtl*>(0x0400'000e))

[[nodiscard]]
inline volatile BgCtl &regBgCtl(uintptr_t const bgIdx) noexcept {
	return *reinterpret_cast<volatile BgCtl*>(0x0400'0008 + 2 * bgIdx);
}

// background horizontal scrolling registers
#define REG_BG0HOFS (*reinterpret_cast<volatile int16_t*>(0x0400'0010))
#define REG_BG1HOFS (*reinterpret_cast<volatile int16_t*>(0x0400'0014))
#define REG_BG2HOFS (*reinterpret_cast<volatile int16_t*>(0x0400'0018))
#define REG_BG3HOFS (*reinterpret_cast<volatile int16_t*>(0x0400'001c))

[[nodiscard]]
volatile int16_t &regBgHofs(auto const bgIdx) noexcept {
	return *reinterpret_cast<volatile int16_t*>(0x0400'0010 + 4 * bgIdx);
}

// background vertical scrolling registers
#define REG_BG0VOFS (*reinterpret_cast<volatile int16_t*>(0x0400'0012))
#define REG_BG1VOFS (*reinterpret_cast<volatile int16_t*>(0x0400'0016))
#define REG_BG2VOFS (*reinterpret_cast<volatile int16_t*>(0x0400'001a))
#define REG_BG3VOFS (*reinterpret_cast<volatile int16_t*>(0x0400'001e))

[[nodiscard]]
volatile int16_t &regBgVofs(auto const bgIdx) noexcept {
	return *reinterpret_cast<volatile int16_t*>(0x0400'0012 + 4 * bgIdx);
}

// background scrolling registers

struct OffsetPair {
	int16_t x{}, y{};
};

#define REG_BG0OFS (*reinterpret_cast<volatile OffsetPair*>(0x0400'0010))
#define REG_BG1OFS (*reinterpret_cast<volatile OffsetPair*>(0x0400'0014))
#define REG_BG2OFS (*reinterpret_cast<volatile OffsetPair*>(0x0400'0018))
#define REG_BG3OFS (*reinterpret_cast<volatile OffsetPair*>(0x0400'001c))

[[nodiscard]]
volatile OffsetPair &regBgOfs(auto const bgIdx) noexcept {
	return *reinterpret_cast<volatile OffsetPair*>(0x0400'0010 + sizeof(OffsetPair) * bgIdx);
}

/////////////////////////////////////////////////////////////////
// User Input

#define REG_GAMEPAD (*reinterpret_cast<volatile uint16_t*>(0x0400'0130))

/////////////////////////////////////////////////////////////////
// Memory Addresses

#define MEM_EWRAM (*(reinterpret_cast<ox::Array<uint16_t, 0x0203'FFFF - 0x0200'0000>*>(0x0200'0000)))

#define MEM_IWRAM (*(reinterpret_cast<ox::Array<uint8_t, 0x0300'7FFF - 0x0300'0000>*>(0x0300'0000)))

#define REG_BLNDCTL (*reinterpret_cast<uint16_t*>(0x0400'0050))

using Palette = ox::Array<uint16_t, 128>;
#define MEM_BG_PALETTE (*(reinterpret_cast<::Palette*>(0x0500'0000)))
#define MEM_SPRITE_PALETTE (*(reinterpret_cast<::Palette*>(0x0500'0200)))

using BgMapTile = ox::Array<uint16_t, 8192>;
#define MEM_BG_TILES (*(reinterpret_cast<ox::Array<BgMapTile, 4>*>(0x0600'0000)))
#define MEM_BG_MAP (*(reinterpret_cast<ox::Array<BgMapTile, 4>*>(0x0600'e000)))

#define MEM_SPRITE_TILES (*(reinterpret_cast<ox::Array<uint16_t, 32 * ox::units::KB>*>(0x0601'0000)))
#define MEM_OAM (*(reinterpret_cast<ox::Array<uint64_t, 64>*>(0x0700'0000)))

#define MEM_ROM (*(reinterpret_cast<ox::Array<char, 32 * ox::units::MB>*>(0x0700'0000)))

#define MEM_SRAM (*(reinterpret_cast<ox::Array<char, 64 * ox::units::KB>*>(0x0e00'0000)))
