#ifndef MODORGANIZER_CREATEINSTANCEDIALOGPAGES_INCLUDED
#define MODORGANIZER_CREATEINSTANCEDIALOGPAGES_INCLUDED

#include "createinstancedialog.h"
#include <filterwidget.h>

#include <QCommandLinkButton>
#include <QLabel>
#include <QLineEdit>

namespace MOBase
{
class IPluginGame;
}
class NexusConnectionUI;

namespace cid
{

// returns "%base_dir%/dir"
//
QString makeDefaultPath(const std::wstring& dir);

// remembers the original text of the given label and, if it contains a %1,
// sets it in setText()
//
class PlaceholderLabel
{
public:
  PlaceholderLabel(QLabel* label);

  // if the original label text contained a %1, replaces it by the arg and
  // sets that as the new label text
  //
  void setText(const QString& arg);

  // whether the label is visible
  //
  void setVisible(bool b);

private:
  QLabel* m_label;
  QString m_original;
};

// one page in the wizard
//
// each page can implement one or more selected*() below; those are called
// by CreateInstanceDialog to gather data from all pages
//
class Page
{
public:
  Page(CreateInstanceDialog& dlg);

  // whether this page has been filled and is valid; used by the dialog to
  // determine if it can move to the next page
  //
  virtual bool ready() const;

  // called every time a page is shown in the screen
  //
  void activated();

  // overrides whether this page should be skipped; this is used by
  // CreateInstanceDialog::setSinglePage() to disable all other pages
  //
  void setSkip(bool b);

  // whether this page should be skipped
  //
  bool skip() const;

  // asks the dialog to update its navigation buttons, typically used when a
  // page changes its ready state without moving to a different page
  //
  void updateNavigation();

  // asks the dialog to move to the next page; some pages will automatically
  // advance once the user has made the proper selection
  //
  void next();

  // called from the dialog when an action is requested on the current page;
  // returns true when handled
  //
  virtual bool action(CreateInstanceDialog::Actions a);

  // returns the instance type
  //
  virtual CreateInstanceDialog::Types selectedInstanceType() const;

  // returns the game plugin
  //
  virtual MOBase::IPluginGame* selectedGame() const;

  // returns the game directory
  //
  virtual QString selectedGameLocation() const;

  // returns the game variant
  //
  virtual QString selectedGameVariant(MOBase::IPluginGame* game) const;

  // returns the instance name
  //
  virtual QString selectedInstanceName() const;

  // returns the various paths
  //
  virtual CreateInstanceDialog::Paths selectedPaths() const;

  // returns the profile settings
  //
  virtual CreateInstanceDialog::ProfileSettings profileSettings() const;

protected:
  Ui::CreateInstanceDialog* ui;
  CreateInstanceDialog& m_dlg;
  const PluginContainer& m_pc;
  bool m_skip;
  bool m_firstActivation;

  // called every time a page is shown in the screen; `firstTime` is true for
  // first activation
  //
  virtual void doActivated(bool firstTime);

  // implemented by derived classes, overridden by setSkip(true)
  //
  virtual bool doSkip() const;
};

// introduction page, can be disabled by a global setting
//
class IntroPage : public Page
{
public:
  IntroPage(CreateInstanceDialog& dlg);

protected:
  bool doSkip() const override;

private:
  // the setting is only checked once when opening the dialog, or going forwards
  // then back after checking the box wouldn't show the intro page any more,
  // which would be unexpected
  bool m_skip;
};

// instance type page
//
class TypePage : public Page
{
public:
  TypePage(CreateInstanceDialog& dlg);

  // whether a type has been been selected
  //
  bool ready() const override;

  // returns the selected type
  //
  CreateInstanceDialog::Types selectedInstanceType() const override;

  // selects a global instance
  //
  void global();

  // selects a portable instance
  //
  void portable();

protected:
  // focuses global instance button on first activation
  //
  void doActivated(bool firstTime) override;

private:
  CreateInstanceDialog::Types m_type;
};

// game plugin page, displays a list of command buttons for each game, along
// with a "browse" button for custom directories and filtering stuff
//
// the game list initially only shows plugins that report isInstalled(), and the
// user has two ways of specifying paths for games that were not found:
//
//   1) by clicking the "Browse..." button and selecting an arbitrary directory
//
//      all plugins are checked until one returns true for looksValid(); if none
//      of them do, this is an error
//
//   2) by checking the "Show all supported games" checkbox and clicking one
//      of the games on the list
//
//      if the selected plugin doesn't recognize the directory, the user is
//      warned, but is allowed to continue; there's also some logic to try to
//      find another plugin that can manage this directory and suggest it
//      instead
//
class GamePage : public Page
{
public:
  GamePage(CreateInstanceDialog& dlg);

  // whether a game has been selected
  //
  bool ready() const override;

  // handles find
  //
  bool action(CreateInstanceDialog::Actions a) override;

  // returns the selected game
  //
  MOBase::IPluginGame* selectedGame() const override;

  // returns the selected game directory
  QString selectedGameLocation() const override;

  // selects the given game and toggles its associated button; the game
  // directory can be overridden
  //
  // pops up a directory selection dialog if `dir` is empty and the plugin
  // hasn't detected the game
  //
  void select(MOBase::IPluginGame* game, const QString& dir = {});

  // pops up a directory selection dialog and looks for a plugin to manage
  // it
  //
  void selectCustom();

  // pops up a warning dialog that the game at the given path is not supported
  // by any plugin, includes a list of all game plugins in the details section
  // of the dialog
  //
  void warnUnrecognized(const QString& path);

private:
  // a single game, with its button and custom directory, if any
  //
  struct Game
  {
    // game plugin
    MOBase::IPluginGame* game = nullptr;

    // button on the ui
    QCommandLinkButton* button = nullptr;

    // game directory; set in ctor if the plugin has detected the game, or
    // set later when the user selects a directory
    QString dir;

    // whether a directory has been set for this game, either auto detected
    // or by the user
    bool installed = false;

    Game(MOBase::IPluginGame* g);
    Game(const Game&)            = delete;
    Game& operator=(const Game&) = delete;
  };

  // list of all game plugins, even if they're not installed; those are filtered
  // from the ui if the checkbox isn't checked
  std::vector<std::unique_ptr<Game>> m_games;

  // current selection
  Game* m_selection;

  // filter
  MOBase::FilterWidget m_filter;

  // returns a list of all the game plugins sorted with natsort
  //
  std::vector<MOBase::IPluginGame*> sortedGamePlugins() const;

  // creates the m_games list
  //
  void createGames();

  // finds the game struct associated with the given game
  //
  Game* findGame(MOBase::IPluginGame* game);

  // creates the ui for the given game button
  //
  void createGameButton(Game* g);

  // adds the given button to the ui
  //
  void addButton(QAbstractButton* b);

  // updates the given button on the ui, sets the text, icon, etc.
  //
  void updateButton(Game* g);

  // game buttons are toggles, this creates the button for the given game if
  // it doesn't exist and toggles it on
  //
  // the button might not exist if, for example:
  //   1) this game is currently filtered out (not installed, doesn't match
  //      filter text, etc) and,
  //   2) the user browses to a directory that a hidden plugin can use
  //
  void selectButton(Game* g);

  // removes all buttons from the ui
  //
  void clearButtons();

  // creates the "Browse" button
  //
  QCommandLinkButton* createCustomButton();

  // clears the button list and adds all the buttons to it, depending on
  // filtering and stuff
  //
  void fillList();

  // checks whether the given path looks valid to the given game plugin
  //
  // if the plugin doesn't like the path, allows the user to override and
  // accept, but also attempts to find another plugin that wants it and
  // propose that as an alternative, if there's one
  //
  // returns:
  //   - if the user selects the alternative plugin, returns that plugin
  //     instead;
  //   - if the path is bad but the user overrides, returns the given plugin
  //   - if the user cancels or if no plugins can manage the directory, returns
  //     null
  //
  Game* checkInstallation(const QString& path, Game* g);

  // tells the user that the path cannot be handled by any game plugin, returns
  // true if the user decides to accept anyway
  //
  bool confirmUnknown(const QString& path, MOBase::IPluginGame* game);

  // tells the user that the path can be handled by a different plugin than the
  // selected one and allows them to either
  //   1) use the alternative, guessedGame is returned;
  //   2) use the selection anyway, selectedGame is returned; or
  //   3) cancel, null is returned
  //
  MOBase::IPluginGame* confirmOtherGame(const QString& path,
                                        MOBase::IPluginGame* selectedGame,
                                        MOBase::IPluginGame* guessedGame);

  // detects if the given path likely contains a Microsoft Store game
  //
  bool detectMicrosoftStore(const QString& path);

  // tells the user that the path probably contains a Microsoft Store game that
  // is not supported, returns true if the user decides to accept anyway.
  //
  bool confirmMicrosoftStore(const QString& path, MOBase::IPluginGame* game);
};

// game variants page; displays a list of command buttons for game variants, as
// reported by the game plugin
//
// this page is always skipped if the game plugin reports no variants
//
class VariantsPage : public Page
{
public:
  VariantsPage(CreateInstanceDialog& dlg);

  // whether a variant has been selected or the game plugin reports no variants
  //
  bool ready() const override;

  // uses the game selected in the previous page to fill the list, this must be
  // called every time because the user may go back in forth in the wizard
  //
  void doActivated(bool firstTime) override;

  // returns the selected variant, if any
  //
  QString selectedGameVariant(MOBase::IPluginGame* game) const override;

  // selects the given variant
  //
  void select(const QString& variant);

protected:
  // returns true if the game has no variants
  //
  bool doSkip() const override;

private:
  // game that was selected the last time this page was active
  MOBase::IPluginGame* m_previousGame;

  // buttons
  std::vector<QCommandLinkButton*> m_buttons;

  // selected variant
  QString m_selection;

  // fills the list with buttons
  void fillList();
};

// instance name page; displays a textbox where the user can enter a name and
// does basic checks to make sure the name is valid and not a duplicate
//
// skipped for portable instances
//
class NamePage : public Page
{
public:
  NamePage(CreateInstanceDialog& dlg);

  // whether a valid name has been entered
  //
  bool ready() const override;

  // returns the instance name
  //
  QString selectedInstanceName() const override;

protected:
  // uses the selected game to generate an instance name
  //
  // as long as the user hasn't modified the textbox, this will regenerate a new
  // instance name every time the selected game changes
  //
  void doActivated(bool firstTime) override;

  // returns true for portable instances
  //
  bool doSkip() const override;

private:
  // game label, replaces %1 with the game name
  PlaceholderLabel m_label;

  // "instance already exists" label, replaces %1 with instance name
  PlaceholderLabel m_exists;

  // "instance name invalid" label, replaces %1 with instance name
  PlaceholderLabel m_invalid;

  // whether the user has modified the text, prevents auto generation when the
  // selected game changes
  bool m_modified;

  // whether the instance name is valid
  bool m_okay;

  // called when the user modifies the textbox, remember that it has changed and
  // calls verify()
  //
  void onChanged();

  // check if the entered name is valid, sets m_okay and calls checkName()
  //
  void verify();

  // updates the ui depending on whether the given instance name is valid in
  // the given directory; returns false if the name is invalid
  //
  bool checkName(QString parentDir, QString name);
};

// instance paths page; shows a single textbox for the base directory, or a
// series of textboxes for all the configurable paths if the advanced checkbox
// is checked
//
class PathsPage : public Page
{
public:
  PathsPage(CreateInstanceDialog& dlg);

  // whether all paths make sense
  //
  bool ready() const override;

  // returns the selected paths
  //
  CreateInstanceDialog::Paths selectedPaths() const override;

protected:
  // resets all the paths if the instance type or instance name have changed,
  // the current values are kept as long as these don't change; also updates the
  // game name in the ui
  //
  void doActivated(bool firstTime) override;

private:
  // instance name the last time this page was active
  QString m_lastInstanceName;

  // instance type the last time this page was active
  CreateInstanceDialog::Types m_lastType;

  // help label, replaces %1 by the game name
  PlaceholderLabel m_label;

  // path exists/is invalid labels for the simple page, replaces %1 with the
  // path
  PlaceholderLabel m_simpleExists, m_simpleInvalid;

  // path exists/is invalid labels for the advanced page, replaces %1 with the
  // path
  PlaceholderLabel m_advancedExists, m_advancedInvalid;

  // whether the paths are valid
  bool m_okay;

  // called when the user changes any textbox, checks the path and updates nav
  //
  void onChanged();

  // opens a browse directory dialog and sets the given textbox
  //
  void browse(QLineEdit* e);

  // checks the simple or advanced paths, sets m_okay
  //
  void checkPaths();

  // checks a simple path, forwards to checkPath() with the simple labels
  //
  bool checkSimplePath(const QString& path);

  // checks an advanced path, forwards to checkPath() with the advanced labels
  //
  bool checkAdvancedPath(const QString& path);

  // returns false if the path is invalid or already exists, sets the given
  // labels accordingly
  //
  bool checkPath(QString path, PlaceholderLabel& existsLabel,
                 PlaceholderLabel& invalidLabel);

  // replaces %base_dir% in the given path by whatever's in the base path
  // textbox
  //
  QString resolve(const QString& path) const;

  // called when the advanced checkbox is toggled, switches the active page
  // and checks the paths
  //
  void onAdvanced();

  // called whenever the page becomes active
  //
  // this normally doesn't change the textboxes unless they're empty, but if the
  // instance name or type have changed, `force` is true, which forces all paths
  // to reset
  //
  void setPaths(const QString& name, bool force);

  // sets the given textbox to the path if it's empty or if `force` is true
  //
  void setIfEmpty(QLineEdit* e, const QString& path, bool force);
};

// default settings for profiles page; allow the user to set their preferred
// defaults for the profile options
//
class ProfilePage : public Page
{
public:
  ProfilePage(CreateInstanceDialog& dlg);

  // always returns true, options are boolean
  //
  bool ready() const override;

  CreateInstanceDialog::ProfileSettings profileSettings() const override;
};

// nexus connection page; this reuses the ui found in the settings dialog and
// is skipped if there's already an api key in the credentials manager
//
class NexusPage : public Page
{
public:
  NexusPage(CreateInstanceDialog& dlg);
  ~NexusPage();

  // always returns true, this is an optional page
  //
  bool ready() const override;

protected:
  // returns true if the api key was already detected
  //
  bool doSkip() const override;

private:
  // connection ui
  std::unique_ptr<NexusConnectionUI> m_connectionUI;

  // set to true only if the api key was detected when opening the dialog, or
  // going back and forth would skip the page after the process is completed,
  // which would be unexpected
  bool m_skip;
};

// shows a text log of all the creation parameters
//
class ConfirmationPage : public Page
{
public:
  ConfirmationPage(CreateInstanceDialog& dlg);

  // recreates the log with the latest settings
  //
  void doActivated(bool firstTime) override;

  // returns the text for the log
  //
  QString makeReview() const;

private:
  // returns a log line with the given caption and path, something like
  // " - caption: path"
  //
  QString dirLine(const QString& caption, const QString& path) const;
};

}  // namespace cid

#endif  // MODORGANIZER_CREATEINSTANCEDIALOGPAGES_INCLUDED