#include <config.h>
#include <iostream>
#include <opts.h>
#include <componentThread.h>
#include <spinscale/asynchronousContinuation.h>
#include <spinscale/asynchronousLoop.h>
#include <spinscale/callback.h>
#include <spinscale/callableTracer.h>
#include <spinscale/componentThread.h>
#include <mind.h>
#include <mindThread.h>
#include <director/director.h>
#include <simulator/simulator.h>
#include <stimBuffApis/stimBuffApiManager.h>
#include <marionette/marionette.h>

namespace smo {

Mind::Mind(void)
:	sscl::PuppetApplication(
		std::vector<std::shared_ptr<sscl::PuppetThread>>{
			std::make_shared<MindThread>(
				SmoThreadId::DIRECTOR, getThreadName(SmoThreadId::DIRECTOR),
				sscl::PuppetComponent::defaultPuppetMain, director,
				&MindComponent::preJoltHook),
			std::make_shared<MindThread>(
				SmoThreadId::SIMULATOR, getThreadName(SmoThreadId::SIMULATOR),
				sscl::PuppetComponent::defaultPuppetMain, canvas,
				&MindComponent::preJoltHook),
			std::make_shared<MindThread>(
				SmoThreadId::SUBCONSCIOUS,
				getThreadName(SmoThreadId::SUBCONSCIOUS),
				sscl::PuppetComponent::defaultPuppetMain, subconscious,
				&MindComponent::preJoltHook),
			std::make_shared<MindThread>(
				SmoThreadId::BODY, getThreadName(SmoThreadId::BODY),
				sscl::PuppetComponent::defaultPuppetMain, body,
				&MindComponent::preJoltHook)
#ifndef CONFIG_WORLD_USE_BODY_THREAD
			, std::make_shared<MindThread>(
				SmoThreadId::WORLD, getThreadName(SmoThreadId::WORLD),
				sscl::PuppetComponent::defaultPuppetMain, world,
				&MindComponent::preJoltHook)
#endif
		}
	),
director(*this, componentThreads[SmoThreadId::DIRECTOR - 1]),
canvas(*this, componentThreads[SmoThreadId::SIMULATOR - 1]),
subconscious(*this, componentThreads[SmoThreadId::SUBCONSCIOUS - 1]),
body(*this, componentThreads[SmoThreadId::BODY - 1]),
world(*this, componentThreads[SmoThreadId::WORLD - 1])
{
}

std::shared_ptr<MindThread>
Mind::getComponentThread(sscl::ThreadId id) const
{
	if (id == SmoThreadId::MRNTT)
	{
		throw std::runtime_error(
			std::string(__func__) +
			": MRNTT is not a MindThread and cannot be returned by "
			"getComponentThread");
	}

    // Search through the vector for the thread with matching id
    for (auto& thread : componentThreads)
	{
        if (thread->id == id) {
			return std::static_pointer_cast<MindThread>(thread);
		}
    }

    // Throw exception if no thread found
    throw std::runtime_error(std::string(__func__) +
        ": No MindThread found with ID "
		+ std::to_string(static_cast<int>(id)));
}

std::shared_ptr<MindThread>
Mind::getComponentThread(const std::string& name) const
{
	if (name == "mrntt")
	{
		throw std::runtime_error(
			std::string(__func__) +
			": MRNTT is not a MindThread and cannot be returned by "
			"getComponentThread");
	}

    for (auto& thread : componentThreads)
	{
        if (thread->name == name) {
			return std::static_pointer_cast<MindThread>(thread);
		}
    }

    // Throw exception if no thread found
    throw std::runtime_error(std::string(__func__) +
        ": No MindThread found with name '" + name + "'");
}

std::vector<std::shared_ptr<MindThread>>
Mind::getMindThreads() const
{
	std::vector<std::shared_ptr<MindThread>> mindThreads;
	mindThreads.reserve(componentThreads.size());
	for (auto& thread : componentThreads) {
		mindThreads.push_back(std::static_pointer_cast<MindThread>(thread));
	}
	return mindThreads;
}

class Mind::MindLifetimeMgmtOp
:	public sscl::PostedAsynchronousContinuation<mindLifetimeMgmtOpCbFn>
{
public:
	MindLifetimeMgmtOp(
		Mind &parent, const std::shared_ptr<sscl::ComponentThread> &caller,
		sscl::Callback<mindLifetimeMgmtOpCbFn> callback)
	:	sscl::PostedAsynchronousContinuation<mindLifetimeMgmtOpCbFn>(
			caller, callback),
		parent(parent)
	{}

public:
	Mind &parent;

public:
	void initializeReq1_posted(
		[[maybe_unused]] std::shared_ptr<MindLifetimeMgmtOp> context
		)
	{
		/* Jolt the threads, then start them */
		parent.joltAllPuppetThreadsReq(
			{context, std::bind(
				&MindLifetimeMgmtOp::initializeReq2,
				context.get(), context)});
	}

	void initializeReq2(
		[[maybe_unused]] std::shared_ptr<MindLifetimeMgmtOp> context
		)
	{
		std::cout << "Mrntt: All mind threads JOLTed." << "\n";

		parent.startAllPuppetThreadsReq(
			{context, std::bind(
				&MindLifetimeMgmtOp::initializeReq3,
				context.get(), context)});
	}

	void initializeReq3(
		[[maybe_unused]] std::shared_ptr<MindLifetimeMgmtOp> context
		)
	{
		std::cout << "Mrntt: All mind threads started." << "\n";

		parent.body.initializeReq(
			{context, std::bind(
				&MindLifetimeMgmtOp::initializeReq4,
				context.get(), context, std::placeholders::_1)});
	}

	void initializeReq4(
		[[maybe_unused]] std::shared_ptr<MindLifetimeMgmtOp> context,
		bool success
		)
	{
		std::cout << "Mrntt: Body component initialized." << "\n";
		callOriginalCb(success);
	}

	void finalizeReq1_posted(
		[[maybe_unused]] std::shared_ptr<MindLifetimeMgmtOp> context
		)
	{
		parent.body.finalizeReq(
			{context, std::bind(
				&MindLifetimeMgmtOp::finalizeReq2,
				context.get(), context, std::placeholders::_1)});
	}

	void finalizeReq2(
		[[maybe_unused]] std::shared_ptr<MindLifetimeMgmtOp> context,
		bool success
		)
	{
		if (!success) {
			std::cerr << "Mrntt: Body component failed to finalize." << "\n";
		} else {
			std::cout << "Mrntt: Body component finalized." << "\n";
		}

		/* If the threads haven't been jolted, we need to do that first, because
		* otherwise they'll just enter their main loops and wait for control
		* messages from mrntt after processing the exit request.
		*/
		parent.joltAllPuppetThreadsReq(
			{context, std::bind(
				&MindLifetimeMgmtOp::finalizeReq3,
				context.get(), context)});
	}

	void finalizeReq3(
		[[maybe_unused]] std::shared_ptr<MindLifetimeMgmtOp> context
		)
	{
		std::cout << "Mrntt: All mind threads JOLTed for finalization." << "\n";

		parent.exitAllPuppetThreadsReq(
			{context, std::bind(
				&MindLifetimeMgmtOp::finalizeReq4,
				context.get(), context)});
	}

	void finalizeReq4(
		[[maybe_unused]] std::shared_ptr<MindLifetimeMgmtOp> context
		)
	{
		std::cout << "Mrntt: All mind threads exited." << "\n";
		callOriginalCb(true);
	}
};

void Mind::initializeReq(sscl::Callback<mindLifetimeMgmtOpCbFn> callback)
{
    /* Distribute threads across available CPUs */
    try
    {
        distributeAndPinThreadsAcrossCpus();
    }
    catch (const std::exception& e)
    {
        std::cerr << "Salmanoff couldn't distribute the mind threads across "
            "the CPUs, so performance may be suboptimal.\n"
            "Error: " << e.what() << "\n";
    }

	const auto& caller = sscl::ComponentThread::getSelf();
	auto request = std::make_shared<MindLifetimeMgmtOp>(
		*this, caller, callback);

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

void Mind::finalizeReq(sscl::Callback<mindLifetimeMgmtOpCbFn> callback)
{
	const auto& caller = sscl::ComponentThread::getSelf();
	auto request = std::make_shared<MindLifetimeMgmtOp>(
		*this, caller, callback);

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

} // namespace smo
