/*
 * LibrePCB - Professional EDA for everyone!
 * Copyright (C) 2013 LibrePCB Developers, see AUTHORS.md for contributors.
 * https://librepcb.org/
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/*******************************************************************************
 *  Includes
 ******************************************************************************/
#include "debug.h"

#include "fileio/filepath.h"

#include <QtCore>

/*******************************************************************************
 *  Namespace
 ******************************************************************************/
namespace librepcb {

/*******************************************************************************
 *  Constructors / Destructor
 ******************************************************************************/

Debug::Debug()
  : mDebugLevelStderr(DebugLevel_t::All),
    mDebugLevelLogFile(DebugLevel_t::Nothing),
    mStderrStream(new QTextStream(stderr)),
    mLogFilepath(),
    mLogFile(0) {
  // determine the filename of the log file which will be used if logging is
  // enabled
  QString datetime =
      QDateTime::currentDateTime().toString("yyyy-MM-dd_hh-mm-ss");
  QString dataDir =
      QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation);
  if (!dataDir.isEmpty())
    mLogFilepath.setPath(dataDir % "/logs/" % datetime % ".log");

  // install the message handler for Qt's debug functions (qDebug(), ...)
  qInstallMessageHandler(messageHandler);
}

Debug::~Debug() {
  delete mStderrStream;
  mStderrStream = 0;

  if (mLogFile) {
    mLogFile->close();
    delete mLogFile;
    mLogFile = 0;
  }
}

/*******************************************************************************
 *  General Methods
 ******************************************************************************/

void Debug::setDebugLevelStderr(DebugLevel_t level) {
  mDebugLevelStderr = level;
}

void Debug::setDebugLevelLogFile(DebugLevel_t level) {
  if (level == mDebugLevelLogFile) return;

  if ((mDebugLevelLogFile == DebugLevel_t::Nothing) &&
      (level != DebugLevel_t::Nothing)) {
    // enable logging to file
    QDir().mkpath(mLogFilepath.getParentDir().toStr());
    mLogFile = new QFile(mLogFilepath.toStr());
    bool success = mLogFile->open(QFile::WriteOnly);
    if (success) {
      mDebugLevelLogFile = level;  // activate logging to file immediately!
      qDebug() << "Enabled logging to file:" << mLogFilepath.toNative();
    } else {
      qWarning().nospace() << "Failed to enable logging to file "
                           << mLogFilepath.toNative() << ": "
                           << mLogFile->errorString();
      delete mLogFile;
      mLogFile = 0;
    }
  } else if ((mDebugLevelLogFile != DebugLevel_t::Nothing) &&
             (level == DebugLevel_t::Nothing) && (mLogFile)) {
    // disable logging to file
    mLogFile->close();
    delete mLogFile;
    mLogFile = 0;
  }

  mDebugLevelLogFile = level;
}

Debug::DebugLevel_t Debug::getDebugLevelStderr() const {
  return mDebugLevelStderr;
}

Debug::DebugLevel_t Debug::getDebugLevelLogFile() const {
  return mDebugLevelLogFile;
}

const FilePath& Debug::getLogFilepath() const {
  return mLogFilepath;
}

void Debug::print(DebugLevel_t level, const QString& msg, const char* file,
                  int line) {
  QMutexLocker locker(&mMutex);

  // If there is nothing to print, return immediately from this function.
  if ((mDebugLevelStderr < level) &&
      ((mDebugLevelLogFile < level) || (!mLogFile))) {
    return;
  }

  // The debug level string has always 9 characters.
  const char* levelStr = "---------";
  switch (level) {
    case DebugLevel_t::DebugMsg:
      levelStr = "DEBUG-MSG";
      break;
    case DebugLevel_t::Info:
      levelStr = "  INFO   ";
      break;
    case DebugLevel_t::Warning:
      levelStr = " WARNING ";
      break;
    case DebugLevel_t::Exception:
      levelStr = "EXCEPTION";
      break;
    case DebugLevel_t::Critical:
      levelStr = "CRITICAL ";
      break;
    case DebugLevel_t::Fatal:
      levelStr = "  FATAL  ";
      break;
    default:
      break;
  }

  QString logMsg = QString("[%1] %2").arg(levelStr).arg(msg);
  if (file || line) {
    // Avoid printing useless "(:0)" in release builds.
    logMsg += QString(" (%1:%2)").arg(file).arg(line);
  }

  if (mDebugLevelStderr >= level) {
    // write to stderr
    *mStderrStream << logMsg << '\n';
    mStderrStream->flush();
  }

  if ((mDebugLevelLogFile >= level) && (mLogFile)) {
    // write to the log file
    QTextStream logFileStream(mLogFile);
    logFileStream << logMsg << '\n';
  }
}

/*******************************************************************************
 *  The message handler for qDebug(), qWarning(), qCritical() and qFatal()
 ******************************************************************************/

void Debug::messageHandler(QtMsgType type, const QMessageLogContext& context,
                           const QString& msg) {
  switch (type) {
    case QtDebugMsg:
      instance()->print(DebugLevel_t::DebugMsg, msg, context.file,
                        context.line);
      break;
    case QtInfoMsg:
      instance()->print(DebugLevel_t::Info, msg, context.file, context.line);
      break;
    case QtWarningMsg:
      instance()->print(DebugLevel_t::Warning, msg, context.file, context.line);
      break;

    case QtCriticalMsg:
      instance()->print(DebugLevel_t::Critical, msg, context.file,
                        context.line);
      break;

    case QtFatalMsg:
      instance()->print(DebugLevel_t::Fatal, msg, context.file, context.line);
      abort();  // fatal error --> quit the whole application!

    default:
      Q_ASSERT(false);
      instance()->print(DebugLevel_t::Critical,
                        QString("unhandled case: %1").arg(type), __FILE__,
                        __LINE__);
      break;
  }
}

/*******************************************************************************
 *  End of File
 ******************************************************************************/

}  // namespace librepcb
