// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)

#pragma once

#include "controllersettingswindow.h"
#include "displaywidget.h"
#include "settingswindow.h"
#include "ui_mainwindow.h"

#include "core/types.h"

#include "util/window_info.h"

#include <QtCore/QThread>
#include <QtWidgets/QLabel>
#include <QtWidgets/QMainWindow>
#include <QtWidgets/QMenu>
#include <QtWidgets/QStackedWidget>
#include <memory>
#include <optional>

class QLabel;
class QThread;
class QProgressBar;

class GameListWidget;
class EmuThread;
class AutoUpdaterDialog;
class MemoryCardEditorWindow;
class CheatManagerDialog;
class DebuggerWindow;
class MainWindow;

class GPUDevice;
namespace Achievements {
enum class LoginRequestReason;
}
namespace GameList {
struct Entry;
}

class MainWindow final : public QMainWindow
{
  Q_OBJECT

public:
  /// This class is a scoped lock on the system, which prevents it from running while
  /// the object exists. Its purpose is to be used for blocking/modal popup boxes,
  /// where the VM needs to exit fullscreen temporarily.
  class SystemLock
  {
  public:
    SystemLock(SystemLock&& lock);
    SystemLock(const SystemLock&) = delete;
    ~SystemLock();

    /// Returns the parent widget, which can be used for any popup dialogs.
    ALWAYS_INLINE QWidget* getDialogParent() const { return m_dialog_parent; }

    /// Cancels any pending unpause/fullscreen transition.
    /// Call when you're going to destroy the system anyway.
    void cancelResume();

  private:
    SystemLock(QWidget* dialog_parent, bool was_paused, bool was_fullscreen);
    friend MainWindow;

    QWidget* m_dialog_parent;
    bool m_was_paused;
    bool m_was_fullscreen;
  };

public:
  explicit MainWindow();
  ~MainWindow();

  /// Sets application theme according to settings.
  static void updateApplicationTheme();

  /// Performs update check if enabled in settings.
  void startupUpdateCheck();

  /// Opens memory card editor with the specified paths.
  void openMemoryCardEditor(const QString& card_a_path, const QString& card_b_path);

  /// Locks the system by pausing it, while a popup dialog is displayed.
  SystemLock pauseAndLockSystem();

  /// Accessors for the status bar widgets, updated by the emulation thread.
  ALWAYS_INLINE QLabel* getStatusRendererWidget() const { return m_status_renderer_widget; }
  ALWAYS_INLINE QLabel* getStatusResolutionWidget() const { return m_status_resolution_widget; }
  ALWAYS_INLINE QLabel* getStatusFPSWidget() const { return m_status_fps_widget; }
  ALWAYS_INLINE QLabel* getStatusVPSWidget() const { return m_status_vps_widget; }

public Q_SLOTS:
  /// Updates debug menu visibility (hides if disabled).
  void updateDebugMenuVisibility();

  void refreshGameList(bool invalidate_cache);
  void cancelGameListRefresh();

  void runOnUIThread(const std::function<void()>& func);
  bool requestShutdown(bool allow_confirm = true, bool allow_save_to_state = true, bool save_state = true);
  void requestExit(bool allow_confirm = true);
  void checkForSettingChanges();
  std::optional<WindowInfo> getWindowInfo();

  void checkForUpdates(bool display_message);
  void recreate();

  void* getNativeWindowId();

private Q_SLOTS:
  void reportError(const QString& title, const QString& message);
  bool confirmMessage(const QString& title, const QString& message);

  std::optional<WindowInfo> acquireRenderWindow(bool recreate_window, bool fullscreen, bool render_to_main,
                                                bool surfaceless, bool use_main_window_pos);
  void displayResizeRequested(qint32 width, qint32 height);
  void releaseRenderWindow();
  void focusDisplayWidget();
  void onMouseModeRequested(bool relative_mode, bool hide_cursor);

  void onSettingsResetToDefault(bool system, bool controller);
  void onSystemStarting();
  void onSystemStarted();
  void onSystemDestroyed();
  void onSystemPaused();
  void onSystemResumed();
  void onRunningGameChanged(const QString& filename, const QString& game_serial, const QString& game_title);
  void onAchievementsLoginRequested(Achievements::LoginRequestReason reason);
  void onAchievementsLoginSucceeded(const QString& display_name, quint32 points, quint32 sc_points,
                                    quint32 unread_messages);
  void onAchievementsChallengeModeChanged(bool enabled);
  void onApplicationStateChanged(Qt::ApplicationState state);

  void onStartFileActionTriggered();
  void onStartDiscActionTriggered();
  void onStartBIOSActionTriggered();
  void onChangeDiscFromFileActionTriggered();
  void onChangeDiscFromGameListActionTriggered();
  void onChangeDiscFromDeviceActionTriggered();
  void onChangeDiscMenuAboutToShow();
  void onChangeDiscMenuAboutToHide();
  void onLoadStateMenuAboutToShow();
  void onSaveStateMenuAboutToShow();
  void onCheatsMenuAboutToShow();
  void onStartFullscreenUITriggered();
  void onFullscreenUIStateChange(bool running);
  void onRemoveDiscActionTriggered();
  void onViewToolbarActionToggled(bool checked);
  void onViewLockToolbarActionToggled(bool checked);
  void onViewStatusBarActionToggled(bool checked);
  void onViewGameListActionTriggered();
  void onViewGameGridActionTriggered();
  void onViewSystemDisplayTriggered();
  void onViewGamePropertiesActionTriggered();
  void onGitHubRepositoryActionTriggered();
  void onIssueTrackerActionTriggered();
  void onDiscordServerActionTriggered();
  void onAboutActionTriggered();
  void onCheckForUpdatesActionTriggered();
  void onToolsMemoryCardEditorTriggered();
  void onToolsCoverDownloaderTriggered();
  void onToolsCheatManagerTriggered();
  void onToolsOpenDataDirectoryTriggered();
  void onSettingsTriggeredFromToolbar();

  void onGameListRefreshComplete();
  void onGameListRefreshProgress(const QString& status, int current, int total);
  void onGameListSelectionChanged();
  void onGameListEntryActivated();
  void onGameListEntryContextMenuRequested(const QPoint& point);

  void onUpdateCheckComplete();

  void openCPUDebugger();
  void onCPUDebuggerClosed();

protected:
  void showEvent(QShowEvent* event) override;
  void closeEvent(QCloseEvent* event) override;
  void changeEvent(QEvent* event) override;
  void dragEnterEvent(QDragEnterEvent* event) override;
  void dropEvent(QDropEvent* event) override;
  void moveEvent(QMoveEvent* event) override;
  void resizeEvent(QResizeEvent* event) override;

#ifdef _WIN32
  bool nativeEvent(const QByteArray& eventType, void* message, qintptr* result) override;
#endif

private:
  static void setStyleFromSettings();
  static void setIconThemeFromSettings();

  /// Initializes the window. Call once at startup.
  void initialize();

  void setupAdditionalUi();
  void connectSignals();

  void updateEmulationActions(bool starting, bool running, bool cheevos_challenge_mode);
  void updateStatusBarWidgetVisibility();
  void updateWindowTitle();
  void updateWindowState(bool force_visible = false);

  void setProgressBar(int current, int total);
  void clearProgressBar();

  QWidget* getContentParent();
  QWidget* getDisplayContainer() const;
  bool isShowingGameList() const;
  bool isRenderingFullscreen() const;
  bool isRenderingToMain() const;
  bool shouldHideMouseCursor() const;
  bool shouldHideMainWindow() const;

  void switchToGameListView();
  void switchToEmulationView();
  void saveGeometryToConfig();
  void restoreGeometryFromConfig();
  void saveDisplayWindowGeometryToConfig();
  void restoreDisplayWindowGeometryFromConfig();
  void createDisplayWidget(bool fullscreen, bool render_to_main, bool use_main_window_pos);
  void destroyDisplayWidget(bool show_game_list);
  void updateDisplayWidgetCursor();
  void updateDisplayRelatedActions(bool has_surface, bool render_to_main, bool fullscreen);

  SettingsWindow* getSettingsDialog();
  void doSettings(const char* category = nullptr);

  void doControllerSettings(ControllerSettingsWindow::Category category = ControllerSettingsWindow::Category::Count);

  void updateDebugMenuCPUExecutionMode();
  void updateDebugMenuGPURenderer();
  void updateDebugMenuCropMode();
  void updateMenuSelectedTheme();
  std::string getDeviceDiscPath(const QString& title);
  void setGameListEntryCoverImage(const GameList::Entry* entry);
  void clearGameListEntryPlayTime(const GameList::Entry* entry);
  void setTheme(const QString& theme);
  void updateTheme();
  void reloadThemeSpecificImages();
  void destroySubWindows();

  void registerForDeviceNotifications();
  void unregisterForDeviceNotifications();

  /// Fills menu with save state info and handlers.
  void populateGameListContextMenu(const GameList::Entry* entry, QWidget* parent_window, QMenu* menu);

  void populateLoadStateMenu(const char* game_serial, QMenu* menu);
  void populateSaveStateMenu(const char* game_serial, QMenu* menu);

  /// Fills menu with the current playlist entries. The disc index is marked as checked.
  void populateChangeDiscSubImageMenu(QMenu* menu, QActionGroup* action_group);

  /// Fills menu with the current cheat options.
  void populateCheatsMenu(QMenu* menu);

  std::optional<bool> promptForResumeState(const std::string& save_state_path);
  void startFile(std::string path, std::optional<std::string> save_path, std::optional<bool> fast_boot);
  void startFileOrChangeDisc(const QString& path);
  void promptForDiscChange(const QString& path);

  Ui::MainWindow m_ui;

  GameListWidget* m_game_list_widget = nullptr;

  DisplayWidget* m_display_widget = nullptr;
  DisplayContainer* m_display_container = nullptr;

  QProgressBar* m_status_progress_widget = nullptr;
  QLabel* m_status_renderer_widget = nullptr;
  QLabel* m_status_fps_widget = nullptr;
  QLabel* m_status_vps_widget = nullptr;
  QLabel* m_status_resolution_widget = nullptr;

  QMenu* m_settings_toolbar_menu = nullptr;

  SettingsWindow* m_settings_window = nullptr;
  ControllerSettingsWindow* m_controller_settings_window = nullptr;

  AutoUpdaterDialog* m_auto_updater_dialog = nullptr;
  MemoryCardEditorWindow* m_memory_card_editor_window = nullptr;
  CheatManagerDialog* m_cheat_manager_dialog = nullptr;
  DebuggerWindow* m_debugger_window = nullptr;

  bool m_was_paused_by_focus_loss = false;
  bool m_open_debugger_on_start = false;
  bool m_relative_mouse_mode = false;
  bool m_hide_mouse_cursor = false;

  bool m_display_created = false;
  bool m_save_states_invalidated = false;
  bool m_was_paused_on_surface_loss = false;
  bool m_was_disc_change_request = false;
  bool m_is_closing = false;

#ifdef _WIN32
  void* m_device_notification_handle = nullptr;
#endif
};

extern MainWindow* g_main_window;