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

namespace smo {
namespace mrntt {

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

private:
	MarionetteComponent &parent;

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

		smo::mind::globalMind = std::make_shared<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;
		}

		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 = ComponentThread::getSelf();
		if (self->id != ComponentThread::MRNTT)
		{
			throw std::runtime_error(std::string(__func__)
				+ ": Must be executed on Marionette thread");
		}

		/**	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.
		 */

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

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

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

	if (mrntt->id != ComponentThread::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(
		std::bind(
			&MrnttLifetimeMgmtOp::initializeReq1_posted,
			request.get(), request));
}

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

	if (mrntt->id != ComponentThread::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(
		std::bind(
			&MrnttLifetimeMgmtOp::finalizeReq1_posted,
			request.get(), request));
}

} // namespace mrntt
} // namespace smo
