#include <cstdlib>
#include <iostream>
#include <stdexcept>

#include <boost/asio/post.hpp>
#include <componentThread.h>
#include <deviceManager/deviceManager.h>
#include <marionette/marionette.h>
#include <marionette/marionetteThread.h>
#include <mindManager/mindManager.h>
#include <spinscale/co/nonViralCompletion.h>
#include <spinscale/componentThread.h>

namespace smo {
namespace mrntt {

namespace {

void assertMarionetteThread()
{
	auto self = sscl::ComponentThread::getSelf();
	if (self->id != SmoThreadId::MRNTT)
	{
		throw std::runtime_error(
			std::string(__func__)
			+ ": Must be executed on Marionette thread");
	}
}

} // namespace

void MarionetteComponent::holdInitializeCReq(
	std::function<void(std::exception_ptr &exceptionPtr)> completion)
{
	taskNursery.launch(
		[this](sscl::co::NonViralTaskNursery::Slot::Lease &lease)
		{
			return initializeCReq(
				lease.getExceptionStorage(),
				lease.getCallerLambda());
		},
		std::move(completion));
}

void MarionetteComponent::holdFinalizeCReq(
	std::function<void(std::exception_ptr &exceptionPtr)> completion)
{
	taskNursery.launch(
		[this](sscl::co::NonViralTaskNursery::Slot::Lease &lease)
		{
			return finalizeCReq(
				lease.getExceptionStorage(),
				lease.getCallerLambda());
		},
		std::move(completion));
}

MrnttNonViralPostingInvoker MarionetteComponent::initializeCReq(
	[[maybe_unused]] std::exception_ptr &exceptionPtr,
	[[maybe_unused]] std::function<void()> callback)
{
	assertMarionetteThread();

	smo::mind::globalMind = std::make_shared<smo::Mind>();

	co_await smo::mind::globalMind->initializeCReq();

	smo::device::DeviceManager::getInstance().initializeDeviceReattacher();

	// Call negtrinEventInd on the Director in the final callback
	smo::mind::globalMind->director.negtrinEventInd();

	co_return;
}

MrnttNonViralPostingInvoker MarionetteComponent::finalizeCReq(
	[[maybe_unused]] std::exception_ptr &exceptionPtr,
	[[maybe_unused]] std::function<void()> callback)
{
	assertMarionetteThread();

	smo::device::DeviceManager::getInstance().finalizeDeviceReattacher();

	if (!smo::mind::globalMind) { co_return; }

	co_await smo::mind::globalMind->finalizeCReq();

	co_return;
}

void MarionetteComponent::handleLoopExceptionHook()
{
	sscl::pptr::exitCode = EXIT_FAILURE;
	exceptionInd();
}

void MarionetteComponent::exceptionInd()
{
	auto puppeteer = sscl::ComponentThread::getPptr();

	boost::asio::post(
		puppeteer->getIoContext(),
		[]
		{
			mrntt.holdFinalizeCReq(
				[](std::exception_ptr &exceptionPtr)
				{
					sscl::co::NonViralCompletion nvc(exceptionPtr);
					nvc.checkAndRethrowException();
					marionetteFinalizeReqCb(true);
				});
		});
}

} // namespace mrntt
} // namespace smo
