/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
   file LICENSE.rst or https://cmake.org/licensing for details.  */
#pragma once

#include <map>
#include <memory>
#include <string>

#include "cmCPackGenerator.h"
#include "cmWIXPatch.h"
#include "cmWIXShortcut.h"

class cmWIXSourceWriter;
class cmWIXDirectoriesSourceWriter;
class cmWIXFilesSourceWriter;
class cmWIXFeaturesSourceWriter;

/** \class cmCPackWIXGenerator
 * \brief A generator for WIX files
 */
class cmCPackWIXGenerator : public cmCPackGenerator
{
public:
  cmCPackTypeMacro(cmCPackWIXGenerator, cmCPackGenerator);

  cmCPackWIXGenerator();
  cmCPackWIXGenerator(cmCPackWIXGenerator const&) = delete;
  cmCPackWIXGenerator const& operator=(cmCPackWIXGenerator const&) = delete;
  ~cmCPackWIXGenerator();

protected:
  int InitializeInternal() override;

  int PackageFiles() override;

  char const* GetOutputExtension() override { return ".msi"; }

  enum CPackSetDestdirSupport SupportsSetDestdir() const override
  {
    return SETDESTDIR_UNSUPPORTED;
  }

  bool SupportsAbsoluteDestination() const override { return false; }

  bool SupportsComponentInstallation() const override { return true; }

private:
  using id_map_t = std::map<std::string, std::string>;
  using ambiguity_map_t = std::map<std::string, size_t>;
  using extension_set_t = std::set<std::string>;
  using xmlns_map_t = std::map<std::string, std::string>;

  enum class DefinitionType
  {
    STRING,
    PATH
  };

  bool InitializeWiXConfiguration();

  bool PackageFilesImpl();
  bool PackageWithWix();
  bool PackageWithWix3();

  void CreateWiXVariablesIncludeFile();

  void CreateWiXPropertiesIncludeFile();

  void CreateWiXProductFragmentIncludeFile();

  void CopyDefinition(cmWIXSourceWriter& source, std::string const& name,
                      DefinitionType type = DefinitionType::STRING);

  void AddDefinition(cmWIXSourceWriter& source, std::string const& name,
                     std::string const& value);

  bool CreateWiXSourceFiles();

  std::string GetRootFolderId() const;

  bool GenerateMainSourceFileFromTemplate();

  bool CreateFeatureHierarchy(cmWIXFeaturesSourceWriter& featureDefinitions);

  bool AddComponentsToFeature(
    std::string const& rootPath, std::string const& featureId,
    cmWIXDirectoriesSourceWriter& directoryDefinitions,
    cmWIXFilesSourceWriter& fileDefinitions,
    cmWIXFeaturesSourceWriter& featureDefinitions, cmWIXShortcuts& shortcuts,
    int diskId);

  bool CreateShortcuts(std::string const& cpackComponentName,
                       std::string const& featureId,
                       cmWIXShortcuts const& shortcuts,
                       bool emitUninstallShortcut,
                       cmWIXFilesSourceWriter& fileDefinitions,
                       cmWIXFeaturesSourceWriter& featureDefinitions);

  bool CreateShortcutsOfSpecificType(
    cmWIXShortcuts::Type type, std::string const& cpackComponentName,
    std::string const& featureId, std::string const& idPrefix,
    cmWIXShortcuts const& shortcuts, bool emitUninstallShortcut,
    cmWIXFilesSourceWriter& fileDefinitions,
    cmWIXFeaturesSourceWriter& featureDefinitions);

  void AppendUserSuppliedExtraSources();

  void AppendUserSuppliedExtraObjects(std::ostream& stream);

  bool CreateLicenseFile();

  bool RunWiXCommand(std::string const& command);

  bool RunCandleCommand(std::string const& sourceFile,
                        std::string const& objectFile);

  bool RunLightCommand(std::string const& objectFiles);

  void AddDirectoryAndFileDefinitions(
    std::string const& topdir, std::string const& directoryId,
    cmWIXDirectoriesSourceWriter& directoryDefinitions,
    cmWIXFilesSourceWriter& fileDefinitions,
    cmWIXFeaturesSourceWriter& featureDefinitions,
    std::vector<std::string> const& packageExecutables,
    std::vector<std::string> const& desktopExecutables,
    cmWIXShortcuts& shortcuts, int diskId);

  bool RequireOption(std::string const& name, std::string& value) const;

  std::string GetArchitecture() const;

  static std::string GenerateGUID();

  static std::string QuotePath(std::string const& path);

  static std::string GetRightmostExtension(std::string const& filename);

  std::string PathToId(std::string const& path);

  std::string CreateNewIdForPath(std::string const& path);

  static std::string CreateHashedId(std::string const& path,
                                    std::string const& normalizedFilename);

  std::string NormalizeComponentForId(std::string const& component,
                                      size_t& replacementCount);

  static bool IsLegalIdCharacter(char c);

  void CollectExtensions(std::string const& variableName,
                         extension_set_t& extensions);

  void CollectXmlNamespaces(std::string const& variableName,
                            xmlns_map_t& namespaces);

  void AddCustomFlags(std::string const& variableName, std::ostream& stream);

  std::string RelativePathWithoutComponentPrefix(std::string const& path);

  void InjectXmlNamespaces(cmWIXSourceWriter& sourceWriter);

  std::vector<std::string> WixSources;
  id_map_t PathToIdMap;
  ambiguity_map_t IdAmbiguityCounter;

  extension_set_t WixExtensions;
  extension_set_t CandleExtensions;
  extension_set_t LightExtensions;
  xmlns_map_t CustomXmlNamespaces;

  std::string CPackTopLevel;

  std::unique_ptr<cmWIXPatch> Patch;

  unsigned long WixVersion = 3;

  cmWIXSourceWriter::GuidType ComponentGuidType;
};
