#include "RenderThread.h"

#include "../frames/SystemFrameExchange.h"
#include "../frames/SystemFrameTypes.h"
#include "../logging/Logger.h"
#include "../platform/HiddenGlWindow.h"
#include "Bgra8ReadbackPipeline.h"
#include "GLExtensions.h"
#include "RuntimeShaderRenderer.h"
#include "SimpleMotionRenderer.h"

#include <algorithm>
#include <thread>

RenderThread::RenderThread(SystemFrameExchange& frameExchange, Config config) :
	mFrameExchange(frameExchange),
	mConfig(config)
{
}

RenderThread::~RenderThread()
{
	Stop();
}

bool RenderThread::Start(std::string& error)
{
	if (mThread.joinable())
		return true;

	{
		std::lock_guard<std::mutex> lock(mStartupMutex);
		mStarted = false;
		mStartupError.clear();
	}

	mStopping.store(false, std::memory_order_release);
	mThread = std::thread([this]() { ThreadMain(); });

	std::unique_lock<std::mutex> lock(mStartupMutex);
	if (!mStartupCondition.wait_for(lock, std::chrono::seconds(3), [this]() {
		return mStarted || !mStartupError.empty();
	}))
	{
		error = "Timed out starting render thread.";
		return false;
	}
	if (!mStartupError.empty())
	{
		error = mStartupError;
		lock.unlock();
		if (mThread.joinable())
			mThread.join();
		return false;
	}
	return true;
}

void RenderThread::Stop()
{
	mStopping.store(true, std::memory_order_release);
	if (mThread.joinable())
		mThread.join();
}

RenderThread::Metrics RenderThread::GetMetrics() const
{
	Metrics metrics;
	metrics.renderedFrames = mRenderedFrames.load(std::memory_order_relaxed);
	metrics.completedReadbacks = mCompletedReadbacks.load(std::memory_order_relaxed);
	metrics.acquireMisses = mAcquireMisses.load(std::memory_order_relaxed);
	metrics.pboQueueMisses = mPboQueueMisses.load(std::memory_order_relaxed);
	metrics.clockOverruns = mClockOverruns.load(std::memory_order_relaxed);
	metrics.skippedFrames = mSkippedFrames.load(std::memory_order_relaxed);
	metrics.shaderBuildsCommitted = mShaderBuildsCommitted.load(std::memory_order_relaxed);
	metrics.shaderBuildFailures = mShaderBuildFailures.load(std::memory_order_relaxed);
	return metrics;
}

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) || !window.MakeCurrent())
	{
		SignalStartupFailure(error.empty() ? "OpenGL context creation failed." : error);
		return;
	}
	if (!ResolveGLExtensions())
	{
		SignalStartupFailure("OpenGL extension resolution failed.");
		return;
	}

	SimpleMotionRenderer renderer;
	RuntimeShaderRenderer runtimeShaderRenderer;
	Bgra8ReadbackPipeline readback;
	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(); });

		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(runtimeShaderRenderer);
		if (!readback.RenderAndQueue(frameIndex, [this, &renderer, &runtimeShaderRenderer](uint64_t index) {
			if (runtimeShaderRenderer.HasProgram())
				runtimeShaderRenderer.RenderFrame(index, mConfig.width, mConfig.height);
			else
				renderer.RenderFrame(index);
		}))
		{
			mPboQueueMisses.fetch_add(1, std::memory_order_relaxed);
		}

		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(); });
	}

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

void RenderThread::SignalStarted()
{
	std::lock_guard<std::mutex> lock(mStartupMutex);
	mStarted = true;
	mStartupCondition.notify_all();
}

void RenderThread::SignalStartupFailure(const std::string& error)
{
	RenderCadenceCompositor::TryLog(RenderCadenceCompositor::LogLevel::Error, "render-thread", error);
	std::lock_guard<std::mutex> lock(mStartupMutex);
	mStartupError = error;
	mStartupCondition.notify_all();
}

void RenderThread::CountRendered()
{
	mRenderedFrames.fetch_add(1, std::memory_order_relaxed);
}

void RenderThread::CountCompleted()
{
	mCompletedReadbacks.fetch_add(1, std::memory_order_relaxed);
}

void RenderThread::CountAcquireMiss()
{
	mAcquireMisses.fetch_add(1, std::memory_order_relaxed);
}

void RenderThread::SubmitRuntimeShaderArtifact(const RuntimeShaderArtifact& artifact)
{
	if (artifact.fragmentShaderSource.empty())
		return;

	std::lock_guard<std::mutex> lock(mShaderArtifactMutex);
	mPendingShaderArtifact = artifact;
	mHasPendingShaderArtifact = true;
}

bool RenderThread::TryTakePendingRuntimeShaderArtifact(RuntimeShaderArtifact& artifact)
{
	std::lock_guard<std::mutex> lock(mShaderArtifactMutex);
	if (!mHasPendingShaderArtifact)
		return false;

	artifact = std::move(mPendingShaderArtifact);
	mPendingShaderArtifact = RuntimeShaderArtifact();
	mHasPendingShaderArtifact = false;
	return true;
}

void RenderThread::TryCommitReadyRuntimeShader(RuntimeShaderRenderer& runtimeShaderRenderer)
{
	RuntimeShaderArtifact artifact;
	if (!TryTakePendingRuntimeShaderArtifact(artifact))
		return;

	std::string commitError;
	if (!runtimeShaderRenderer.CommitShaderArtifact(artifact, commitError))
	{
		RenderCadenceCompositor::TryLog(
			RenderCadenceCompositor::LogLevel::Error,
			"render-thread",
			"Runtime shader GL commit failed: " + commitError);
		mShaderBuildFailures.fetch_add(1, std::memory_order_relaxed);
		return;
	}

	RenderCadenceCompositor::TryLog(
		RenderCadenceCompositor::LogLevel::Log,
		"render-thread",
		"Runtime shader committed: " + artifact.shaderId + ". " + artifact.message);
	mShaderBuildsCommitted.fetch_add(1, std::memory_order_relaxed);
}
