#include "RuntimeRenderScene.h"

#include "../../platform/HiddenGlWindow.h"

#include <algorithm>
#include <functional>
#include <utility>

#ifndef GL_FRAMEBUFFER_BINDING
#define GL_FRAMEBUFFER_BINDING 0x8CA6
#endif

RuntimeRenderScene::~RuntimeRenderScene()
{
	ShutdownGl();
}

bool RuntimeRenderScene::StartPrepareWorker(std::unique_ptr<HiddenGlWindow> sharedWindow, std::string& error)
{
	return mPrepareWorker.Start(std::move(sharedWindow), error);
}

bool RuntimeRenderScene::CommitRenderLayers(const std::vector<RenderCadenceCompositor::RuntimeRenderLayerModel>& layers, std::string& error, bool* structuralChange)
{
	ConsumePreparedPrograms();
	bool changedStructure = false;

	std::vector<std::string> nextOrder;
	std::vector<RenderCadenceCompositor::RuntimeRenderLayerModel> layersToPrepare;
	nextOrder.reserve(layers.size());
	for (const RenderCadenceCompositor::RuntimeRenderLayerModel& layer : layers)
	{
		if (!layer.bypass)
			nextOrder.push_back(layer.id);
	}

	for (auto layerIt = mLayers.begin(); layerIt != mLayers.end();)
	{
		const bool stillPresent = std::find(nextOrder.begin(), nextOrder.end(), layerIt->layerId) != nextOrder.end();
		if (stillPresent)
		{
			++layerIt;
			continue;
		}

		for (LayerProgram::PassProgram& pass : layerIt->passes)
		{
			if (pass.renderer)
				pass.renderer->ShutdownGl();
		}
		ReleasePendingPrograms(*layerIt);
		layerIt = mLayers.erase(layerIt);
		changedStructure = true;
	}

	for (const RenderCadenceCompositor::RuntimeRenderLayerModel& layer : layers)
	{
		if (layer.bypass)
			continue;
		if (layer.artifact.passes.empty() && layer.artifact.fragmentShaderSource.empty())
			continue;

		const std::string fingerprint = Fingerprint(layer.artifact);
		LayerProgram* program = FindLayer(layer.id);
		if (!program)
		{
			LayerProgram next;
			next.layerId = layer.id;
			mLayers.push_back(std::move(next));
			program = &mLayers.back();
			changedStructure = true;
		}

		bool hasReadyPass = false;
		for (const LayerProgram::PassProgram& pass : program->passes)
		{
			if (pass.renderer && pass.renderer->HasProgram())
			{
				hasReadyPass = true;
				break;
			}
		}
		if (program->shaderId == layer.shaderId && program->sourceFingerprint == fingerprint && hasReadyPass)
		{
			for (LayerProgram::PassProgram& pass : program->passes)
			{
				if (pass.renderer)
					pass.renderer->UpdateArtifactState(layer.artifact);
			}
			continue;
		}
		if (program->pendingFingerprint == fingerprint)
			continue;

		ReleasePendingPrograms(*program);
		program->shaderId = layer.shaderId;
		program->pendingFingerprint = fingerprint;
		layersToPrepare.push_back(layer);
		changedStructure = true;
	}

	if (mLayerOrder != nextOrder)
		changedStructure = true;
	mLayerOrder = std::move(nextOrder);
	if (!layersToPrepare.empty())
		mPrepareWorker.Submit(layersToPrepare);
	if (structuralChange)
		*structuralChange = changedStructure;
	error.clear();
	return true;
}

void RuntimeRenderScene::ShutdownGl()
{
	mPrepareWorker.Stop();
	for (LayerProgram& layer : mLayers)
	{
		for (LayerProgram::PassProgram& pass : layer.passes)
		{
			if (pass.renderer)
				pass.renderer->ShutdownGl();
		}
		ReleasePendingPrograms(layer);
	}
	mLayers.clear();
	mLayerOrder.clear();
	DestroyLayerTargets();
}

void RuntimeRenderScene::ConsumePreparedPrograms()
{
	RuntimePreparedShaderProgram preparedProgram;
	while (mPrepareWorker.TryConsume(preparedProgram))
	{
		if (!preparedProgram.succeeded)
		{
			preparedProgram.ReleaseGl();
			continue;
		}

		LayerProgram* layer = FindLayer(preparedProgram.layerId);
		if (!layer || layer->pendingFingerprint != preparedProgram.sourceFingerprint)
		{
			preparedProgram.ReleaseGl();
			continue;
		}

		bool replacesExistingPendingPass = false;
		for (RuntimePreparedShaderProgram& existing : layer->pendingPreparedPrograms)
		{
			if (existing.passId != preparedProgram.passId)
				continue;
			existing.ReleaseGl();
			existing = std::move(preparedProgram);
			replacesExistingPendingPass = true;
			break;
		}
		if (!replacesExistingPendingPass)
			layer->pendingPreparedPrograms.push_back(std::move(preparedProgram));
		TryCommitPendingPrograms(*layer);
	}
}

void RuntimeRenderScene::ReleasePendingPrograms(LayerProgram& layer)
{
	for (RuntimePreparedShaderProgram& program : layer.pendingPreparedPrograms)
		program.ReleaseGl();
	layer.pendingPreparedPrograms.clear();
}

void RuntimeRenderScene::TryCommitPendingPrograms(LayerProgram& layer)
{
	if (layer.pendingPreparedPrograms.empty())
		return;

	const RuntimeShaderArtifact& artifact = layer.pendingPreparedPrograms.front().artifact;
	const std::size_t expectedPassCount = artifact.passes.empty() ? 1 : artifact.passes.size();
	if (layer.pendingPreparedPrograms.size() < expectedPassCount)
		return;

	std::vector<LayerProgram::PassProgram> nextPasses;
	nextPasses.reserve(expectedPassCount);
	for (const RuntimeShaderPassArtifact& passArtifact : artifact.passes)
	{
		auto preparedIt = std::find_if(
			layer.pendingPreparedPrograms.begin(),
			layer.pendingPreparedPrograms.end(),
			[&passArtifact](const RuntimePreparedShaderProgram& prepared) {
				return prepared.passId == passArtifact.passId;
			});
		if (preparedIt == layer.pendingPreparedPrograms.end())
			return;

		std::unique_ptr<RuntimeShaderRenderer> nextRenderer = std::make_unique<RuntimeShaderRenderer>();
		std::string error;
		if (!nextRenderer->CommitPreparedProgram(*preparedIt, error))
		{
			ReleasePendingPrograms(layer);
			return;
		}

		LayerProgram::PassProgram nextPass;
		nextPass.passId = preparedIt->passId;
		nextPass.inputNames = preparedIt->inputNames;
		nextPass.outputName = preparedIt->outputName.empty() ? preparedIt->passId : preparedIt->outputName;
		nextPass.renderer = std::move(nextRenderer);
		nextPasses.push_back(std::move(nextPass));
	}
	if (artifact.passes.empty())
	{
		RuntimePreparedShaderProgram& prepared = layer.pendingPreparedPrograms.front();
		std::unique_ptr<RuntimeShaderRenderer> nextRenderer = std::make_unique<RuntimeShaderRenderer>();
		std::string error;
		if (!nextRenderer->CommitPreparedProgram(prepared, error))
		{
			ReleasePendingPrograms(layer);
			return;
		}

		LayerProgram::PassProgram nextPass;
		nextPass.passId = prepared.passId;
		nextPass.inputNames = prepared.inputNames;
		nextPass.outputName = prepared.outputName.empty() ? prepared.passId : prepared.outputName;
		nextPass.renderer = std::move(nextRenderer);
		nextPasses.push_back(std::move(nextPass));
	}

	for (LayerProgram::PassProgram& pass : layer.passes)
	{
		if (pass.renderer)
			pass.renderer->ShutdownGl();
	}
	layer.passes = std::move(nextPasses);
	layer.shaderId = artifact.shaderId;
	layer.sourceFingerprint = layer.pendingPreparedPrograms.front().sourceFingerprint;
	layer.pendingFingerprint.clear();
	layer.pendingPreparedPrograms.clear();
}

RuntimeRenderScene::LayerProgram* RuntimeRenderScene::FindLayer(const std::string& layerId)
{
	for (LayerProgram& layer : mLayers)
	{
		if (layer.layerId == layerId)
			return &layer;
	}
	return nullptr;
}

const RuntimeRenderScene::LayerProgram* RuntimeRenderScene::FindLayer(const std::string& layerId) const
{
	for (const LayerProgram& layer : mLayers)
	{
		if (layer.layerId == layerId)
			return &layer;
	}
	return nullptr;
}

RuntimeRenderScene::LayerProgram::PassProgram* RuntimeRenderScene::FindPass(LayerProgram& layer, const std::string& passId)
{
	for (LayerProgram::PassProgram& pass : layer.passes)
	{
		if (pass.passId == passId)
			return &pass;
	}
	return nullptr;
}

std::string RuntimeRenderScene::Fingerprint(const RuntimeShaderArtifact& artifact)
{
	const std::hash<std::string> hasher;
	std::string source;
	for (const RuntimeShaderPassArtifact& pass : artifact.passes)
		source += pass.passId + ":" + pass.outputName + ":" + pass.fragmentShaderSource + "\n";
	if (source.empty())
		source = artifact.fragmentShaderSource;
	return artifact.shaderId + ":" + std::to_string(source.size()) + ":" + std::to_string(hasher(source));
}
