#include "logger.h"

#include <boost/algorithm/string/case_conv.hpp>
#include <boost/filesystem/path.hpp>

#include <log4cxx/appender.h>
#include <log4cxx/consoleappender.h>
#include <log4cxx/logmanager.h>
#include <log4cxx/rollingfileappender.h>

namespace nlp_service {

std::string ServiceLogger::PATTERN_STRING = "\%d{dd MMM yyyy HH:mm:ss,SSS} \%-5p [\%c]: \%m\%n";

ServiceLogger::ServiceLogger(logging_lvl level, std::string main_log_path, std::string task_logs_path, bool log_to_console)
: main_log_path(main_log_path), task_logs_path(task_logs_path),
  default_logging_level(ServiceLogger::translate_level(level)),
  default_layout(new log4cxx::PatternLayout(ServiceLogger::PATTERN_STRING))
{
	this->main_logger = this->get_main_logger(main_log_path, log_to_console);
}

ServiceLogger::~ServiceLogger()
{ }

void ServiceLogger::log(logging_lvl level, const std::string& message)
{
	this->log(this->main_logger, level, message);
}

void ServiceLogger::task_log(std::string task_token, logging_lvl level, const std::string& message)
{
	log4cxx::LoggerPtr task_logger = this->get_task_logger(task_token);
	this->log(task_logger, level, message);
}

void ServiceLogger::close_task_log(const std::string& task_token)
{
	log4cxx::LoggerPtr task_logger = this->get_task_logger(task_token);
	log4cxx::AppenderList appenders = task_logger->getAllAppenders();

	log4cxx::AppenderList::iterator iter;
	for(iter = appenders.begin(); iter != appenders.end(); iter++) {
		log4cxx::AppenderPtr appender = *iter;
		appender->close();
	}
}

logging_lvl ServiceLogger::str2logging_lvl(std::string str_level)
{
	boost::algorithm::to_upper(str_level);
	if (str_level == "TRACE")
		return TRACE_LVL;
	else if (str_level == "DEBUG")
		return DEBUG_LVL;
	else if (str_level == "INFO")
		return INFO_LVL;
	else if (str_level == "WARNING")
		return WARNING_LVL;
	else if (str_level == "ERROR")
		return ERROR_LVL;
	else
		throw std::invalid_argument("Wrong logging level. Possible values: \
									trace, debug, info, warning, error");
}

/* Logs message with certain level to the specified log4cxx Logger.
 */
void ServiceLogger::log(log4cxx::LoggerPtr logger, logging_lvl level, const std::string& message)
{
	log4cxx::LevelPtr log4cxx_level = this->translate_level(level);
	LOG4CXX_LOG(logger, log4cxx_level, message);
}

/* Gets/Creates main file logger.
 */
log4cxx::LoggerPtr ServiceLogger::get_main_logger(const std::string& main_log_path, bool log_to_console)
{
	using namespace log4cxx;

	bool inited = this->check_logger_init("service");
	LoggerPtr main_logger = this->get_logger("service", main_log_path);
	if(log_to_console && !inited) {
		LayoutPtr layout(this->default_layout);
		AppenderPtr appender(new ConsoleAppender(layout));
		main_logger->addAppender(appender);
	}
	return main_logger;
}

/* Gets/Creates logger for the given task (task_token).
 */
log4cxx::LoggerPtr ServiceLogger::get_task_logger(const std::string& task_token)
{
	namespace fs = boost::filesystem;

	std::string logger_name = "service.task-" + task_token;
	fs::path logfile_path(this->task_logs_path); logfile_path /= task_token;
	std::string log_filename = logfile_path.native();

	return this->get_logger(logger_name, log_filename);
}

/* Creates log4cxx logger with the given name writing to given file.
 * filename should be absolute path.
 */
log4cxx::LoggerPtr ServiceLogger::get_logger(const std::string& name, const std::string& filename)
{
	using namespace log4cxx;

	bool inited = this->check_logger_init(name);
	LoggerPtr logger(Logger::getLogger(name));

	if(!inited) {
		logger->setLevel(this->default_logging_level);

		LayoutPtr layout(this->default_layout);
		RollingFileAppenderPtr appender(new RollingFileAppender(layout, filename));
		appender->setMaxBackupIndex(10);
		appender->setMaxFileSize("1MB");

		logger->addAppender(appender);
	}

	return logger;
}

/* Checks if logger with the given name was created previously. If it is so, we MUST NOT
 * add any appenders - this will result in repeated lines in logfiles.
 */
bool ServiceLogger::check_logger_init(const std::string& logger_name)
{
	log4cxx::spi::LoggerRepositoryPtr repo = log4cxx::LogManager::getLoggerRepository();
	log4cxx::LoggerPtr exists = repo->exists(logger_name);

	if(exists)
		return true;
	return false;
}

/* Translates our logging level, that is public, into log4cxx logging level.
 */
log4cxx::LevelPtr ServiceLogger::translate_level(logging_lvl level)
{
	switch(level) {
		case TRACE_LVL:
			return log4cxx::Level::getTrace();
		case DEBUG_LVL:
			return log4cxx::Level::getDebug();
		case INFO_LVL:
			return log4cxx::Level::getInfo();
		case WARNING_LVL:
			return log4cxx::Level::getWarn();
		case ERROR_LVL:
			return log4cxx::Level::getError();
		default:
			throw std::invalid_argument("Unknown logging level");
	}
}

}	//end namespace nlp_service
