// For license of this file, see <project-root-folder>/LICENSE.md.

#ifndef APPLICATION_H
#define APPLICATION_H

#include "core/feeddownloader.h"
#include "database/databasefactory.h"
#include "definitions/definitions.h"
#include "gui/tray/trayicon.h"
#include "miscellaneous/feedreader.h"
#include "miscellaneous/iofactory.h"
#include "miscellaneous/localization.h"
#include "miscellaneous/notification.h"
#include "miscellaneous/singleapplication.h"
#include "miscellaneous/skinfactory.h"
#include "miscellaneous/systemfactory.h"

#include <functional>

#include <QCommandLineParser>
#include <QList>
#include <QSystemTrayIcon>

#if defined(qApp)
#undef qApp
#endif

// Define new qApp macro. Yeaaaaah.
#define qApp (Application::instance())

class FormMain;
class FormLog;
class IconFactory;
class Mutex;
class WebFactory;
class NotificationFactory;
class ToastNotificationsManager;
class WebViewer;
class Settings;

#if defined(Q_OS_WIN)
struct ITaskbarList4;
#endif

struct GuiMessage {
  public:
    GuiMessage() {}
    GuiMessage(QString title,
               QString message,
               QSystemTrayIcon::MessageIcon type = QSystemTrayIcon::MessageIcon::Information)
      : m_title(std::move(title)), m_message(std::move(message)), m_type(type) {}

    QString m_title;
    QString m_message;
    QSystemTrayIcon::MessageIcon m_type;
    FeedDownloadResults m_feedFetchResults;
};

Q_DECLARE_METATYPE(GuiMessage)

struct GuiMessageDestination {
  public:
    GuiMessageDestination(bool tray = true, bool message_box = false, bool status_bar = false)
      : m_tray(tray), m_messageBox(message_box), m_statusBar(status_bar) {}

    bool m_tray;
    bool m_messageBox;
    bool m_statusBar;
};

Q_DECLARE_METATYPE(GuiMessageDestination)

struct GuiAction {
  public:
    GuiAction(QString title = {}, const std::function<void()>& action = nullptr)
      : m_title(std::move(title)), m_action(action) {}

    QString m_title;
    std::function<void()> m_action;
};

Q_DECLARE_METATYPE(GuiAction)

class RSSGUARD_DLLSPEC Application : public SingleApplication {
    Q_OBJECT

  public:
    explicit Application(const QString& id, int& argc, char** argv, const QStringList& raw_cli_args);
    virtual ~Application();

    virtual bool event(QEvent* event);

    void updateCliDebugStatus();
    void reactOnForeignNotifications();
    void hideOrShowMainForm();
    void loadDynamicShortcuts();
    void offerPolls() const;
    void offerChanges() const;

    bool isAlreadyRunning();
    QStringList builtinSounds() const;

    FeedReader* feedReader();
    void setFeedReader(FeedReader* feed_reader);

    // Globally accessible actions.
    QList<QAction*> userActions();

    // Globally accessible actions for the sole purpose of keyboard shortcuts.
    QList<QAction*> userAndExtraActions();

    // Check whether this application starts for the first time (ever).
    bool isFirstRun() const;

    // Check whether CURRENT VERSION of the application starts for the first time.
    bool isFirstRunCurrentVersion() const;

    QCommandLineParser* cmdParser();
    WebFactory* web() const;
    SystemFactory* system();
    SkinFactory* skins();
    Localization* localization();
    DatabaseFactory* database();
    IconFactory* icons();
    Settings* settings() const;
    Mutex* feedUpdateLock();
    FormMain* mainForm();
    QWidget* mainFormWidget();
    TrayIcon* trayIcon();
    NotificationFactory* notifications() const;

#if QT_VERSION_MAJOR > 5
    QThreadPool* workHorsePool() const;
#endif

    ToastNotificationsManager* toastNotifications() const;

    QIcon desktopAwareIcon() const;

    QString tempFolder() const;
    QString documentsFolder() const;
    QString homeFolder() const;
    QString configFolder() const;

    // These return user ready folders.
    QString userDataAppFolder() const;
    QString userDataHomeFolder() const;
    QString customDataFolder() const;

    // Returns the base folder to which store user data, the "data" folder.
    // NOTE: Use this to get correct path under which store user data.
    QString userDataFolder();

    QString replaceUserDataFolderPlaceholder(QString text, bool double_escape = false) const;
    QStringList replaceUserDataFolderPlaceholder(QStringList texts) const;

    void setMainForm(FormMain* main_form);

    void backupDatabaseSettings(bool backup_database,
                                bool backup_settings,
                                const QString& target_path,
                                const QString& backup_name);
    void restoreDatabaseSettings(bool restore_database,
                                 bool restore_settings,
                                 const QString& source_database_file_path = QString(),
                                 const QString& source_settings_file_path = QString());

    void showTrayIcon();
    void deleteTrayIcon();

    QStringList rawCliArgs() const;

    // Displays given simple message in tray icon bubble or OSD
    // or in message box if tray icon is disabled.
    void showGuiMessage(Notification::Event event,
                        const GuiMessage& msg,
                        GuiMessageDestination dest = {},
                        const GuiAction& action = {},
                        QWidget* parent = nullptr);

    WebViewer* createWebView();
    bool isWayland() const;

    // Returns pointer to "GOD" application singleton.
    static Application* instance();

    // Custom debug/console log handler.
    static void performLogging(QtMsgType type, const QMessageLogContext& context, const QString& msg);

  public slots:
    void setupFont();
    void reloadCurrentSkin(bool replace_existing_qss);

    // Processes incoming message from another RSS Guard instance.
    void parseCmdArgumentsFromOtherInstance(const QString& message);
    void parseCmdArgumentsFromMyInstance(const QStringList& raw_cli_args, QString& custom_ua);

    void displayLog();

    void showGuiMessageCore(Notification::Event event,
                            const GuiMessage& msg,
                            GuiMessageDestination dest = {},
                            const GuiAction& action = {},
                            QWidget* parent = nullptr);

  private slots:
    void loadMessageToFeedAndArticleList(Feed* feed, const Message& message);
    void fillCmdArgumentsParser(QCommandLineParser& parser);
    void onCommitData(QSessionManager& manager);
    void onSaveState(QSessionManager& manager);
    void onAboutToQuit();
    void showMessagesNumber(int unread_messages, bool any_feed_has_new_unread_messages);
    void onFeedUpdatesStarted();
    void onFeedUpdatesProgress(const Feed* feed, int current, int total);
    void onFeedUpdatesFinished(const FeedDownloadResults& results);

  signals:
    void sendLogToDialog(QString message);

  private:
#if defined(Q_OS_WIN)
    QImage generateOverlayIcon(int number) const;
#endif

    void setupCustomDataFolder(const QString& data_folder);
    void setupWorkHorsePool();
    void determineFirstRuns();
    void eliminateFirstRuns();
    void initializeFileBasedLogging();
    void displayLogMessageInDialog(const QString& message);

  private:
    QStringList m_rawCliArgs;
    QCommandLineParser m_cmdParser;
    FeedReader* m_feedReader;

    bool m_quitLogicDone;

    // This read-write lock is used by application on its close.
    // Application locks this lock for WRITING.
    // This means that if application locks that lock, then
    // no other transaction-critical action can acquire lock
    // for reading and won't be executed, so no critical action
    // will be running when application quits
    //
    // EACH critical action locks this lock for READING.
    // Several actions can lock this lock for reading.
    // But of user decides to close the application (in other words,
    // tries to lock the lock for writing), then no other
    // action will be allowed to lock for reading.
    QScopedPointer<Mutex> m_updateFeedsLock;

    QList<QAction*> m_userActions;
    FormMain* m_mainForm;
    FormLog* m_logForm;
    TrayIcon* m_trayIcon;
    Settings* m_settings;
    WebFactory* m_webFactory;
    SystemFactory* m_system;
    SkinFactory* m_skins;
    Localization* m_localization;
    IconFactory* m_icons;
    DatabaseFactory* m_database;
    NotificationFactory* m_notifications;
    ToastNotificationsManager* m_toastNotifications;

#if QT_VERSION_MAJOR > 5
    QThreadPool* m_workHorsePool;
#endif

    bool m_firstRunEver;
    bool m_firstRunCurrentVersion;
    QString m_customDataFolder;
    bool m_allowMultipleInstances;

#if defined(Q_OS_WIN)
    ITaskbarList4* m_windowsTaskBar;
#endif
};

inline Application* Application::instance() {
  return static_cast<Application*>(QCoreApplication::instance());
}

#endif // APPLICATION_H
