#include "RenderThread.h"

#include "../frames/InputFrameMailbox.h"
#include "../frames/SystemFrameExchange.h"
#include "../frames/SystemFrameTypes.h"
#include "../logging/Logger.h"
#include "../platform/HiddenGlWindow.h"
#include "InputFrameTexture.h"
#include "readback/Bgra8ReadbackPipeline.h"
#include "GLExtensions.h"
#include "RuntimeRenderScene.h"
#include "SimpleMotionRenderer.h"

#include <memory>
#include <thread>

void RenderThread::ThreadMain()
{
	RenderCadenceCompositor::TryLog(RenderCadenceCompositor::LogLevel::Log, "render-thread", "Render thread starting.");
	HiddenGlWindow window;
	std::string error;
	if (!window.Create(mConfig.width, mConfig.height, error))
	{
		SignalStartupFailure(error.empty() ? "OpenGL context creation failed." : error);
		return;
	}
	std::unique_ptr<HiddenGlWindow> prepareWindow = std::make_unique<HiddenGlWindow>();
	if (!prepareWindow->CreateShared(mConfig.width, mConfig.height, window.DeviceContext(), window.Context(), error))
	{
		SignalStartupFailure(error.empty() ? "Runtime shader prepare shared context creation failed." : error);
		return;
	}
	if (!window.MakeCurrent())
	{
		SignalStartupFailure("OpenGL context creation failed.");
		return;
	}
	if (!ResolveGLExtensions())
	{
		SignalStartupFailure("OpenGL extension resolution failed.");
		return;
	}

	SimpleMotionRenderer renderer;
	RuntimeRenderScene runtimeRenderScene;
	Bgra8ReadbackPipeline readback;
	InputFrameTexture inputTexture;
	if (!runtimeRenderScene.StartPrepareWorker(std::move(prepareWindow), error))
	{
		SignalStartupFailure(error.empty() ? "Runtime shader prepare worker initialization failed." : error);
		return;
	}
	if (!renderer.InitializeGl(mConfig.width, mConfig.height) || !readback.Initialize(mConfig.width, mConfig.height, mConfig.pboDepth))
	{
		SignalStartupFailure("Render pipeline initialization failed.");
		return;
	}

	RenderCadenceClock clock(mConfig.frameDurationMilliseconds);
	uint64_t frameIndex = 0;
	mRunning.store(true, std::memory_order_release);
	SignalStarted();

	while (!mStopping.load(std::memory_order_acquire))
	{
		readback.ConsumeCompleted(
			[this](SystemFrame& frame) { return mFrameExchange.AcquireForRender(frame); },
			[this](const SystemFrame& frame) { return mFrameExchange.PublishCompleted(frame); },
			[this]() {
				CountAcquireMiss();
			},
			[this]() { CountCompleted(); });
		PublishReadbackMetrics(readback);

		const auto now = RenderCadenceClock::Clock::now();
		const RenderCadenceClock::Tick tick = clock.Poll(now);
		if (!tick.due)
		{
			if (tick.sleepFor > RenderCadenceClock::Duration::zero())
				std::this_thread::sleep_for(tick.sleepFor);
			continue;
		}

		TryCommitReadyRuntimeShader(runtimeRenderScene);
		const GLuint videoInputTexture = inputTexture.PollAndUpload(mInputMailbox);
		PublishInputMetrics(inputTexture);
		if (!readback.RenderAndQueue(frameIndex, [this, &renderer, &runtimeRenderScene, videoInputTexture](uint64_t index) {
			if (runtimeRenderScene.HasLayers())
				runtimeRenderScene.RenderFrame(index, mConfig.width, mConfig.height, videoInputTexture);
			else if (videoInputTexture != 0)
				renderer.RenderTexture(videoInputTexture);
			else
				renderer.RenderFrame(index);
		}))
		{
			mPboQueueMisses.fetch_add(1, std::memory_order_relaxed);
		}
		PublishReadbackMetrics(readback);

		CountRendered();
		++frameIndex;
		clock.MarkRendered(RenderCadenceClock::Clock::now());

		mClockOverruns.store(clock.OverrunCount(), std::memory_order_relaxed);
		mSkippedFrames.store(clock.SkippedFrameCount(), std::memory_order_relaxed);
	}

	for (std::size_t i = 0; i < mConfig.pboDepth * 2; ++i)
	{
		readback.ConsumeCompleted(
			[this](SystemFrame& frame) { return mFrameExchange.AcquireForRender(frame); },
			[this](const SystemFrame& frame) { return mFrameExchange.PublishCompleted(frame); },
			[this]() {
				CountAcquireMiss();
			},
			[this]() { CountCompleted(); });
		PublishReadbackMetrics(readback);
	}

	readback.Shutdown();
	inputTexture.ShutdownGl();
	runtimeRenderScene.ShutdownGl();
	renderer.ShutdownGl();
	window.ClearCurrent();
	mRunning.store(false, std::memory_order_release);
	RenderCadenceCompositor::TryLog(RenderCadenceCompositor::LogLevel::Log, "render-thread", "Render thread stopped.");
}
