#include <config.h>
#include <iostream>
#include <opts.h>
#include <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;
}

mrntt::MrnttViralNonPostingInvokerT<void> Mind::initializeCReq()
{
	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";
	}

	co_await joltAllPuppetThreadsCReq();
	std::cout << "Mrntt: All mind threads JOLTed." << "\n";

	co_await startAllPuppetThreadsCReq();
	std::cout << "Mrntt: All mind threads started." << "\n";

	mrntt::initializeSmoThreadingModelDesc(body.thread);

	co_await body.initializeCReq();
	std::cout << "Mrntt: Body component initialized." << "\n";

	co_return;
}

mrntt::MrnttViralNonPostingInvokerT<void> Mind::finalizeCReq()
{
	co_await body.finalizeCReq();
	std::cout << "Mrntt: Body component finalized." << "\n";

	co_await joltAllPuppetThreadsCReq();
	std::cout << "Mrntt: All mind threads JOLTed for finalization." << "\n";

	co_await exitAllPuppetThreadsCReq();
	std::cout << "Mrntt: All mind threads exited." << "\n";

	co_return;
}

} // namespace smo
