#include <iostream>
#include <opts.h>
#include <asynchronousContinuation.h>
#include <asynchronousLoop.h>
#include <callback.h>
#include <body/body.h>
#include <componentThread.h>
#include <mind.h>
#include <stimBuffApis/stimBuffApiManager.h>
#include <deviceManager/deviceManager.h>

namespace smo {
namespace body {

Body::Body(Mind &parent, const std::shared_ptr<ComponentThread> &thread)
    : MindComponent(parent, thread)
{
}

class Body::InitializeReq
:	public PostedAsynchronousContinuation<bodyLifetimeMgmtOpCbFn>
{
public:
	InitializeReq(
		Mind &parent, const std::shared_ptr<ComponentThread> &caller,
		Callback<bodyLifetimeMgmtOpCbFn> callback)
	:	PostedAsynchronousContinuation<bodyLifetimeMgmtOpCbFn>(caller, callback),
	parent(parent)
	{}

private:
	Mind &parent;

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

		/**	EXPLANATION:
		 * The ComponentThread instance we pass in here is the one that will be
		 * used by Senseapi libs to perform device-independent background
		 * operations.
		 * For example, liblivoxProto1's BroadcastListener will use this thread
		 * to listen for UDP broadcast dgrams from Livox devices.
		 *
		 * Right now we use Marionette, but there's a strong argument for using
		 * Body instead since it's meant to handle device-management operations.
		 */
		stim_buff::StimBuffApiManager::getInstance()
			.loadAllStimBuffApiLibsFromOptions(caller);

		/**	EXPLANATION:
		 * Consider body::initializeReq to have been called if even one of its
		 * operations was executed at all, whether successfully or
		 * unsuccessfully.
		 */
		context->parent.bodyComponentInitialized = true;

		std::cout << stim_buff::StimBuffApiManager::getInstance().stringifyLibs()
			<< std::endl;

		if (OptionParser::getOptions().verbose)
		{
			std::cout << __func__ << ": About to initializeAllStimBuffApiLibs"
				<< '\n';
		}
		stim_buff::StimBuffApiManager::getInstance()
			.initializeAllStimBuffApiLibs();

		if (OptionParser::getOptions().verbose)
		{
			std::cout << __func__ << ": About to attachAllSenseDevicesFromSpecs"
				<< '\n';
		}
		device::DeviceManager::getInstance()
			.attachAllUnattachedDevicesFromCmdlineReq(
				{context, std::bind(
					&InitializeReq::initializeReq2,
					context.get(), context,
					std::placeholders::_1)});
	}

	void initializeReq2(
		[[maybe_unused]] std::shared_ptr<InitializeReq> context,
		smo::AsynchronousLoop &results
		)
	{
		std::cout << "Mrntt: attached "
			<< results.nSucceeded << " of " << results.nTotal
			<< " sense devices." << "\n";

		callOriginalCb(results.nSucceeded > 0);
	}
};

class Body::FinalizeReq
:	public InitializeReq
{
public:
	using InitializeReq::InitializeReq;

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

		std::cout << "Mrntt: About to detach all sense devices." << "\n";
		device::DeviceManager::getInstance().detachAllAttachedDeviceRoles(
			{context, std::bind(
				&FinalizeReq::finalizeReq2,
				context.get(), context,
				std::placeholders::_1)});
	}

	void finalizeReq2(
		[[maybe_unused]] std::shared_ptr<FinalizeReq> context,
		smo::AsynchronousLoop &results
	)
	{
		std::cout << "Mrntt: Successfully detached "
			<< results.nSucceeded << " of " << results.nTotal
			<< " sense devices." << "\n";

		std::cout << "Mrntt: About to finalize all stim buff api libs." << "\n";
		stim_buff::StimBuffApiManager::getInstance().finalizeAllStimBuffApiLibs();

		std::cout << "Mrntt: About to unload all stim buff api libs." << "\n";
		stim_buff::StimBuffApiManager::getInstance().unloadAllStimBuffApiLibs();
		callOriginalCb(results.nSucceeded == results.nTotal);
	}
};

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

	if (mrntt->id != ComponentThread::MRNTT)
	{
		throw std::runtime_error(std::string(__func__)
			+ ": Must be invoked by Mrntt thread");
	}

	auto request = std::make_shared<InitializeReq>(
		parent, mrntt, callback);

	thread->getIoService().post(
		std::bind(
			&InitializeReq::initializeReq1_posted,
			request.get(), request));
}

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

	if (mrntt->id != ComponentThread::MRNTT)
	{
		std::cerr << __func__ << ": Must be invoked by Mrntt thread"
		          << std::endl;
		callback.callbackFn(false);
		return;
	}

	if (!parent.bodyComponentInitialized)
	{
		std::cout << "Mrntt: Body component not initialized. "
			<< "Skipping finalization." << "\n";
		callback.callbackFn(true);
		return;
	}

	auto request = std::make_shared<FinalizeReq>(
		parent, mrntt, callback);

	thread->getIoService().post(
		std::bind(
			&FinalizeReq::finalizeReq1_posted,
			request.get(), request));
}

} // namespace body
} // namespace smo
