#include "logger.hh"
#include <QFileInfo>
#include <QDir>
#include <QDateTime>


/* ********************************************************************************************* *
 * Implementation of LogMessage
 * ********************************************************************************************* */
LogMessage::LogMessage(Level level, const QString &file, int line, const QString &message)
  : QTextStream(), _level(level), _file(file), _line(line), _message(message)
{
  this->setString(&_message);
  this->seek(_message.size());
}

LogMessage::LogMessage(const LogMessage &other)
  : QTextStream(), _level(other._level), _file(other._file), _line(other._line), _message(other._message)
{
  this->setString(&_message);
  this->seek(_message.size());
}

LogMessage::~LogMessage() {
  Logger::get().log(*this);
}

LogMessage::Level
LogMessage::level() const {
  return _level;
}

const QString &
LogMessage::file() const {
  return _file;
}

int
LogMessage::line() const {
  return _line;
}

const QString &
LogMessage::message() const {
  return _message;
}


/* ********************************************************************************************* *
 * Implementation of LogHandler
 * ********************************************************************************************* */
LogHandler::LogHandler(QObject *parent)
  : QObject(parent)
{
  // pass...
}

LogHandler::~LogHandler() {
  // pass...
}


/* ********************************************************************************************* *
 * Implementation of Logger
 * ********************************************************************************************* */
Logger *Logger::_instance = nullptr;

Logger::Logger()
  : QObject(nullptr), _handler(), _lock()
{
  // pass...
}

Logger::~Logger() {
  _handler.clear();
}

void
Logger::log(const LogMessage &msg) {
  _lock.lock();
  foreach (LogHandler *handler, _handler) {
    handler->handle(msg);
  }
  _lock.unlock();
}

void
Logger::addHandler(LogHandler *handler) {
  if (nullptr == handler)
    return;
  if (_handler.contains(handler))
    return;
  handler->setParent(this);
  _handler.append(handler);
  connect(handler, SIGNAL(destroyed(QObject*)), this, SLOT(onHandlerDeleted(QObject*)));
}

void
Logger::remHandler(LogHandler *handler) {
  if (_handler.contains(handler)) {
    handler->setParent(nullptr);
    disconnect(handler, SIGNAL(destroyed(QObject*)), this, SLOT(onHandlerDeleted(QObject*)));
  }
  _handler.removeAll(handler);
}

void
Logger::onHandlerDeleted(QObject *obj) {
  _handler.removeAll(dynamic_cast<LogHandler*>(obj));
}

Logger &
Logger::get() {
  if (nullptr == _instance) {
    _instance = new Logger();
  }
  return *_instance;
}


/* ********************************************************************************************* *
 * Implementation of StreamLogHandler
 * ********************************************************************************************* */
StreamLogHandler::StreamLogHandler(QTextStream &stream, LogMessage::Level minLevel, bool color, QObject *parent)
  : LogHandler(parent), _stream(stream), _minLevel(minLevel), _color(color)
{
  // pass...
}

LogMessage::Level
StreamLogHandler::minLevel() const {
  return _minLevel;
}

void
StreamLogHandler::setMinLevel(LogMessage::Level minLevel) {
  _minLevel = minLevel;
}

void
StreamLogHandler::handle(const LogMessage &message) {
  if (message.level() < _minLevel)
    return;
  switch (message.level()) {
  case LogMessage::DEBUG:
    if (_color)
      _stream << "\033[37m";
    _stream << "Debug ";
    break;
  case LogMessage::INFO:
    if (_color)
      _stream << "\033[39m";
    _stream << "Info ";
    break;
  case LogMessage::WARNING:
    if (_color)
      _stream << "\033[33m";
    _stream << "Warning ";
    break;
  case LogMessage::ERROR:
    if (_color)
      _stream << "\033[31m";
    _stream << "ERROR ";
    break;
  case LogMessage::FATAL:
    if (_color)
      _stream << "\033[30m\033[101m";
    _stream << "FATAL ";
    break;
  }
  QFileInfo finfo(message.file());
  _stream << "in " << finfo.dir().dirName() << "/" << finfo.fileName()
          << "@" << message.line() << ": " << message.message() << "\n";
  if (_color)
    _stream << "\033[39m";
  _stream.flush();
}


/* ********************************************************************************************* *
 * Implementation of FileLogHandler
 * ********************************************************************************************* */
FileLogHandler::FileLogHandler(const QString &filename, LogMessage::Level minLevel, QObject *parent)
  : LogHandler(parent), _file(filename), _stream(), _minLevel(minLevel)
{
  QFileInfo info(filename);
  // Check if logfile exists
  if (! info.exists()) {
    // check and create path to logfile (if needed)
    if (! info.absoluteDir().mkpath(info.absoluteDir().absolutePath())) {
      logError() << "Cannot create log-file directory '" << info.absoluteDir().absolutePath() << "'.";
      return;
    }
    // Create logfile
    _file.open(QFile::WriteOnly);
  } else {
    // Open logfile
    _file.open(QFile::Append);
  }

  if (_file.isOpen()) {
    // Set stream desitnation to logfile
    _stream.setDevice(&_file);
  }
}

FileLogHandler::~FileLogHandler() {
  if (_file.isOpen()) {
    _file.flush();
    _file.close();
  }
}

LogMessage::Level
FileLogHandler::minLevel() const {
  return _minLevel;
}

void
FileLogHandler::setMinLevel(LogMessage::Level minLevel) {
  _minLevel = minLevel;
}

void
FileLogHandler::handle(const LogMessage &message) {
  if (!_file.isOpen())
    return;

  if (message.level() < _minLevel)
    return;

  _stream << QDateTime::currentDateTime().toString(Qt::ISODateWithMs) << ": ";
  switch (message.level()) {
  case LogMessage::DEBUG:   _stream << "Debug "; break;
  case LogMessage::INFO:    _stream << "Info "; break;
  case LogMessage::WARNING: _stream << "Warning "; break;
  case LogMessage::ERROR:   _stream << "ERROR "; break;
  case LogMessage::FATAL:   _stream << "FATAL "; break;
  }
  QFileInfo finfo(message.file());
  _stream << "in " << finfo.dir().dirName() << "/" << finfo.fileName()
          << "@" << message.line() << ": " << message.message() << "\n";
  _stream.flush();
}
