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

#include <teagba/addresses.hpp>
#include <teagba/gfx.hpp>
#include <teagba/irq.hpp>


#include <keel/keel.hpp>
#include <turbine/context.hpp>
#include <turbine/gfx.hpp>
#include <turbine/turbine.hpp>

#include "context.hpp"
#include "turbine.hpp"

extern "C" void turbine_isr();

namespace turbine {

// Timer Consts
constexpr int NanoSecond = 1'000'000'000;
constexpr int MilliSecond = 1000;
constexpr int TicksMs59ns =
		65535 - static_cast<uint16_t>(static_cast<double>(NanoSecond / MilliSecond) / 59.59);

extern volatile gba_timer_t g_timerMs;

static void initIrq() noexcept {
	REG_ISR = turbine_isr;
	REG_IME = 1; // enable interrupts
}

static void initTimer() noexcept {
	// make timer0 a ~1 millisecond timer
	REG_TIMER0 = TicksMs59ns;
	REG_TIMER0CTL = 0b11000000;
	// enable interrupt for timer0
	REG_IE = REG_IE | teagba::Int_timer0;
}

[[maybe_unused]]
static ox::Result<std::size_t> findPreloadSection() noexcept {
	// put the header in the wrong order to prevent mistaking this code for the
	// media section
	constexpr auto headerP2 = "DER_____________";
	constexpr auto headerP1 = "KEEL_PRELOAD_HEA";
OX_ALLOW_UNSAFE_BUFFERS_BEGIN
	constexpr auto headerP1Len = ox::strlen(headerP2);
	constexpr auto headerP2Len = ox::strlen(headerP1);
	constexpr auto headerLen = headerP1Len + headerP2Len;
	for (auto current = MEM_ROM.data(); current < reinterpret_cast<char*>(0x0a000000); current += headerLen) {
		if (memcmp(current, headerP1, headerP1Len) == 0 &&
		    memcmp(current + headerP1Len, headerP2, headerP2Len) == 0) {
			return reinterpret_cast<std::size_t>(current + headerLen);
		}
	}
OX_ALLOW_UNSAFE_BUFFERS_END
	return ox::Error(1);
}

static ox::Error initGfx(Context&) noexcept {
	REG_DISPCTL = teagba::DispCtl_Mode0
				| teagba::DispCtl_SpriteMap1D
				| teagba::DispCtl_Obj;
	// tell display to trigger vblank interrupts
	REG_DISPSTAT = REG_DISPSTAT | teagba::DispStat_irq_vblank;
	// enable vblank interrupt
	REG_IE = REG_IE | teagba::Int_vblank;
	return {};
}

void setWindowTitle(Context&, ox::StringViewCR) noexcept {
}

int getScreenWidth(Context const&) noexcept {
	return 240;
}

int getScreenHeight(Context const&) noexcept {
	return 160;
}

ox::Size getScreenSize(Context const&) noexcept {
	return {240, 160};
}

ox::Bounds getWindowBounds(Context const&) noexcept {
	return {0, 0, 240, 160};
}

ox::Error setWindowBounds(Context&, ox::Bounds const&) noexcept {
	return ox::Error(1, "setWindowBounds not supported on GBA");
}

ox::Result<ox::UPtr<Context>> init(
		ox::UPtr<ox::FileSystem> &&fs, ox::StringViewCR appName, ox::StringViewCR) noexcept {
	auto ctx = ox::make_unique<Context>();
	OX_RETURN_ERROR(keel::init(ctx->keelCtx, std::move(fs), appName));
#ifdef OX_BARE_METAL
	OX_RETURN_ERROR(findPreloadSection().moveTo(ctx->keelCtx.preloadSectionOffset));
#endif
	OX_RETURN_ERROR(initGfx(*ctx));
	initTimer();
	initIrq();
	return ctx;
}

void shutdown(Context&) noexcept {
}

TimeMs ticksMs(Context const&) noexcept {
	return g_timerMs;
}

bool buttonDown(Context const&, Key k) noexcept {
	return k <= Key::GamePad_L && !(REG_GAMEPAD & (1 << static_cast<int>(k)));
}

void requestShutdown(Context &ctx, bool) noexcept {
	ctx.running = false;
}

void setShutdownHandler(Context&, ShutdownHandler) noexcept {
}

void setUpdateHandler(Context &ctx, UpdateHandler h) noexcept {
	ctx.updateHandler = h;
}

void setKeyEventHandler(Context &ctx, KeyEventHandler h) noexcept {
	ctx.keyEventHandler = h;
}

void setMouseButtonEventHandler(Context&, MouseButtonEventHandler) noexcept {}

KeyEventHandler keyEventHandler(Context const &ctx) noexcept {
	return ctx.keyEventHandler;
}

float scale(Context const&) noexcept {
	return 1;
}

bool isWayland() noexcept {
	return false;
}

}
