// CoolerControl - monitor and control your cooling and other devices
// Copyright (c) 2021-2025  Guy Boldon and contributors
//
// 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 <https://www.gnu.org/licenses/>.

#include <QApplication>
#include <QCommandLineParser>
#include <QDBusInterface>
#include <QDir>
#include <QLoggingCategory>
#include <QStandardPaths>
#include <optional>

#include "constants.h"
#include "dbus_listener.h"
#include "main_window.h"

void setChromiumFlags(const bool debugOrFullDebug, const bool disableGpu,
                      const QString& extraFlags) {
  QByteArray chromiumFlags;
  if (debugOrFullDebug) {
    chromiumFlags = disableGpu ? "--enable-logging --log-level=0 --disable-gpu"
                               : "--enable-logging --log-level=0";
  } else {
    chromiumFlags = disableGpu ? "--enable-logging --log-level=3 --disable-gpu"
                               : "--enable-logging --log-level=3";
  }
  if (!extraFlags.isEmpty()) {
    chromiumFlags.append(' ');
    chromiumFlags.append(extraFlags.toUtf8());
  }
  qputenv("QTWEBENGINE_CHROMIUM_FLAGS", chromiumFlags);
}

void setEnvVars(const bool debugOrFullDebug, const bool disableGpu) {
  if (debugOrFullDebug) {
    qputenv("QTWEBENGINE_REMOTE_DEBUGGING", QByteArray::number(9000));
  }
  if (disableGpu) {
    qputenv("QT_OPENGL", "software");
  }
}

void setLogFilters(const bool debug, const bool fullDebug) {
  if (debug) {
    QLoggingCategory::setFilterRules(
        "default.debug=true\n"
        "qt.webenginecontext.debug=true");
  } else if (fullDebug) {
    QLoggingCategory::setFilterRules(
        "*.debug=true\n"
        "qt.webenginecontext.debug=true");
  } else {
    QLoggingCategory::setFilterRules(
        "*.debug=false\n"
        "js.warning=false");
  }
}

void handleCmdOptions(const bool debug, const bool fullDebug, const bool disableGpu,
                      const QString& extraChromiumFlags) {
  setChromiumFlags(debug || fullDebug, disableGpu, extraChromiumFlags);
  setEnvVars(debug || fullDebug, disableGpu);
  setLogFilters(debug, fullDebug);
}

// Returns an exit code if the app should exit immediately, or std::nullopt to continue launching.
std::optional<int> parseCLIOptions(const QApplication& a) {
  QCommandLineParser parser;
  parser.setApplicationDescription("CoolerControl GUI Desktop Application");
  parser.addHelpOption();
  parser.addVersionOption();
  const QCommandLineOption debugOption(QStringList() << "d"
                                                     << "debug",
                                       "Enable debug output.");
  parser.addOption(debugOption);
  const QCommandLineOption fullDebugOption(QStringList() << "full-debug",
                                           "Enable full debug output. This outputs a lot of data.");
  parser.addOption(fullDebugOption);
  const QCommandLineOption gpuOption(QStringList() << "disable-gpu",
                                     "Disable GPU hardware acceleration.");
  parser.addOption(gpuOption);
  const QCommandLineOption chromiumFlagsOption(
      "chromium-flags",
      "Additional Chromium flags to pass to the WebEngine (e.g. \"--ozone-platform=wayland\").",
      "flags");
  parser.addOption(chromiumFlagsOption);
  const QCommandLineOption clearCacheOption("clear-cache",
                                            "Clear the browser HTTP cache and exit.");
  parser.addOption(clearCacheOption);
  parser.process(a);
  if (parser.isSet(clearCacheOption)) {
    const QString cachePath = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) +
                              "/QtWebEngine/" + WEBENGINE_PROFILE_NAME.c_str();
    QDir cacheDir(cachePath);
    if (!cacheDir.exists()) {
      qInfo() << "Browser cache directory does not exist, nothing to clear.";
      return 0;
    }
    if (cacheDir.removeRecursively()) {
      qInfo() << "Browser cache cleared:" << cachePath;
      return 0;
    }
    qWarning() << "Failed to clear browser cache at:" << cachePath;
    return 1;
  }
  handleCmdOptions(parser.isSet(debugOption), parser.isSet(fullDebugOption),
                   parser.isSet(gpuOption), parser.value(chromiumFlagsOption));
  return std::nullopt;
}

/// Entrypoint for the application
int main(int argc, char* argv[]) {
  qputenv("QT_MESSAGE_PATTERN",
          "%{time} coolercontrol "
          "%{if-debug}\033[0;34mDEBUG%{endif}%{if-info}\033[0;32mINFO%{endif}%{if-warning}\033[0;"
          "33mWARN%{endif}%{if-critical}\033[0;31mCRIT%{endif}%{if-fatal}\033[0;31mFATAL%{endif}"
          "\033[0m [%{category}]: %{message}");
  // Standard Qt Paths:
  // https://doc.qt.io/qt-6/qstandardpaths.html
  // settings: ~/.config/{app_id}/{app_id}.conf
  const QApplication a(argc, argv);

  QApplication::setWindowIcon(QIcon::fromTheme(
      APP_ID.data(), QIcon(":/icons/org.coolercontrol.CoolerControl-symbolic.svg")));
  QCoreApplication::setOrganizationName(APP_ID.data());
  QApplication::setApplicationName("CoolerControl");
  QApplication::setDesktopFileName(APP_ID.data());
  QApplication::setApplicationVersion(COOLER_CONTROL_VERSION.data());
  QApplication::setQuitOnLastWindowClosed(false);
  if (const auto exitCode = parseCLIOptions(a); exitCode.has_value()) {
    return exitCode.value();
  }
  // single-instance
  auto connection = QDBusConnection::sessionBus();
  if (connection.isConnected()) {
    if (!connection.registerService(DBUS_SERVICE_NAME.data())) {
      qCritical()
          << "There appears to already be an instance of CoolerControl running.\nPlease check your "
             "system tray for the application icon or the task manager to find the running "
             "instance.";
      // call the running service to bring the window to the foreground
      if (QDBusInterface iface(DBUS_SERVICE_NAME.data(), DBUS_PATH.data()); iface.isValid()) {
        qInfo() << "Attempting to show existing instance.";
        if (QDBusReply<void> reply = iface.call("showInstance"); !reply.isValid()) {
          qWarning("DBus Call failed: %s\n", qPrintable(reply.error().message()));
        }
      }
      return 1;
    }
  } else {
    qWarning("Cannot connect to the D-Bus session bus.");
  }

  MainWindow w;
  w.setWindowTitle("CoolerControl");
  w.setMinimumSize(400, 400);
  w.resize(1600, 900);
  w.handleStartInTray();

  if (connection.isConnected()) {
    new DBusListener(&w);
    if (const auto ifaceRegistered = connection.registerObject(DBUS_PATH.data(), &w);
        !ifaceRegistered) {
      qWarning() << "Failed to register D-Bus object interface";
    }
  }

  const auto exitCode = QApplication::exec();

  if (connection.isConnected()) {
    connection.unregisterService(DBUS_SERVICE_NAME.data());
  }
  return exitCode;
}
