#include <config.h>
#include <cstdlib>
#include <iostream>
#include <exception>
#include <opts.h>
#include <typeinfo>
#include <boost/asio/signal_set.hpp>
#include <asynchronousBridge.h>
#include <mindManager/mindManager.h>
#include <componentThread.h>
#include <marionette/marionette.h>
#include <salmanoff.h>

namespace smo {

CrtCommandLineArgs crtCommandLineArgs(0, nullptr, nullptr);

void CrtCommandLineArgs::set(int argc, char *argv[], char *envp[])
{
    crtCommandLineArgs = CrtCommandLineArgs(argc, argv, envp);
}

namespace mrntt {
std::atomic<int> exitCode;
// Global marionette thread instance
std::shared_ptr<MarionetteThread> thread =
	std::make_shared<MarionetteThread>();
MarionetteComponent mrntt(thread);

void exitMarionetteLoop()
{
	mrntt::thread->keepLooping = false;
	mrntt::thread->getIoService().stop();
	std::cout << "Mrntt: Signaled main loop to exit." << "\n";
}

void marionetteFinalizeReqCb(bool success)
{
	if (!success) {
		std::cerr << __func__ << ": Failed to finalize Marionette." << '\n';
	}
	std::cout << __func__ << ": Marionette finalized." << '\n';
	exitMarionetteLoop();
}

void marionetteInitializeReqCb(bool success)
{
	if (success)
	{
		std::cout << __func__ << ": Marionette initialized." << '\n';
		return;
	}

	std::cerr << __func__ << ": Failed to initialize Marionette. Shutting down."
		<< '\n';

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

} // namespace mrntt

void MarionetteThread::main(MarionetteThread& self)
{
    // Wait for CRT's main() to post us the command line args.
    std::cout << __func__ << ": Waiting for command line JOLT" << std::endl;
    self.getIoService().run();
    self.initializeTls();
    mrntt::exitCode = EXIT_SUCCESS;
	static boost::asio::signal_set signals(self.getIoService(), SIGINT);
	bool callFinalizeReq = false, callShutdownSalmanoff = false;

	try {
		// Register SIGINT (Ctrl+C) and SIGSEGV handlers
		signals.async_wait(
			[&self](const boost::system::error_code& ec, int signal)
			{
				if (ec) { return; }

				switch (signal) {
					case SIGINT:
						std::cerr << "SIGINT (Ctrl+C) received. Initiating "
							"shutdown..." << '\n';
						break;
					case SIGSEGV:
						std::cerr << "FATAL: Segmentation fault detected. "
							"Initiating shutdown..." << '\n';
						break;
					default:
						break;
				}
				mrntt::mrntt.finalizeReq(
					std::bind(
						&mrntt::marionetteFinalizeReqCb,
						std::placeholders::_1));
			}
		);

		OptionParser &options = OptionParser::getOptions();

		std::cout << __func__ << ": " << PACKAGE_NAME << " " << PACKAGE_VERSION
			<< std::endl;

		options.parseArguments(
			crtCommandLineArgs.argc, crtCommandLineArgs.argv,
			crtCommandLineArgs.envp);

		std::cout << __func__ << ": " << options.stringifyOptions()
			<< std::endl;

		if (options.printUsage) {
			throw JustPrintUsageNoError(options);
		}

		/**		EXPLANATION:
		 * Initialize Salmanoff's Manager classes first.
		 * Manager classes' initialization is synchronous in nature, so we
		 * don't need the minds to be running to initialize them.
		 *
		 * Then we initialize the Minds. Minds are asynchronous and they
		 * call upon the async methods of the Manager classes. Device
		 * attachment is actually Mind specific and not Smo-global
		 *
		 * It is arguable whether library loading is Mind specific or
		 * Smo-global. You could argue that libraries should be loaded and
		 * unloaded dynamically as-needed by the bodies and worlds of
		 * particular Minds. You could also argue that we should load all
		 * libraries at startup and unload them at shutdown.
		 *
		 * The latter is cleaner and more resource-respecting. The former is
		 * easier to implement.
		 */
		initializeSalmanoff();
		callShutdownSalmanoff = true;

		// Create new Mind instance just before initializeReq
		mrntt::mrntt.initializeReq(
			std::bind(
				&mrntt::marionetteInitializeReqCb, std::placeholders::_1));

		std::cout << __func__ << ": Entering event loop" << "\n";

		/* We loop here because when an exception occurs in mrntt, we need to
		 * both direct the mind threads to exit gracefully, and then we also
		 * need to post messages to our own event loop to initiate our own
		 * orderly exit. So we loop here to re-enter the event loop, both
		 * to receive the ACK messages from the mind threads, and to post
		 * messages to our own event loop to initiate our own orderly exit.
		 */
        for (self.keepLooping = true; self.keepLooping;)
        {
			bool sendExceptionInd = false;
            try {
				/**		EXPLANATION:
				 * This reset() call is crucial for async bridging patterns
				 * to work.
				 * When the outermost thread's io_service is stop()ped (e.g.,
				 * from JOLT sequence), it won't process any new work until
				 * reset() is called, even if nested async operations try to
				 * post work to it. This means async bridges invoked from
				 * the outermost thread main sequence won't work until this
				 * reset() call.
				 */
				self.getIoService().reset();
				self.getIoService().run();
            }
            catch (const std::exception& e)
            {
				sendExceptionInd = true;
                std::cerr << self.name << ":" << __func__
                    << ": Exception occurred: " << e.what() << "\n";
            }
            catch (...)
            {
				sendExceptionInd = true;
                std::cerr << self.name << ":" << __func__
                    << ": Unknown exception occurred" << "\n";
            }

			if (sendExceptionInd)
			{
				mrntt::exitCode = EXIT_FAILURE;
				self.exceptionInd(self.shared_from_this());
			}
        }

        std::cout << __func__ << ": Exited event loop" << "\n";
	}
	catch (const OptionParser::Exception& e)
	{
		std::ostream *out = &std::cout;
		std::string outUsageMsg;

		if (typeid(e) == typeid(OptionsParserError))
		{
			mrntt::exitCode = EXIT_FAILURE;
			out = &std::cerr;
			outUsageMsg = std::string(__func__) + ": ";
		}

		*out << outUsageMsg << e.what() << std::endl;
		callFinalizeReq = true;
	}
	catch (const std::exception& e)
	{
		std::cerr << __func__ << ": Exception occurred: " << e.what()
			<< std::endl;
		mrntt::exitCode = EXIT_FAILURE;
		callShutdownSalmanoff = callFinalizeReq = true;
	}
	catch (...)
	{
		std::cerr << __func__ << ": Unknown exception occurred" << std::endl;
		mrntt::exitCode = EXIT_FAILURE;
		callShutdownSalmanoff = callFinalizeReq = true;
	}

	if (callFinalizeReq)
	{
std::cout << __func__ << ": About call finalizeReq at end of mrnttMain." << '\n';
		mind::globalMind->finalizeReq([](bool success)
		{
			if (!success) {
				std::cerr << "Failed to finalize Mind object (threads)" << '\n';
			}
			mrntt::thread->getIoService().stop();
		});
		self.getIoService().reset();
		self.getIoService().run();
	}

	if (callShutdownSalmanoff) {
		shutdownSalmanoff();
	}
}

} // namespace smo
