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

#include <glad/glad.h>
#include <GLFW/glfw3.h>

#include <nostalgia/core/gfx.hpp>
#include <nostalgia/core/input.hpp>
#include <nostalgia/core/userland/gfx.hpp>

#include "core.hpp"

namespace nostalgia::core {

ox::Result<ox::UniquePtr<Context>> init(ox::UniquePtr<ox::FileSystem> fs, const char *appName) noexcept {
	auto ctx = ox::make_unique<Context>();
	ctx->rom = std::move(fs);
	ctx->appName = appName;
	const auto id = new GlfwImplData;
	ctx->setWindowerData(id);
	using namespace std::chrono;
	id->startTime = duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
	glfwInit();
	oxReturnError(initGfx(ctx.get()));
	return ctx;
}

ox::Error run(Context *ctx) noexcept {
	const auto id = ctx->windowerData<GlfwImplData>();
	int sleepTime = 0;
	while (!glfwWindowShouldClose(id->window)) {
		glfwPollEvents();
		const auto ticks = ticksMs(ctx);
		if (id->eventHandler) {
			if (id->wakeupTime <= ticks) {
				 sleepTime = id->eventHandler(ctx);
				 if (sleepTime >= 0) {
					  id->wakeupTime = ticks + static_cast<unsigned>(sleepTime);
				 } else {
					  id->wakeupTime = ~uint64_t(0);
				 }
			}
		} else {
			sleepTime = 10;
		}
		draw(ctx);
		glfwSwapBuffers(id->window);
		if (!ctx->constantRefresh) {
			if (ctx->uninterruptedRefreshes) {
				--ctx->uninterruptedRefreshes;
			} else {
				glfwWaitEventsTimeout(sleepTime);
			}
		}
	}
	// destroy GLFW window
	renderer::shutdown(ctx, ctx->rendererData<void>());
	glfwDestroyWindow(id->window);
	ctx->setWindowerData(nullptr);
	ox::safeDelete(id);
	return OxError(0);
}

void setUpdateHandler(Context *ctx, UpdateHandler h) noexcept {
	const auto id = ctx->windowerData<GlfwImplData>();
	id->eventHandler = h;
}

uint64_t ticksMs(Context *ctx) noexcept {
	using namespace std::chrono;
	const auto id = ctx->windowerData<GlfwImplData>();
	const auto now = duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
	return static_cast<uint64_t>(now - id->startTime);
}

bool buttonDown(Context *ctx, Key key) noexcept {
	const auto id = ctx->windowerData<GlfwImplData>();
	return (id->keysDown >> static_cast<int>(key)) & 1;
}

void shutdown(Context *ctx) noexcept {
	const auto id = ctx->windowerData<GlfwImplData>();
	glfwSetWindowShouldClose(id->window, true);
}

}
