#ifndef MODORGANIZER_COMMANDLINE_INCLUDED
#define MODORGANIZER_COMMANDLINE_INCLUDED
#include "moshortcut.h"
#include <memory>
#include <vector>

class OrganizerCore;
class MOApplication;
class MOMultiProcess;

namespace cl
{

namespace po = boost::program_options;

// base class for all commands
//
class Command
{
public:
  virtual ~Command() = default;

  // command name, used on the command line to invoke it; this is meta().name
  //
  std::string name() const;

  // command short description, shown in general help; this is
  // meta().description
  //
  std::string description() const;

  // usage line, puts together the name and whatever meta().usage is
  //
  std::string usageLine() const;

  // shown after the usage line; this is meta().more
  //
  std::string moreInfo() const;

  // returns all options for this command, including hidden ones; used to parse
  // the command line
  //
  po::options_description allOptions() const;

  // returns visible options, used to display usage
  //
  po::options_description visibleOptions() const;

  // returns positional arguments
  //
  po::positional_options_description positional() const;

  // when this returns true, the command line is not parsed at all and doRun()
  // is expected to use untouched()
  //
  virtual bool legacy() const;

  // remembers the given values
  //
  void set(const std::wstring& originalLine, po::variables_map vm,
           std::vector<std::wstring> untouched);

  // called as soon as the command line has been parsed; return something to
  // exit immediately
  //
  virtual std::optional<int> runEarly();

  // called as soon as the MOApplication has been created, which is also the
  // first time where Qt stuff is available
  //
  virtual std::optional<int> runPostApplication(MOApplication& a);

  // called as soon as the multi process checks have confirmed that this is
  // a primary instance; return something to exit immediately
  //
  virtual std::optional<int> runPostMultiProcess(MOMultiProcess& mp);

  // called after the OrganizerCore has been initialized; return something to
  // exit immediatley
  //
  virtual std::optional<int> runPostOrganizer(OrganizerCore& core);

  // whether this command can be forwarded to the primary instance if one is
  // already running
  //
  // if an instance is already running and this returns true, the whole command
  // line is sent as a message to the primary instance, which will construct a
  // new CommandLine and call runPostOrganizer() with it
  //
  // if an instance is already running and this returns false, the "an instance
  // is already running" error dialog will be shown
  //
  // if this is the primary instance, this function is not called
  //
  virtual bool canForwardToPrimary() const;

protected:
  // meta information about this command, returned by derived classes
  //
  struct Meta
  {
    std::string name, description, usage, more;

    Meta(std::string name, std::string description, std::string usage,
         std::string more);
  };

  // meta
  //
  virtual Meta meta() const = 0;

  // returns visible options specific to this command
  //
  virtual po::options_description getVisibleOptions() const;

  // returns hidden options specific to this command
  //
  virtual po::options_description getInternalOptions() const;

  // returns positional arguments specific to this command
  //
  virtual po::positional_options_description getPositional() const;

  // returns the original command line
  //
  const std::wstring& originalCmd() const;

  // variables
  //
  const po::variables_map& vm() const;

  // returns unparsed options, only used by launch
  //
  const std::vector<std::wstring>& untouched() const;

private:
  std::wstring m_original;
  po::variables_map m_vm;
  std::vector<std::wstring> m_untouched;
};

// generates a crash dump for another MO process
//
class CrashDumpCommand : public Command
{
protected:
  po::options_description getVisibleOptions() const override;
  Meta meta() const override;
  std::optional<int> runEarly() override;
};

// this is the `launch` command used when starting a process from within the
// virtualized directory, see processrunner.cpp
//
// it has its own parsing of the command line to extract the argument after
// `launch` and use it as the cwd of the process, but pass the remaining
// arguments verbatim
//
// this is very old code that should probably never be changed
//
// note that it's actually buggy; in particular, it doesn't handle multiple
// whitespace between arguments
//
class LaunchCommand : public Command
{
public:
  bool legacy() const override;

protected:
  Meta meta() const override;
  std::optional<int> runEarly() override;

  int SpawnWaitProcess(LPCWSTR workingDirectory, LPCWSTR commandLine);

  LPCWSTR UntouchedCommandLineArguments(int parseArgCount,
                                        std::vector<std::wstring>& parsedArgs);
};

// runs a program or an executable
//
class RunCommand : public Command
{
protected:
  Meta meta() const override;

  po::options_description getVisibleOptions() const override;
  po::options_description getInternalOptions() const override;
  po::positional_options_description getPositional() const override;

  bool canForwardToPrimary() const override;
  std::optional<int> runPostOrganizer(OrganizerCore& core) override;
};

// reloads the given plugin
//
class ReloadPluginCommand : public Command
{
protected:
  Meta meta() const override;

  po::options_description getInternalOptions() const override;
  po::positional_options_description getPositional() const override;

  bool canForwardToPrimary() const override;
  std::optional<int> runPostOrganizer(OrganizerCore& core) override;
};

// downloads a file
//
class DownloadFileCommand : public Command
{
protected:
  Meta meta() const override;

  po::options_description getInternalOptions() const override;
  po::positional_options_description getPositional() const override;

  bool canForwardToPrimary() const override;
  std::optional<int> runPostOrganizer(OrganizerCore& core) override;
};

// refreshes mo
//
class RefreshCommand : public Command
{
protected:
  Meta meta() const override;
  bool canForwardToPrimary() const override;
  std::optional<int> runPostOrganizer(OrganizerCore& core) override;
};

// parses the command line and runs any given command
//
// the command line used to support a few commands but with no real conventions;
// those are mostly preserved for backwards compatibility, but deprecated:
//
//   - moshortcut:// for desktop/taskbar shortcuts, may contain an instance name
//     and a configured executable or arbitrary binary (still used in MO for
//     shortcuts)
//
//   - nxm:// links
//
//   - the name of a configured executable or path to binary, followed by
//     arbitrary parameters, forwarded to the program
//
// any command added CommandLine will unfortunately break any executable with
// the same name: `ModOrganizer.exe run` used to launch a program named "run"
// but will now execute the command "run"
//
// if moshortcut:// is detected and has an instance, it will override -i if both
// are given
//
//
// the command is used in two phases:
//
//    1) run() is called in main() shortly after startup; it parses the command
//       line and runs the given command, if any
//
//    2) if the command did not request to exit, the instance is loaded
//       in main() et al. and setupCore() is called; it handles moshortcut,
//       nxm links and starting executables/binaries
//
// either can return an exit code, which will make MO exit immediately
//
class CommandLine
{
public:
  CommandLine();

  // parses the given command line and calls runEarly() on the appropriate
  // command, if any
  //
  // returns an empty optional if execution should continue, or a return code
  // if MO must quit
  //
  std::optional<int> process(const std::wstring& line);

  // called as soon as the MOApplication has been created; this handles a few
  // global actions and forwards to the command, if any
  //
  std::optional<int> runPostApplication(MOApplication& a);

  // calls Command::runPostMultiProcess() on the command, if any
  //
  std::optional<int> runPostMultiProcess(MOMultiProcess& mp);

  // calls Command::runPostOrganizer() on the command, if any
  //
  // if MO wasn't invoked with a valid command, this also handles moshortcut,
  // nxm links and starting processes
  //
  std::optional<int> runPostOrganizer(OrganizerCore& core);

  // called when this instance is not the primary one
  //
  // if a command was executed and the command returns true for
  // canForwardToPrimary(), this forwards the command line as an external
  // message and returns true
  //
  // if the command returns false for canForwardToPrimary(), returns false
  //
  // if there was no valid command on the command line, this also forwards
  // moshortcut and nxm links
  //
  // if there was no command, moshortcut or nxm links, this returns false, which
  // will end up displaying an error message about an instance already running
  //
  bool forwardToPrimary(MOMultiProcess& multiProcess);

  // clears parsed options, used when MO is "restarted" so the options aren't
  // processed again
  //
  void clear();

  // global usage string plus usage for the given command, if any
  //
  std::string usage(const Command* c = nullptr) const;

  // whether --pick was given
  //
  bool pick() const;

  // whether --multiple was given
  //
  bool multiple() const;

  // profile override (-p)
  //
  std::optional<QString> profile() const;

  // instance override (-i)
  //
  std::optional<QString> instance() const;

  // returns the data parsed from an moshortcut:// option, if any
  //
  const MOShortcut& shortcut() const;

  // returns the nxm:// link, if any
  //
  std::optional<QString> nxmLink() const;

  // returns the executable/binary, if any
  //
  std::optional<QString> executable() const;

  // returns the list of arguments, excluding moshortcut or executable name;
  // deprecated, only use with executable()
  //
  const QStringList& untouched() const;

private:
  po::options_description m_visibleOptions, m_allOptions;
  po::positional_options_description m_positional;
  std::vector<std::unique_ptr<Command>> m_commands;
  po::variables_map m_vm;
  MOShortcut m_shortcut;
  std::optional<QString> m_nxmLink;
  std::optional<QString> m_executable;
  QStringList m_untouched;
  Command* m_command;

  void createOptions();
  std::string more() const;

  template <class... Ts>
  void add()
  {
    (m_commands.push_back(std::make_unique<Ts>()), ...);
  }

  std::optional<int> runEarly();
};

}  // namespace cl

#endif  // MODORGANIZER_COMMANDLINE_INCLUDED