/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
   file Copyright.txt or https://cmake.org/licensing for details.  */
#include "cmCPackIFWPackage.h"

#include <cstddef>
#include <map>
#include <sstream>
#include <utility>

#include "cmCPackComponentGroup.h"
#include "cmCPackIFWCommon.h"
#include "cmCPackIFWGenerator.h"
#include "cmCPackIFWInstaller.h"
#include "cmCPackLog.h" // IWYU pragma: keep
#include "cmGeneratedFileStream.h"
#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"
#include "cmTimestamp.h"
#include "cmXMLWriter.h"

//---------------------------------------------------------- CompareStruct ---
cmCPackIFWPackage::CompareStruct::CompareStruct()
  : Type(cmCPackIFWPackage::CompareNone)
{
}

//------------------------------------------------------- DependenceStruct ---
cmCPackIFWPackage::DependenceStruct::DependenceStruct() = default;

cmCPackIFWPackage::DependenceStruct::DependenceStruct(
  const std::string& dependence)
{
  // Search compare section
  size_t pos = std::string::npos;
  if ((pos = dependence.find("<=")) != std::string::npos) {
    Compare.Type = cmCPackIFWPackage::CompareLessOrEqual;
    Compare.Value = dependence.substr(pos + 2);
  } else if ((pos = dependence.find(">=")) != std::string::npos) {
    Compare.Type = cmCPackIFWPackage::CompareGreaterOrEqual;
    Compare.Value = dependence.substr(pos + 2);
  } else if ((pos = dependence.find('<')) != std::string::npos) {
    Compare.Type = cmCPackIFWPackage::CompareLess;
    Compare.Value = dependence.substr(pos + 1);
  } else if ((pos = dependence.find('=')) != std::string::npos) {
    Compare.Type = cmCPackIFWPackage::CompareEqual;
    Compare.Value = dependence.substr(pos + 1);
  } else if ((pos = dependence.find('>')) != std::string::npos) {
    Compare.Type = cmCPackIFWPackage::CompareGreater;
    Compare.Value = dependence.substr(pos + 1);
  } else if ((pos = dependence.find('-')) != std::string::npos) {
    Compare.Type = cmCPackIFWPackage::CompareNone;
    Compare.Value = dependence.substr(pos + 1);
  }
  size_t dashPos = dependence.find('-');
  if (dashPos != std::string::npos) {
    pos = dashPos;
  }
  Name = dependence.substr(0, pos);
}

std::string cmCPackIFWPackage::DependenceStruct::NameWithCompare() const
{
  if (Compare.Type == cmCPackIFWPackage::CompareNone) {
    return Name;
  }

  std::string result = Name;

  if (Compare.Type != cmCPackIFWPackage::CompareNone ||
      !Compare.Value.empty()) {
    result += "-";
  }

  if (Compare.Type == cmCPackIFWPackage::CompareLessOrEqual) {
    result += "<=";
  } else if (Compare.Type == cmCPackIFWPackage::CompareGreaterOrEqual) {
    result += ">=";
  } else if (Compare.Type == cmCPackIFWPackage::CompareLess) {
    result += "<";
  } else if (Compare.Type == cmCPackIFWPackage::CompareEqual) {
    result += "=";
  } else if (Compare.Type == cmCPackIFWPackage::CompareGreater) {
    result += ">";
  }

  result += Compare.Value;

  return result;
}

//------------------------------------------------------ cmCPackIFWPackage ---
cmCPackIFWPackage::cmCPackIFWPackage()
  : Installer(nullptr)
{
}

std::string cmCPackIFWPackage::GetComponentName(cmCPackComponent* component)
{
  if (!component) {
    return "";
  }
  const char* option =
    GetOption("CPACK_IFW_COMPONENT_" +
              cmsys::SystemTools::UpperCase(component->Name) + "_NAME");
  return option ? option : component->Name;
}

void cmCPackIFWPackage::DefaultConfiguration()
{
  DisplayName.clear();
  Description.clear();
  Version.clear();
  ReleaseDate.clear();
  Script.clear();
  Licenses.clear();
  UserInterfaces.clear();
  Translations.clear();
  SortingPriority.clear();
  UpdateText.clear();
  Default.clear();
  Essential.clear();
  Virtual.clear();
  ForcedInstallation.clear();
  RequiresAdminRights.clear();
}

// Default configuration (all in one package)
int cmCPackIFWPackage::ConfigureFromOptions()
{
  // Restore default configuration
  DefaultConfiguration();

  // Name
  Name = Generator->GetRootPackageName();

  // Display name
  if (const char* option = GetOption("CPACK_PACKAGE_NAME")) {
    DisplayName[""] = option;
  } else {
    DisplayName[""] = "Your package";
  }

  // Description
  if (const char* option = GetOption("CPACK_PACKAGE_DESCRIPTION_SUMMARY")) {
    Description[""] = option;
  } else {
    Description[""] = "Your package description";
  }

  // Version
  if (const char* option = GetOption("CPACK_PACKAGE_VERSION")) {
    Version = option;
  } else {
    Version = "1.0.0";
  }

  ForcedInstallation = "true";

  return 1;
}

int cmCPackIFWPackage::ConfigureFromComponent(cmCPackComponent* component)
{
  if (!component) {
    return 0;
  }

  // Restore default configuration
  DefaultConfiguration();

  std::string prefix = "CPACK_IFW_COMPONENT_" +
    cmsys::SystemTools::UpperCase(component->Name) + "_";

  // Display name
  DisplayName[""] = component->DisplayName;

  // Description
  Description[""] = component->Description;

  // Version
  if (const char* optVERSION = GetOption(prefix + "VERSION")) {
    Version = optVERSION;
  } else if (const char* optPACKAGE_VERSION =
               GetOption("CPACK_PACKAGE_VERSION")) {
    Version = optPACKAGE_VERSION;
  } else {
    Version = "1.0.0";
  }

  // Script
  if (const char* option = GetOption(prefix + "SCRIPT")) {
    Script = option;
  }

  // User interfaces
  if (const char* option = GetOption(prefix + "USER_INTERFACES")) {
    UserInterfaces.clear();
    cmExpandList(option, UserInterfaces);
  }

  // CMake dependencies
  if (!component->Dependencies.empty()) {
    for (cmCPackComponent* dep : component->Dependencies) {
      Dependencies.insert(Generator->ComponentPackages[dep]);
    }
  }

  // Licenses
  if (const char* option = GetOption(prefix + "LICENSES")) {
    Licenses.clear();
    cmExpandList(option, Licenses);
    if (Licenses.size() % 2 != 0) {
      cmCPackIFWLogger(
        WARNING,
        prefix << "LICENSES"
               << " should contain pairs of <display_name> and <file_path>."
               << std::endl);
      Licenses.clear();
    }
  }

  // Priority
  if (const char* option = GetOption(prefix + "PRIORITY")) {
    SortingPriority = option;
    cmCPackIFWLogger(
      WARNING,
      "The \"PRIORITY\" option is set "
        << "for component \"" << component->Name << "\", but there option is "
        << "deprecated. Please use \"SORTING_PRIORITY\" option instead."
        << std::endl);
  }

  // Default
  Default = component->IsDisabledByDefault ? "false" : "true";

  // Essential
  if (IsOn(prefix + "ESSENTIAL")) {
    Essential = "true";
  }

  // Virtual
  Virtual = component->IsHidden ? "true" : "";

  // ForcedInstallation
  ForcedInstallation = component->IsRequired ? "true" : "false";

  return ConfigureFromPrefix(prefix);
}

int cmCPackIFWPackage::ConfigureFromGroup(cmCPackComponentGroup* group)
{
  if (!group) {
    return 0;
  }

  // Restore default configuration
  DefaultConfiguration();

  std::string prefix = "CPACK_IFW_COMPONENT_GROUP_" +
    cmsys::SystemTools::UpperCase(group->Name) + "_";

  DisplayName[""] = group->DisplayName;
  Description[""] = group->Description;

  // Version
  if (const char* optVERSION = GetOption(prefix + "VERSION")) {
    Version = optVERSION;
  } else if (const char* optPACKAGE_VERSION =
               GetOption("CPACK_PACKAGE_VERSION")) {
    Version = optPACKAGE_VERSION;
  } else {
    Version = "1.0.0";
  }

  // Script
  if (const char* option = GetOption(prefix + "SCRIPT")) {
    Script = option;
  }

  // User interfaces
  if (const char* option = GetOption(prefix + "USER_INTERFACES")) {
    UserInterfaces.clear();
    cmExpandList(option, UserInterfaces);
  }

  // Licenses
  if (const char* option = GetOption(prefix + "LICENSES")) {
    Licenses.clear();
    cmExpandList(option, Licenses);
    if (Licenses.size() % 2 != 0) {
      cmCPackIFWLogger(
        WARNING,
        prefix << "LICENSES"
               << " should contain pairs of <display_name> and <file_path>."
               << std::endl);
      Licenses.clear();
    }
  }

  // Priority
  if (const char* option = GetOption(prefix + "PRIORITY")) {
    SortingPriority = option;
    cmCPackIFWLogger(
      WARNING,
      "The \"PRIORITY\" option is set "
        << "for component group \"" << group->Name
        << "\", but there option is "
        << "deprecated. Please use \"SORTING_PRIORITY\" option instead."
        << std::endl);
  }

  return ConfigureFromPrefix(prefix);
}

int cmCPackIFWPackage::ConfigureFromGroup(const std::string& groupName)
{
  // Group configuration

  cmCPackComponentGroup group;
  std::string prefix =
    "CPACK_COMPONENT_GROUP_" + cmsys::SystemTools::UpperCase(groupName) + "_";

  if (const char* option = GetOption(prefix + "DISPLAY_NAME")) {
    group.DisplayName = option;
  } else {
    group.DisplayName = group.Name;
  }

  if (const char* option = GetOption(prefix + "DESCRIPTION")) {
    group.Description = option;
  }
  group.IsBold = IsOn(prefix + "BOLD_TITLE");
  group.IsExpandedByDefault = IsOn(prefix + "EXPANDED");

  // Package configuration

  group.Name = groupName;

  if (Generator) {
    Name = Generator->GetGroupPackageName(&group);
  } else {
    Name = group.Name;
  }

  return ConfigureFromGroup(&group);
}

// Common options for components and groups
int cmCPackIFWPackage::ConfigureFromPrefix(const std::string& prefix)
{
  // Temporary variable for full option name
  std::string option;

  // Display name
  option = prefix + "DISPLAY_NAME";
  if (IsSetToEmpty(option)) {
    DisplayName.clear();
  } else if (const char* value = GetOption(option)) {
    cmCPackIFWPackage::ExpandListArgument(value, DisplayName);
  }

  // Description
  option = prefix + "DESCRIPTION";
  if (IsSetToEmpty(option)) {
    Description.clear();
  } else if (const char* value = GetOption(option)) {
    cmCPackIFWPackage::ExpandListArgument(value, Description);
  }

  // Release date
  option = prefix + "RELEASE_DATE";
  if (IsSetToEmpty(option)) {
    ReleaseDate.clear();
  } else if (const char* value = GetOption(option)) {
    ReleaseDate = value;
  }

  // Sorting priority
  option = prefix + "SORTING_PRIORITY";
  if (IsSetToEmpty(option)) {
    SortingPriority.clear();
  } else if (const char* value = GetOption(option)) {
    SortingPriority = value;
  }

  // Update text
  option = prefix + "UPDATE_TEXT";
  if (IsSetToEmpty(option)) {
    UpdateText.clear();
  } else if (const char* value = GetOption(option)) {
    UpdateText = value;
  }

  // Translations
  option = prefix + "TRANSLATIONS";
  if (IsSetToEmpty(option)) {
    Translations.clear();
  } else if (const char* value = GetOption(option)) {
    Translations.clear();
    cmExpandList(value, Translations);
  }

  // QtIFW dependencies
  std::vector<std::string> deps;
  option = prefix + "DEPENDS";
  if (const char* value = GetOption(option)) {
    cmExpandList(value, deps);
  }
  option = prefix + "DEPENDENCIES";
  if (const char* value = GetOption(option)) {
    cmExpandList(value, deps);
  }
  for (std::string const& d : deps) {
    DependenceStruct dep(d);
    if (Generator->Packages.count(dep.Name)) {
      cmCPackIFWPackage& depPkg = Generator->Packages[dep.Name];
      dep.Name = depPkg.Name;
    }
    bool hasDep = Generator->DependentPackages.count(dep.Name) > 0;
    DependenceStruct& depRef = Generator->DependentPackages[dep.Name];
    if (!hasDep) {
      depRef = dep;
    }
    AlienDependencies.insert(&depRef);
  }

  // Automatic dependency on
  option = prefix + "AUTO_DEPEND_ON";
  if (IsSetToEmpty(option)) {
    AlienAutoDependOn.clear();
  } else if (const char* value = GetOption(option)) {
    std::vector<std::string> depsOn = cmExpandedList(value);
    for (std::string const& d : depsOn) {
      DependenceStruct dep(d);
      if (Generator->Packages.count(dep.Name)) {
        cmCPackIFWPackage& depPkg = Generator->Packages[dep.Name];
        dep.Name = depPkg.Name;
      }
      bool hasDep = Generator->DependentPackages.count(dep.Name) > 0;
      DependenceStruct& depRef = Generator->DependentPackages[dep.Name];
      if (!hasDep) {
        depRef = dep;
      }
      AlienAutoDependOn.insert(&depRef);
    }
  }

  // Visibility
  option = prefix + "VIRTUAL";
  if (IsSetToEmpty(option)) {
    Virtual.clear();
  } else if (IsOn(option)) {
    Virtual = "true";
  }

  // Default selection
  option = prefix + "DEFAULT";
  if (IsSetToEmpty(option)) {
    Default.clear();
  } else if (const char* value = GetOption(option)) {
    std::string lowerValue = cmsys::SystemTools::LowerCase(value);
    if (lowerValue == "true") {
      Default = "true";
    } else if (lowerValue == "false") {
      Default = "false";
    } else if (lowerValue == "script") {
      Default = "script";
    } else {
      Default = value;
    }
  }

  // Forsed installation
  option = prefix + "FORCED_INSTALLATION";
  if (IsSetToEmpty(option)) {
    ForcedInstallation.clear();
  } else if (IsOn(option)) {
    ForcedInstallation = "true";
  } else if (IsSetToOff(option)) {
    ForcedInstallation = "false";
  }

  // Replaces
  option = prefix + "REPLACES";
  if (IsSetToEmpty(option)) {
    Replaces.clear();
  } else if (const char* value = GetOption(option)) {
    Replaces.clear();
    cmExpandList(value, Replaces);
  }

  // Requires admin rights
  option = prefix + "REQUIRES_ADMIN_RIGHTS";
  if (IsSetToEmpty(option)) {
    RequiresAdminRights.clear();
  } else if (IsOn(option)) {
    RequiresAdminRights = "true";
  } else if (IsSetToOff(option)) {
    RequiresAdminRights = "false";
  }

  // Checkable
  option = prefix + "CHECKABLE";
  if (IsSetToEmpty(option)) {
    Checkable.clear();
  } else if (IsOn(option)) {
    Checkable = "true";
  } else if (IsSetToOff(option)) {
    Checkable = "false";
  }

  return 1;
}

void cmCPackIFWPackage::GeneratePackageFile()
{
  // Lazy directory initialization
  if (Directory.empty()) {
    if (Installer) {
      Directory = Installer->Directory + "/packages/" + Name;
    } else if (Generator) {
      Directory = Generator->toplevel + "/packages/" + Name;
    }
  }

  // Output stream
  cmGeneratedFileStream fout(Directory + "/meta/package.xml");
  cmXMLWriter xout(fout);

  xout.StartDocument();

  WriteGeneratedByToStrim(xout);

  xout.StartElement("Package");

  // DisplayName (with translations)
  for (auto const& dn : DisplayName) {
    xout.StartElement("DisplayName");
    if (!dn.first.empty()) {
      xout.Attribute("xml:lang", dn.first);
    }
    xout.Content(dn.second);
    xout.EndElement();
  }

  // Description (with translations)
  for (auto const& d : Description) {
    xout.StartElement("Description");
    if (!d.first.empty()) {
      xout.Attribute("xml:lang", d.first);
    }
    xout.Content(d.second);
    xout.EndElement();
  }

  // Update text
  if (!UpdateText.empty()) {
    xout.Element("UpdateText", UpdateText);
  }

  xout.Element("Name", Name);
  xout.Element("Version", Version);

  if (!ReleaseDate.empty()) {
    xout.Element("ReleaseDate", ReleaseDate);
  } else {
    xout.Element("ReleaseDate", cmTimestamp().CurrentTime("%Y-%m-%d", true));
  }

  // Script (copy to meta dir)
  if (!Script.empty()) {
    std::string name = cmSystemTools::GetFilenameName(Script);
    std::string path = Directory + "/meta/" + name;
    cmsys::SystemTools::CopyFileIfDifferent(Script, path);
    xout.Element("Script", name);
  }

  // User Interfaces (copy to meta dir)
  std::vector<std::string> userInterfaces = UserInterfaces;
  for (std::string& userInterface : userInterfaces) {
    std::string name = cmSystemTools::GetFilenameName(userInterface);
    std::string path = Directory + "/meta/" + name;
    cmsys::SystemTools::CopyFileIfDifferent(userInterface, path);
    userInterface = name;
  }
  if (!userInterfaces.empty()) {
    xout.StartElement("UserInterfaces");
    for (std::string const& userInterface : userInterfaces) {
      xout.Element("UserInterface", userInterface);
    }
    xout.EndElement();
  }

  // Translations (copy to meta dir)
  std::vector<std::string> translations = Translations;
  for (std::string& translation : translations) {
    std::string name = cmSystemTools::GetFilenameName(translation);
    std::string path = Directory + "/meta/" + name;
    cmsys::SystemTools::CopyFileIfDifferent(translation, path);
    translation = name;
  }
  if (!translations.empty()) {
    xout.StartElement("Translations");
    for (std::string const& translation : translations) {
      xout.Element("Translation", translation);
    }
    xout.EndElement();
  }

  // Dependencies
  std::set<DependenceStruct> compDepSet;
  for (DependenceStruct* ad : AlienDependencies) {
    compDepSet.insert(*ad);
  }
  for (cmCPackIFWPackage* d : Dependencies) {
    compDepSet.insert(DependenceStruct(d->Name));
  }
  // Write dependencies
  if (!compDepSet.empty()) {
    std::ostringstream dependencies;
    auto it = compDepSet.begin();
    dependencies << it->NameWithCompare();
    ++it;
    while (it != compDepSet.end()) {
      dependencies << "," << it->NameWithCompare();
      ++it;
    }
    xout.Element("Dependencies", dependencies.str());
  }

  // Automatic dependency on
  std::set<DependenceStruct> compAutoDepSet;
  for (DependenceStruct* aad : AlienAutoDependOn) {
    compAutoDepSet.insert(*aad);
  }
  // Write automatic dependency on
  if (!compAutoDepSet.empty()) {
    std::ostringstream dependencies;
    auto it = compAutoDepSet.begin();
    dependencies << it->NameWithCompare();
    ++it;
    while (it != compAutoDepSet.end()) {
      dependencies << "," << it->NameWithCompare();
      ++it;
    }
    xout.Element("AutoDependOn", dependencies.str());
  }

  // Licenses (copy to meta dir)
  std::vector<std::string> licenses = Licenses;
  for (size_t i = 1; i < licenses.size(); i += 2) {
    std::string name = cmSystemTools::GetFilenameName(licenses[i]);
    std::string path = Directory + "/meta/" + name;
    cmsys::SystemTools::CopyFileIfDifferent(licenses[i], path);
    licenses[i] = name;
  }
  if (!licenses.empty()) {
    xout.StartElement("Licenses");
    for (size_t i = 0; i < licenses.size(); i += 2) {
      xout.StartElement("License");
      xout.Attribute("name", licenses[i]);
      xout.Attribute("file", licenses[i + 1]);
      xout.EndElement();
    }
    xout.EndElement();
  }

  if (!ForcedInstallation.empty()) {
    xout.Element("ForcedInstallation", ForcedInstallation);
  }

  // Replaces
  if (!Replaces.empty()) {
    std::ostringstream replaces;
    auto it = Replaces.begin();
    replaces << *it;
    ++it;
    while (it != Replaces.end()) {
      replaces << "," << *it;
      ++it;
    }
    xout.Element("Replaces", replaces.str());
  }

  if (!RequiresAdminRights.empty()) {
    xout.Element("RequiresAdminRights", RequiresAdminRights);
  }

  if (!Virtual.empty()) {
    xout.Element("Virtual", Virtual);
  } else if (!Default.empty()) {
    xout.Element("Default", Default);
  }

  // Essential
  if (!Essential.empty()) {
    xout.Element("Essential", Essential);
  }

  // Priority
  if (!SortingPriority.empty()) {
    xout.Element("SortingPriority", SortingPriority);
  }

  // Checkable
  if (!Checkable.empty()) {
    xout.Element("Checkable", Checkable);
  }

  xout.EndElement();
  xout.EndDocument();
}
