#include <cstdlib>
#include <iostream>
#include <spinscale/asynchronousContinuation.h>
#include <spinscale/asynchronousLoop.h>
#include <spinscale/callback.h>
#include <spinscale/callableTracer.h>
#include <spinscale/component.h>
#include <marionette/marionette.h>
#include <componentThread.h>
#include <deviceManager/deviceManager.h>
#include <mindManager/mindManager.h>

namespace smo {
namespace mrntt {

class MarionetteComponent::MrnttLifetimeMgmtOp
:	public sscl::PostedAsynchronousContinuation<mrnttLifetimeMgmtOpCbFn>
{
public:
	MrnttLifetimeMgmtOp(
		MarionetteComponent &parent,
		const std::shared_ptr<sscl::ComponentThread> &caller,
		sscl::Callback<mrnttLifetimeMgmtOpCbFn> callback)
	:	sscl::PostedAsynchronousContinuation<mrnttLifetimeMgmtOpCbFn>(
			caller, callback),
	parent(parent)
	{}

private:
	MarionetteComponent &parent;

public:
	void initializeReq1_posted(
		[[maybe_unused]] std::shared_ptr<MrnttLifetimeMgmtOp> context
		)
	{
		auto self = sscl::ComponentThread::getSelf();
		if (self->id != smo::SmoThreadId::MRNTT)
		{
			throw std::runtime_error(std::string(__func__)
				+ ": Must be executed on Marionette thread");
		}

		smo::mind::globalMind = std::make_shared<smo::Mind>();
		smo::mind::globalMind->initializeReq({context, std::bind(
			&MrnttLifetimeMgmtOp::initializeReq2,
			this, context, std::placeholders::_1)});
	}

	void initializeReq2(
		std::shared_ptr<MrnttLifetimeMgmtOp> context,
		bool success
		)
	{
		if (!success)
		{
			std::cerr << __func__ << ": Failed to initialize globalMind"
				<< std::endl;
			context->callOriginalCb(false);
			return;
		}

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

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

		context->callOriginalCb(success);
	}

	void finalizeReq1_posted(
		[[maybe_unused]] std::shared_ptr<MrnttLifetimeMgmtOp> context
		)
	{
		auto self = sscl::ComponentThread::getSelf();
		if (self->id != smo::SmoThreadId::MRNTT)
		{
			throw std::runtime_error(std::string(__func__)
				+ ": Must be executed on Marionette thread");
		}

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

		/**	FIXME:
		 * It may be necessary to add a delay here to ensure that all in-flight
		 * timer timeouts have finished executing? Or some other mechanism.
		 *
		 * We need some way to ensure that in-flight timeouts don't get fired
		 * during the finalize sequence. This is because they may depend on
		 * state that is being finalized or has been finalized at the point
		 * when they timeout.
		 *
		 * This seems to be actually happening with the delayed calls to
		 * AttachDeviceReq::attachDeviceReq2() inside of livoxGen1.cpp.
		 *
		 * One tactic might be to shut down device reattacher before finalizing
		 * and pause for a bit before continuing to shutdown other components.
		 */

		smo::mind::globalMind->finalizeReq({context, std::bind(
			&MrnttLifetimeMgmtOp::finalizeReq2,
			this, context, std::placeholders::_1)});
	}

	void finalizeReq2(
		std::shared_ptr<MrnttLifetimeMgmtOp> context,
		bool success
		)
	{
		if (!success)
		{
			std::cerr << __func__ << ": globalMind finalization failed"
				<< std::endl;
			context->callOriginalCb(false);
			return;
		}

		context->callOriginalCb(success);
	}
};

class MarionetteComponent::TerminationEvent
:	public sscl::PostedAsynchronousContinuation<mrnttLifetimeMgmtOpCbFn>
{
public:
	TerminationEvent(
		const std::shared_ptr<sscl::ComponentThread> &caller)
	:	sscl::PostedAsynchronousContinuation<mrnttLifetimeMgmtOpCbFn>(
			caller, {nullptr, nullptr})
	{}

public:
	void exceptionInd1_posted(
		[[maybe_unused]] std::shared_ptr<TerminationEvent> context
		)
	{
		auto self = sscl::ComponentThread::getSelf();
		if (self->id != smo::SmoThreadId::MRNTT)
		{
			throw std::runtime_error(std::string(__func__)
				+ ": Must be executed on Marionette thread");
		}

		smo::mrntt::mrntt.finalizeReq({nullptr, std::bind(
			&smo::mrntt::marionetteFinalizeReqCb,
			std::placeholders::_1)});
	}
};

void MarionetteComponent::initializeReq(
	sscl::Callback<mrnttLifetimeMgmtOpCbFn> callback)
{
	auto mrntt = sscl::ComponentThread::getSelf();

	if (mrntt->id != smo::SmoThreadId::MRNTT)
	{
		throw std::runtime_error(std::string(__func__)
			+ ": Must be executed on Marionette thread");
	}

	auto request = std::make_shared<MrnttLifetimeMgmtOp>(
		*this, mrntt, callback);

	mrntt->getIoService().post(
		STC(std::bind(
			&MrnttLifetimeMgmtOp::initializeReq1_posted,
			request.get(), request)));
}

void MarionetteComponent::finalizeReq(
	sscl::Callback<mrnttLifetimeMgmtOpCbFn> callback)
{
	auto mrntt = sscl::ComponentThread::getSelf();

	if (mrntt->id != smo::SmoThreadId::MRNTT)
	{
		throw std::runtime_error(std::string(__func__)
			+ ": Must be executed on Marionette thread");
	}

	auto request = std::make_shared<MrnttLifetimeMgmtOp>(
		*this, mrntt, callback);

	mrntt->getIoService().post(
		STC(std::bind(
			&MrnttLifetimeMgmtOp::finalizeReq1_posted,
			request.get(), request)));
}

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

void MarionetteComponent::exceptionInd()
{
	auto faultyThread = sscl::ComponentThread::getSelf();
	auto mrntt = sscl::ComponentThread::getPptr();

	auto request = std::make_shared<TerminationEvent>(
		faultyThread);

	mrntt->getIoService().post(
		STC(std::bind(
			&TerminationEvent::exceptionInd1_posted,
			request.get(), request)));
}

} // namespace mrntt
} // namespace smo
