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

#include <GLFW/glfw3.h>
#if TURBINE_USE_IMGUI
#include <imgui_impl_opengl3.h>
#include <imgui_impl_glfw.h>
#endif

#include <keel/keel.hpp>

#include <turbine/turbine.hpp>

#include "config.hpp"
#include "context.hpp"

namespace turbine {

static void draw(Context &ctx) noexcept {
	// draw start
#if TURBINE_USE_IMGUI
	ImGui_ImplOpenGL3_NewFrame();
	ImGui_ImplGlfw_NewFrame();
	ImGui::NewFrame();
#endif
	for (auto d : ctx.drawers) {
		d->draw(ctx);
	}
#if TURBINE_USE_IMGUI
	ImGui::Render();
	ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
#endif
	// draw end
	glfwSwapBuffers(ctx.window);
}

static void draw(GLFWwindow *window, int, int) noexcept {
	auto &ctx = *static_cast<Context*>(glfwGetWindowUserPointer(window));
	draw(ctx);
}

ox::Result<ContextUPtr> init(
		ox::UPtr<ox::FileSystem> &&fs, ox::CRStringView appName) noexcept {
	auto ctx = ox::make_unique<Context>();
	oxReturnError(keel::init(ctx->keelCtx, std::move(fs), appName));
	using namespace std::chrono;
	ctx->startTime = duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
	glfwInit();
	oxReturnError(initGfx(*ctx));
	glfwSetWindowSizeCallback(ctx->window, draw);
	return ox::UPtr<Context, ContextDeleter>(ctx.release());
}

static void tickFps(Context &ctx, uint64_t nowMs) noexcept {
	++ctx.draws;
	if (ctx.draws >= 500) {
		const auto duration = static_cast<double>(nowMs - ctx.prevFpsCheckTime) / 1000.0;
		const auto fps = static_cast<int>(static_cast<double>(ctx.draws) / duration);
		if constexpr(config::GlFpsPrint) {
			oxOutf("FPS: {}\n", fps);
		}
		oxTracef("turbine.fps", "FPS: {}", fps);
		ctx.prevFpsCheckTime = nowMs;
		ctx.draws = 0;
	}
}

ox::Error run(Context &ctx) noexcept {
	int sleepTime = 0;
	while (!glfwWindowShouldClose(ctx.window)) {
		glfwPollEvents();
		const auto ticks = ticksMs(ctx);
		if (ctx.wakeupTime <= ticks) {
			 sleepTime = ctx.updateHandler(ctx);
			 if (sleepTime >= 0) {
				  ctx.wakeupTime = ticks + static_cast<unsigned>(sleepTime);
			 } else {
				  ctx.wakeupTime = ~uint64_t(0);
			 }
		} else {
			sleepTime = 10;
		}
		tickFps(ctx, ticks);
		draw(ctx);
		if (!ctx.constantRefresh) {
			if (ctx.uninterruptedRefreshes) {
				--ctx.uninterruptedRefreshes;
			} else {
				glfwWaitEventsTimeout(sleepTime);
			}
		}
	}
	shutdown(ctx);
	return {};
}

void shutdown(Context &ctx) noexcept {
	if (ctx.window) {
#if TURBINE_USE_IMGUI
		ImGui_ImplOpenGL3_Shutdown();
		ImGui_ImplGlfw_Shutdown();
#endif
		glfwDestroyWindow(ctx.window);
		ctx.window = nullptr;
	}
}

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

bool buttonDown(Context const&ctx, Key key) noexcept {
	return (ctx.keysDown >> static_cast<int>(key)) & 1;
}

void requestShutdown(Context &ctx) noexcept {
	glfwSetWindowShouldClose(ctx.window, true);
}

}
