#include "servicemanager.h"

#include <cstdio>
#include <csignal>
#include <boost/bind.hpp>
#include <boost/make_shared.hpp>

#ifdef __WIN32__
#include <windows.h>
#else
#include <unistd.h>
#endif

namespace {

/* Data and handler for SIGTERM.
 * We can't just kill the process, as some tasks may still be active.
 */
bool gRunning = true;
void sigterm_handler(int sig)
{
	if(sig == SIGTERM)
		gRunning = false;
}

/* Utility function for sleeping between tasker calls.
 */
void msleep(unsigned int milliseconds)
{
#ifdef __WIN32__
	Sleep(milliseconds);
#else
	usleep(static_cast<useconds_t>(milliseconds) * 1000);
#endif
}

}

namespace nlp_service {

ServiceManager::ServiceManager(ServicePtr service, Service::service_config config,
								ServiceLoggerPtr logger, bool run_as_daemon)
: config(config), logger(logger), task_manager(new TaskManager(logger, config)),
  service(service), run_as_daemon(run_as_daemon)
{
}

ServiceManager::~ServiceManager()
{
	this->logger->info("Service is exiting");
}

void ServiceManager::run()
{
	this->logger->info("Service is starting");
	try {
		if(this->run_as_daemon) {
			this->logger->debug("Starting as daemon");
			this->detach();
		}

		this->run_loop();
	} catch (std::exception& e) {
		this->logger->error(e);
	}

	this->thread_pool_ptr->wait();
	this->logger->info("Service is exiting");
}

/* Runs the main get task -> delegate -> finish_task loop as long as it won't receive the SIGTERM.
 * Also logs the information about new tasks.
 */
void ServiceManager::run_loop()
{
	this->init_threadpool();
	while(gRunning) {
		TaskManager::task_description new_task = this->task_manager->get_new_task();
		std::string task_file = new_task.first;
		if(!task_file.empty()) {
			std::string task_token = this->task_manager->get_task_token(task_file);
			this->logger->info("New task: " + task_token);

			this->thread_pool_ptr->schedule(boost::bind(&ServiceManager::process, this, new_task));
		} else {
			msleep(this->config.get("service.sleep", 100));
		}
	}
}

/* Processing function. It's a wrapper around Service's processing method made in order to
 * catch exceptions comming from worker threads and setting success flag to false
 * when the worker thread fails.
 */
void ServiceManager::process(TaskManager::task_description task)
{
	std::string task_token = this->task_manager->get_task_token(task.first);
	std::string output_path = this->task_manager->get_task_done_path(task_token);

	bool success = true;

	try {
		this->service->process(task.first, task.second, output_path);
	} catch (std::exception& e) {
		this->logger->task_error(task_token, e);
		success = false;
	}

	this->task_manager->finish_task(task_token, success);
	this->logger->close_task_log(task_token);
}

void ServiceManager::init_threadpool()
{
	int workers = this->config.get("service.workers", 0);
	if(workers == 0)
		workers = boost::thread::hardware_concurrency();
	if(workers == 0)
		workers = 1;

	this->thread_pool_ptr = boost::make_shared<boost::threadpool::pool>(workers);
}

/* When run in daemonized mode (run_as_daemon = true) this function detaches the process
 * from the shell, closes all standard streams and registers handler for SIGTERM.
 */
void ServiceManager::detach()
{
	pid_t child_pid = fork();
	if(child_pid < 0) {
		this->logger->error("Unable to initialize child process");
		exit(EXIT_FAILURE);
	}
	if(child_pid > 0) {
		std::stringstream message; message << "New process id: " << child_pid;
		this->logger->trace(message.str());
		exit(EXIT_SUCCESS);
	}

	signal(SIGTERM, sigterm_handler);
	signal(SIGHUP, SIG_IGN);

	fclose(stdin);
	fclose(stdout);

	std::string main_log_path = this->logger->get_main_log_path();
	FILE *res = freopen(main_log_path.c_str(), "w", stderr);
	if(res == NULL)
		throw std::runtime_error("Unable to open logfile for stderr");
}

}	// end namespace nlp_service
