// Copyright (C) 2001 - 2021 Rob Caelers <robc@krandor.nl>
// All rights reserved.
//
// 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/>.
//

#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif

#include "Toolkit.hh"

#include <QApplication>
#include <QDir>
#include <QFile>
#include <QGuiApplication>
#include <QQmlEngine>
#include <QQuickView>
#include <QQuickWidget>
#include <QStyleHints>
#include <QTimer>
#include <QTranslator>

#include "QmlMicroBreakWindow.hh"
#include "QmlPreludeWindow.hh"
#include "QmlDailyLimitWindow.hh"
#include "QmlRestBreakWindow.hh"
#include "debug.hh"
#include "ui/GUIConfig.hh"
#include "utils/Signals.hh"

using namespace workrave;
using namespace workrave::config;

Toolkit::Toolkit(int &argc, char **argv)
  : QApplication(argc, argv)
  , heartbeat_timer(new QTimer(this))
{
  TRACE_ENTRY();
}

void
Toolkit::preinit(std::shared_ptr<workrave::config::IConfigurator> config)
{
  TRACE_ENTRY();
}

void
Toolkit::init(std::shared_ptr<IApplicationContext> app)
{
  this->app = app;

  setQuitOnLastWindowClosed(false);
  installEventFilter(this);

  menu_model = app->get_menu_model();
  sound_theme = app->get_sound_theme();

  GUIConfig::light_dark_mode().attach(tracker, [this](auto mode) { apply_light_dark_mode(mode); });
  GUIConfig::locale().attach(tracker, [this](const std::string &locale) { apply_qt_locale(locale); });
  apply_qt_locale(GUIConfig::locale()());

  main_window = new MainWindow(app);

  workrave::utils::connect(main_window->signal_closed(), tracker, [this]() { on_main_window_closed(); });

  status_icon = std::make_unique<StatusIcon>(app);
  workrave::utils::connect(status_icon->signal_activate(), tracker, [this]() { on_status_icon_activated(); });
  workrave::utils::connect(status_icon->signal_balloon_activate(), tracker, [this](auto id) {
    on_status_icon_balloon_activated(id);
  });

  connect(heartbeat_timer, SIGNAL(timeout()), this, SLOT(on_timer()));
  heartbeat_timer->start(1000);
}

void
Toolkit::deinit()
{
  get_locker()->unlock();
}

void
Toolkit::terminate()
{
  exit();
}

void
Toolkit::hold()
{
  TRACE_ENTRY_PAR(hold_count);
  hold_count++;
  main_window->set_can_close(can_close());
  setQuitOnLastWindowClosed(hold_count == 0);
}

void
Toolkit::release()
{
  TRACE_ENTRY_PAR(hold_count);
  hold_count--;
  main_window->set_can_close(can_close());
  setQuitOnLastWindowClosed(hold_count == 0);
}

auto
Toolkit::can_close() const -> bool
{
  return hold_count > 0;
}

void
Toolkit::run()
{
  exec();
}

auto
Toolkit::get_head_count() const -> int
{
  QList<QScreen *> screens = QGuiApplication::screens();
  return static_cast<int>(screens.size());
}

auto
Toolkit::create_break_window(int screen_index, BreakId break_id, BreakFlags break_flags) -> IBreakWindow::Ptr
{
  IBreakWindow::Ptr ret;

  QList<QScreen *> screens = QGuiApplication::screens();
  QScreen *screen = screens.at(screen_index);

  if (break_id == BREAK_ID_MICRO_BREAK)
    {
      ret = std::make_shared<QmlMicroBreakWindow>(app, screen, break_flags);
    }
  else if (break_id == BREAK_ID_REST_BREAK)
    {
      ret = std::make_shared<QmlRestBreakWindow>(app, screen, break_flags);
    }
  else if (break_id == BREAK_ID_DAILY_LIMIT)
    {
      ret = std::make_shared<QmlDailyLimitWindow>(app, screen, break_flags);
    }

  return ret;
}

auto
Toolkit::create_prelude_window(int screen_index, workrave::BreakId break_id) -> IPreludeWindow::Ptr
{
  QList<QScreen *> screens = QGuiApplication::screens();
  QScreen *screen = screens.at(screen_index);

  // Always use the QML prelude window; it switches between Sanctuary and Classic
  // designs live via bridge.classic without requiring a Workrave restart.
  return std::make_shared<QmlPreludeWindow>(app, screen, break_id);
}

void
Toolkit::show_window(WindowType type)
{
  switch (type)
    {
    case WindowType::Main:
      show_main_window();
      break;

    case WindowType::Statistics:
      show_statistics();
      break;

    case WindowType::Preferences:
      show_preferences();
      break;

    case WindowType::About:
      show_about();
      break;

    case WindowType::Exercises:
      show_exercises();
      break;

    case WindowType::Debug:
      show_debug();
      break;
    }
}

void
Toolkit::show_about()
{
  if (!about_dialog)
    {
      about_dialog = std::make_unique<QmlAboutDialog>();
    }
  about_dialog->show();
}

void
Toolkit::show_debug()
{
  if (debug_dialog == nullptr)
    {
      debug_dialog = new DebugDialog(app);
      debug_dialog->setAttribute(Qt::WA_DeleteOnClose);
    }
  debug_dialog->show();
}

void
Toolkit::show_exercises()
{
  if (!exercises_dialog)
    {
      exercises_dialog = std::make_unique<QmlExercisesDialog>(app);
    }
  exercises_dialog->show();
}

void
Toolkit::show_main_window()
{
  if (main_window->isMinimized())
    {
      main_window->showNormal();
    }
  else
    {
      main_window->show();
    }
  main_window->raise();
  main_window->activateWindow();
  // TODO: move above code to open_window, WAS: main_window->open_window();
}

void
Toolkit::show_preferences()
{
  if (preferences_dialog == nullptr)
    {
      preferences_dialog = std::make_unique<QmlPrefsDialog>(app);
    }
  preferences_dialog->show();
}

void
Toolkit::show_statistics()
{
  if (!statistics_dialog)
    {
      statistics_dialog = std::make_unique<QmlStatisticsDialog>(app);
    }
  statistics_dialog->show();
  statistics_dialog->raise();
}

auto
Toolkit::signal_timer() -> boost::signals2::signal<void()> &
{
  return timer_signal;
}

auto
Toolkit::signal_main_window_closed() -> boost::signals2::signal<void()> &
{
  return main_window_closed_signal;
}

auto
Toolkit::signal_session_idle_changed() -> boost::signals2::signal<void(bool)> &
{
  return session_idle_changed_signal;
}

auto
Toolkit::signal_session_unlocked() -> boost::signals2::signal<void()> &
{
  return session_unlocked_signal;
}

auto
Toolkit::signal_status_icon_activated() -> boost::signals2::signal<void()> &
{
  return status_icon_activated_signal;
}

auto
Toolkit::get_display_name() const -> const char *
{
  display_name = QGuiApplication::platformName().toStdString();
  return display_name.c_str();
}

void
Toolkit::create_oneshot_timer(int ms, std::function<void()> func)
{
  QTimer::singleShot(ms, this, [func = std::move(func)]() { func(); });
}

void
Toolkit::show_notification(const std::string &id,
                           const std::string &title,
                           const std::string &balloon,
                           std::function<void()> func)
{
  notify_add_confirm_function(id, func);
  status_icon->show_balloon(QString::fromStdString(id), QString::fromStdString(title), QString::fromStdString(balloon));
}

void
Toolkit::show_tooltip(const std::string &tip)
{
  status_icon->set_tooltip(QString::fromStdString(tip));
}

void
Toolkit::on_timer()
{
  timer_signal();
  main_window->heartbeat();
}

void
Toolkit::on_main_window_closed()
{
  if (can_close())
    {
      GUIConfig::timerbox_enabled("main_window").set(false);
      main_window_closed_signal();
    }
  else
    {
      terminate();
    }
}

void
Toolkit::on_status_icon_balloon_activated(const std::string &id)
{
  TRACE_ENTRY();
  notify_confirm(id);
}

void
Toolkit::on_status_icon_activated()
{
  TRACE_ENTRY();
  status_icon_activated_signal();
}

void
Toolkit::notify_add_confirm_function(const std::string &id, std::function<void()> func)
{
  TRACE_ENTRY();
  notifiers[id] = func;
}

void
Toolkit::notify_confirm(const std::string &id)
{
  TRACE_ENTRY();
  if (notifiers.contains(id))
    {
      notifiers[id]();
    }
}

void
Toolkit::apply_qt_locale(const std::string &locale_code)
{
  if (current_translator != nullptr)
    {
      removeTranslator(current_translator);
      delete current_translator;
      current_translator = nullptr;
    }

  if (locale_code.empty())
    {
      return;
    }

  QString filename = "workrave_" + QString::fromStdString(locale_code) + ".qm";

  // Find the .qm file: check installed bundle location, then walk up from
  // the executable for dev-build layouts (po/ sibling of the build tree).
  auto appDir = QCoreApplication::applicationDirPath();
  QStringList candidates{
    appDir + "/../Resources/share/translations/" + filename, // macOS bundle
    appDir + "/../share/translations/" + filename,           // Linux/Windows installed
  };
  // Walk up from executable looking for a po/ directory (dev build)
  {
    QDir d(appDir);
    for (int i = 0; i < 10; ++i)
      {
        candidates << d.filePath("po/" + filename);
        if (!d.cdUp())
          {
            break;
          }
      }
  }

  for (const QString &candidate: std::as_const(candidates))
    {
      if (QFile::exists(candidate))
        {
          auto *translator = new GettextTranslator(this);
          if (translator->load(candidate))
            {
              current_translator = translator;
              installTranslator(current_translator);
              spdlog::info("Qt translation loaded: {}", candidate.toStdString());
              retranslate_all_qml_views();
              return;
            }
          delete translator;
        }
    }
  spdlog::warn("Qt translation not found for locale: {}", locale_code);
  retranslate_all_qml_views();
}

void
Toolkit::apply_light_dark_mode(LightDarkTheme mode)
{
  Qt::ColorScheme scheme = Qt::ColorScheme::Unknown;
  switch (mode)
    {
    case LightDarkTheme::Light:
      scheme = Qt::ColorScheme::Light;
      break;
    case LightDarkTheme::Dark:
      scheme = Qt::ColorScheme::Dark;
      break;
    case LightDarkTheme::Auto:
      scheme = Qt::ColorScheme::Unknown;
      break;
    }
  QGuiApplication::styleHints()->setColorScheme(scheme);
}

void
Toolkit::retranslate_all_qml_views()
{
  for (QWindow *w: QGuiApplication::topLevelWindows())
    {
      if (auto *qv = qobject_cast<QQuickView *>(w))
        {
          qv->engine()->retranslate();
        }
    }
  for (QWidget *w: QApplication::allWidgets())
    {
      if (auto *qw = qobject_cast<QQuickWidget *>(w))
        {
          qw->engine()->retranslate();
        }
    }
}

bool
Toolkit::eventFilter(QObject *obj, QEvent *event)
{
  if (event->type() == QEvent::ApplicationPaletteChange || event->type() == QEvent::ThemeChange)
    {
      const auto color_scheme = QGuiApplication::styleHints()->colorScheme();
      if (last_color_scheme != color_scheme)
        {
          last_color_scheme = color_scheme;
          const char *name = "Unknown";
          if (color_scheme == Qt::ColorScheme::Light)
            {
              name = "Light";
            }
          else if (color_scheme == Qt::ColorScheme::Dark)
            {
              name = "Dark";
            }
          spdlog::info("Theme changed to {}", name);
        }
    }
  return false;
}
