#include "app/AppConfig.h"
#include "app/AppConfigProvider.h"
#include "app/RenderCadenceApp.h"
#include "frames/SystemFrameExchange.h"
#include "logging/Logger.h"
#include "render/RenderThread.h"
#include "VideoIOFormat.h"

#include <windows.h>

#include <iostream>
#include <sstream>
#include <string>

namespace
{
class ComInitGuard
{
public:
	~ComInitGuard()
	{
		if (mInitialized)
			CoUninitialize();
	}

	bool Initialize()
	{
		const HRESULT result = CoInitialize(nullptr);
		mInitialized = SUCCEEDED(result);
		mResult = result;
		return mInitialized;
	}

	HRESULT Result() const { return mResult; }

private:
	bool mInitialized = false;
	HRESULT mResult = S_OK;
};
}

int main(int argc, char** argv)
{
	RenderCadenceCompositor::AppConfigProvider configProvider;
	std::string configError;
	if (!configProvider.LoadDefault(configError))
	{
		RenderCadenceCompositor::Logger::Instance().Start(RenderCadenceCompositor::DefaultAppConfig().logging);
		RenderCadenceCompositor::LogError("app", "Config load failed: " + configError);
		RenderCadenceCompositor::Logger::Instance().Stop();
		return 1;
	}
	configProvider.ApplyCommandLine(argc, argv);

	RenderCadenceCompositor::AppConfig appConfig = configProvider.Config();
	RenderCadenceCompositor::Logger::Instance().Start(appConfig.logging);
	RenderCadenceCompositor::Log(
		"app",
		"RenderCadenceCompositor starting. Starts render cadence, system-memory exchange, DeckLink scheduled output, and telemetry. Press Enter to stop.");
	RenderCadenceCompositor::Log("app", "Loaded config from " + configProvider.SourcePath().string());

	ComInitGuard com;
	if (!com.Initialize())
	{
		std::ostringstream message;
		message << "COM initialization failed: 0x" << std::hex << com.Result();
		RenderCadenceCompositor::LogError("app", message.str());
		RenderCadenceCompositor::Logger::Instance().Stop();
		return 1;
	}

	SystemFrameExchangeConfig frameExchangeConfig;
	RenderCadenceCompositor::VideoFormatDimensions(
		appConfig.outputVideoFormat,
		frameExchangeConfig.width,
		frameExchangeConfig.height);
	frameExchangeConfig.pixelFormat = VideoIOPixelFormat::Bgra8;
	frameExchangeConfig.rowBytes = VideoIORowBytes(frameExchangeConfig.pixelFormat, frameExchangeConfig.width);
	frameExchangeConfig.capacity = 12;

	SystemFrameExchange frameExchange(frameExchangeConfig);

	RenderThread::Config renderConfig;
	renderConfig.width = frameExchangeConfig.width;
	renderConfig.height = frameExchangeConfig.height;
	renderConfig.frameDurationMilliseconds = RenderCadenceCompositor::FrameDurationMillisecondsFromRateString(appConfig.outputFrameRate);
	renderConfig.pboDepth = 6;

	RenderThread renderThread(frameExchange, renderConfig);

	RenderCadenceCompositor::RenderCadenceApp<RenderThread, SystemFrameExchange> app(renderThread, frameExchange, appConfig);

	std::string error;
	if (!app.Start(error))
	{
		RenderCadenceCompositor::LogError("app", "RenderCadenceCompositor start failed: " + error);
		RenderCadenceCompositor::Logger::Instance().Stop();
		return 1;
	}

	std::string line;
	std::getline(std::cin, line);
	app.Stop();
	RenderCadenceCompositor::Log("app", "RenderCadenceCompositor stopped.");
	RenderCadenceCompositor::Logger::Instance().Stop();
	return 0;
}
