//+----------------------------------------------------------------------------+
//| Description:  Magic Set Editor - Program to make Magic (tm) cards          |
//| Copyright:    (C) Twan van Laarhoven and the other MSE developers          |
//| License:      GNU General Public License 2 or later (see file COPYING)     |
//+----------------------------------------------------------------------------+

#pragma once

// ----------------------------------------------------------------------------- : Includes

#include <util/prec.hpp>
#include <util/io/package.hpp>

DECLARE_POINTER_TYPE(Installer);
DECLARE_POINTER_TYPE(PackageVersion);
DECLARE_POINTER_TYPE(PackageDescription);
DECLARE_POINTER_TYPE(DownloadableInstaller);
DECLARE_POINTER_TYPE(InstallablePackage);

// The installer system consists of several layers:
//  - Installer              = an actual package available in memory, containing packages to be installed
//  - DownloadableInstaller  = an installar (possibly) not yet available, i.e. just its URL
//  - PackageDescription     = description of a package version
//  - InstallablePackage     = the complete status of a package, both local and remote


// ----------------------------------------------------------------------------- : Installer

/// A package that contains other packages that can be installed
/** Installers will be sent around the internet, etc. so they are fairly selfcontained.
 */
class Installer : public Packaged {
public:
  String prefered_filename;  ///< What filename should be used (by default), when creating the installer
  vector<PackageDescriptionP> packages;  ///< Packages to install
  
  /// Add a package to the installer (if it is not already added).
  /** If the package is named *.mse-installer uses it as the filename instead */
  void addPackage(const String& package);
  /// Add a package to the installer (if it is not already added).
  /** The first package gives the name of the installer.
   */
  void addPackage(Packaged& package);
  
protected:
  String typeName() const override;
  Version fileVersion() const override;
  void validate(Version file_app_version) override;
  DECLARE_REFLECTION();
};

// ----------------------------------------------------------------------------- : Installer descriptions

/// A description of a package in an installer.
/** This is essentially the same information as in a Packaged object.
 *  TODO: try to merge these!
 */
class PackageDescription : public IntrusivePtrBase<PackageDescription> {
public:
  PackageDescription();
  PackageDescription(const Packaged& package);
  
  String name;        ///< Filename of the package
  Version version;      ///< Version number of this package
  String short_name;      ///< Short name of this package
  String full_name;      ///< Name of this package, for menus etc.
  String icon_url;      ///< Filename or URL of icon to use in package lists
  Image  icon;        ///< Icon for the package
  String installer_group;    ///< Where to put this package in the installer
  int    position_hint;    ///< A hint for the package list
  String description;      ///< Changelog/description
  vector<PackageDependencyP> dependencies;  ///< Dependencies of this package
  
  /// Merge two descriptions a package. This package takes precedence
  /** Usually one of the descriptions will refer to the locally installed one, the other to the new one */
  void merge(const PackageDescription& p2);
  
  DECLARE_REFLECTION();
};

/// A description of the contents of an installer
class InstallerDescription : public IntrusivePtrBase<InstallerDescription> {
public:
  vector<PackageDescriptionP> packages;
  
  DECLARE_REFLECTION();
};

/// Information on an installer that can be downloaded
class DownloadableInstaller : public IntrusivePtrBase<DownloadableInstaller> {
public:
  DownloadableInstaller() : downloadable(true) {}
  DownloadableInstaller(const InstallerP& installer);
  ~DownloadableInstaller();
  
  InstallerP installer;      ///< The installer, if it is loaded
  String     installer_url;  ///< The URL where the installer can be found
  String     installer_file; ///< The temp file where the installer can be found (after downloading)
  bool       downloadable;   ///< Is the installer downloadable (in)directly from that url?
  vector<PackageDescriptionP> packages; ///< Packages provided by this installer
  
  DECLARE_REFLECTION();
};

// ----------------------------------------------------------------------------- : Installable package

/// Installation status of a package
enum PackageStatus
{  PACKAGE_NOT_INSTALLED = 0x0000
,  PACKAGE_INSTALLED     = 0x0001
,  PACKAGE_REMOVABLE     = 0x0002
,  PACKAGE_INSTALLER     = 0x0010 ///< Package can be installed (there is an installer)
,  PACKAGE_INSTALLABLE   = 0x0110 ///< Package can be installed (and it makes sense to do so)
,  PACKAGE_NOT_UP_TO_DATE= 0x0200 ///< The local installed version (if any) is not up to date
,  PACKAGE_UPDATES       = 0x0311 ///< Remote updates available
,  PACKAGE_MODIFIED      = 0x1001 ///< Local changes made
,  PACKAGE_MISSING_DEP   = 0x2000 ///< Missing a dependency for installation
,  PACKAGE_CONFLICTS     = PACKAGE_UPDATES | PACKAGE_MODIFIED
};

/// (un)install a package?
enum PackageAction
{  PACKAGE_ACT_NOTHING = 0x001  ///< Don't change anything
,  PACKAGE_ACT_INSTALL = 0x002  ///< Install or upgrade the package
,  PACKAGE_ACT_REMOVE  = 0x004  ///< Remove the package (if it was installed)
,  PACKAGE_ACT_LOCAL   = 0x010  ///< In the local package directory
,  PACKAGE_ACT_GLOBAL  = 0x020  ///< In the global package directory
,  PACKAGE_ACT_WHERE   = PACKAGE_ACT_LOCAL | PACKAGE_ACT_GLOBAL
};
// bit twidling
inline PackageAction operator | (PackageAction a, PackageAction b) { return (PackageAction)((int)a | (int) b); }
inline bool flag(int flags, int flag) { return (flags & flag) == flag; }

/// A package that can be installed, or is already installed
class InstallablePackage : public IntrusivePtrVirtualBase {
public:
  /// A new package
  InstallablePackage(const PackageDescriptionP&, const DownloadableInstallerP&);
  /// An installed package
  InstallablePackage(const PackageDescriptionP&, const PackageVersionP&);
  
  PackageDescriptionP    description; ///< The details of the package. Either from the installed package or from an installer
  PackageVersionP        installed;   ///< The information of the installed package (if installed)
  DownloadableInstallerP installer;   ///< The installer to install from (if updates are available)
  PackageStatus          status;      ///< Status of installation
  PackageAction          action;      ///< What to do with this package?
  
  int automatic; ///< Install/upgrade/remove automaticly to satisfy this many packages
  
  PackageAction old_action;
  int           old_automatic;
  
  void determineStatus();
  
  /// After the action, will the package be installed?
  bool willBeInstalled() const;
  
  /// Is the action possible?
  bool can(PackageAction act) const;
  /// Is the action currently selected?
  bool has(PackageAction act) const;
  /// Does the package have the given status bits all set?
  bool has(PackageStatus stat) const;
  
  /// Merge two descriptions of installable packages
  void merge(const InstallablePackage& p2);
};


typedef vector<InstallablePackageP> InstallablePackages;

/// Sort a list of InstallablePackages by package name
void sort(InstallablePackages& packages);

/// Merge two lists of InstallablePackages.
/** The first list contains installed packages, the second list contains packages in an installer
 *  @pre both lists are sorted by package name
 *  @post the output will be sorted
 */
void merge(InstallablePackages& installed, const InstallablePackages& from_installer);

/// Merge the packages from a DownloadableInstaller into a list of InstallablePackages.
void merge(InstallablePackages& installed, const DownloadableInstallerP& installer);

/// Set the action to perform on a given package, makes sure the dependencies are also set correctly
/** Returns true on success
 *  action may be PACKAGE_NOTHING to clear the action
 */
bool set_package_action(InstallablePackages& packages, const InstallablePackageP& package, PackageAction action);

// ----------------------------------------------------------------------------- : Program package

/// The "magicseteditor.exe" package is special, it refers to the program
extern String mse_package;

InstallablePackageP mse_installable_package();

