/*
 * Copyright (C) by Cédric Bellegarde <gnumdk@gmail.com>
 *
 * 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 2 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.
 */

#include "systray.h"

#include "common/restartmanager.h"
#include "gui/accountmanager.h"
#include "gui/application.h"
#include "gui/folderman.h"
#include "gui/networkinformation.h"
#include "libsync/theme.h"

#include <QApplication>
#include <QDesktopServices>
#include <QMenu>

using namespace Qt::Literals::StringLiterals;

namespace OCC {


Systray::Systray(QObject *parent)
    : QSystemTrayIcon(parent)
{
    connect(this, &QSystemTrayIcon::activated, this, [](QSystemTrayIcon::ActivationReason reason) {
        // Left click
        if (reason == QSystemTrayIcon::Trigger) {
            ocApp()->showSettings();
        }
    });
    connect(AccountManager::instance(), &AccountManager::accountAdded, this,
        [this](AccountStatePtr accountState) { connect(accountState.data(), &AccountState::stateChanged, this, &Systray::slotComputeOverallSyncStatus); });
    connect(FolderMan::instance(), &FolderMan::folderSyncStateChange, this, &Systray::slotComputeOverallSyncStatus);

    // init systray
    slotComputeOverallSyncStatus();
    computeContextMenu();
    show();
}

void Systray::setToolTip(const QString &tip)
{
#ifdef Q_OS_WIN
    QSystemTrayIcon::setToolTip(tr("%1: %2").arg(Theme::instance()->appNameGUI(), tip));
#else
    QSystemTrayIcon::setToolTip(u"%1\n%2"_s.arg(Theme::instance()->appNameGUI(), tip));
#endif
}

void Systray::slotComputeOverallSyncStatus()
{
    auto getIcon = [](const SyncResult &result) { return Theme::instance()->themeTrayIcon(result); };
    auto getIconFromStatus = [getIcon](const SyncResult::Status &status) { return getIcon(SyncResult{status}); };
    bool allSignedOut = true;
    bool allPaused = true;
    QVector<AccountStatePtr> problemAccounts;

    for (const auto &a : AccountManager::instance()->accounts()) {
        if (!a->isSignedOut()) {
            allSignedOut = false;
        }
        if (!a->isConnected()) {
            problemAccounts.append(a);
        }
    }

    const auto &map = FolderMan::instance()->folders();
    for (auto *f : map) {
        if (!f->isSyncPaused()) {
            allPaused = false;
        }
    }

    if (!problemAccounts.empty()) {
        setIcon(getIconFromStatus(SyncResult::Status::Offline));
#ifdef Q_OS_WIN
        // Windows has a 128-char tray tooltip length limit.
        QStringList accountNames;
        for (const auto &a : std::as_const(problemAccounts)) {
            accountNames.append(a->account()->displayNameWithHost());
        }
        setToolTip(tr("Disconnected from %1").arg(accountNames.join(QLatin1String(", "))));
#else
        QStringList messages;
        messages.append(tr("Disconnected from accounts:"));
        for (const auto &a : std::as_const(problemAccounts)) {
            QString message = tr("Account %1").arg(a->account()->displayNameWithHost());
            if (!a->connectionErrors().empty()) {
                message += QLatin1String("\n") + a->connectionErrors().join(QLatin1String("\n"));
            }
            messages.append(message);
        }
        setToolTip(messages.join(QLatin1String("\n\n")));
#endif
        return;
    }

    if (allSignedOut) {
        setIcon(getIconFromStatus(SyncResult::Status::Offline));
        setToolTip(tr("Please sign in"));
        return;
    } else if (allPaused) {
        setIcon(getIconFromStatus(SyncResult::Paused));
        setToolTip(tr("Account synchronization is disabled"));
        return;
    }

    // display the info of the least successful sync (eg. do not just display the result of the latest sync)
    QString trayMessage;

    auto trayOverallStatusResult = FolderMan::trayOverallStatus(map);
    const QIcon statusIcon = getIcon(trayOverallStatusResult.overallStatus());
    setIcon(statusIcon);

    // create the tray blob message, check if we have an defined state
#ifdef Q_OS_WIN
    // Windows has a 128-char tray tooltip length limit.
    trayMessage = FolderMan::instance()->trayTooltipStatusString(trayOverallStatusResult.overallStatus(), false);
#else
    QStringList allStatusStrings;
    for (auto *folder : map) {
        QString folderMessage = FolderMan::trayTooltipStatusString(folder->syncResult(), folder->isSyncPaused());
        allStatusStrings += tr("Space »%1«: %2").arg(folder->shortGuiLocalPath(), folderMessage);
    }
    trayMessage = allStatusStrings.join(QLatin1String("\n"));
#endif
    setToolTip(trayMessage);
}

void Systray::computeContextMenu()
{
    Q_ASSERT(!contextMenu());
    auto *menu = new QMenu(Theme::instance()->appNameGUI());

    menu->addAction(Theme::instance()->applicationIcon(), tr("Show %1").arg(Theme::instance()->appNameGUI()), ocApp(), &Application::showSettings);
    auto *pauseResume = new QAction(menu);
    auto updatePauseResumeAction = [pauseResume] {
        pauseResume->setText(FolderMan::instance()->scheduler()->isRunning() ? tr("Pause synchronizations") : tr("Resume synchronizations"));
    };
    connect(pauseResume, &QAction::triggered, FolderMan::instance()->scheduler(), [] {
        if (FolderMan::instance()->scheduler()->isRunning()) {
            FolderMan::instance()->scheduler()->stop();
            FolderMan::instance()->scheduler()->terminateCurrentSync(tr("Synchronization paused"));
        } else {
            FolderMan::instance()->scheduler()->start();
        }
    });
    connect(FolderMan::instance()->scheduler(), &SyncScheduler::isRunningChanged, pauseResume, updatePauseResumeAction);
    menu->addAction(pauseResume);
    updatePauseResumeAction();

    if (ocApp()->debugMode()) {
        menu->addSeparator();
        auto *debugMenu = menu->addMenu(QStringLiteral("Debug actions"));
        debugMenu->addAction(QStringLiteral("Crash if asserts enabled - OC_ENSURE"), ocApp(), [] {
            if (OC_ENSURE(false)) {
                Q_UNREACHABLE();
            }
        });
        debugMenu->addAction(QStringLiteral("Crash if asserts enabled - Q_ASSERT"), ocApp(), [] { Q_ASSERT(false); });
        debugMenu->addAction(QStringLiteral("Crash now - Utility::crash()"), ocApp(), [] { Utility::crash(); });
        debugMenu->addAction(QStringLiteral("Crash now - OC_ENFORCE()"), ocApp(), [] { OC_ENFORCE(false); });
        debugMenu->addAction(QStringLiteral("Crash now - qFatal"), ocApp(), [] { qFatal("la Qt fatale"); });
        debugMenu->addAction(QStringLiteral("Restart now"), ocApp(), [] { RestartManager::requestRestart(); });
        debugMenu->addSeparator();
        auto captivePortalCheckbox = debugMenu->addAction(QStringLiteral("Behind Captive Portal"));
        captivePortalCheckbox->setCheckable(true);
        captivePortalCheckbox->setChecked(NetworkInformation::instance()->isForcedCaptivePortal());
        connect(captivePortalCheckbox, &QAction::triggered, this, [](bool checked) { NetworkInformation::instance()->setForcedCaptivePortal(checked); });
        connect(NetworkInformation::instance(), &NetworkInformation::isBehindCaptivePortalChanged, captivePortalCheckbox,
            [captivePortalCheckbox] { captivePortalCheckbox->setChecked(NetworkInformation::instance()->isForcedCaptivePortal()); });
    }

    menu->addSeparator();

    if (!Theme::instance()->helpUrl().isEmpty()) {
        menu->addAction(tr("Help"), this, [] { QDesktopServices::openUrl(Theme::instance()->helpUrl()); });
    }

    menu->addAction(tr("About"), ocApp(), &Application::showAbout);

    // this action will be hidden on mac and be part of the application menu
    menu->addAction(tr("About Qt"), qApp, &QApplication::aboutQt)->setMenuRole(QAction::AboutQtRole);

    menu->addAction(tr("Quit"), ocApp(), &QApplication::quit);

    setContextMenu(menu);
}

} // namespace OCC
