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

#include "CPack/cmCPackComponentGroup.h"
#include "CPack/cmCPackGenerator.h"
#include "CPack/cmCPackLog.h"
#include "cmCPackIFWInstaller.h"
#include "cmCPackIFWPackage.h"
#include "cmCPackIFWRepository.h"
#include "cmGeneratedFileStream.h"
#include "cmSystemTools.h"
#include "cmTimestamp.h"
#include "cmVersionConfig.h"
#include "cmXMLWriter.h"

#include <sstream>
#include <utility>

cmCPackIFWGenerator::cmCPackIFWGenerator()
{
}

cmCPackIFWGenerator::~cmCPackIFWGenerator()
{
}

bool cmCPackIFWGenerator::IsVersionLess(const char* version)
{
  return cmSystemTools::VersionCompare(cmSystemTools::OP_LESS,
                                       FrameworkVersion.data(), version);
}

bool cmCPackIFWGenerator::IsVersionGreater(const char* version)
{
  return cmSystemTools::VersionCompare(cmSystemTools::OP_GREATER,
                                       FrameworkVersion.data(), version);
}

bool cmCPackIFWGenerator::IsVersionEqual(const char* version)
{
  return cmSystemTools::VersionCompare(cmSystemTools::OP_EQUAL,
                                       FrameworkVersion.data(), version);
}

int cmCPackIFWGenerator::PackageFiles()
{
  cmCPackLogger(cmCPackLog::LOG_OUTPUT, "- Configuration" << std::endl);

  // Installer configuragion
  Installer.GenerateInstallerFile();

  // Packages configuration
  Installer.GeneratePackageFiles();

  std::string ifwTLD = this->GetOption("CPACK_TOPLEVEL_DIRECTORY");
  std::string ifwTmpFile = ifwTLD;
  ifwTmpFile += "/IFWOutput.log";

  // Run repogen
  if (!Installer.RemoteRepositories.empty()) {
    std::string ifwCmd = RepoGen;

    if (IsVersionLess("2.0.0")) {
      ifwCmd += " -c " + this->toplevel + "/config/config.xml";
    }

    ifwCmd += " -p " + this->toplevel + "/packages";

    if (!PkgsDirsVector.empty()) {
      for (std::vector<std::string>::iterator it = PkgsDirsVector.begin();
           it != PkgsDirsVector.end(); ++it) {
        ifwCmd += " -p " + *it;
      }
    }

    if (!OnlineOnly && !DownloadedPackages.empty()) {
      ifwCmd += " -i ";
      std::set<cmCPackIFWPackage*>::iterator it = DownloadedPackages.begin();
      ifwCmd += (*it)->Name;
      ++it;
      while (it != DownloadedPackages.end()) {
        ifwCmd += "," + (*it)->Name;
        ++it;
      }
    }
    ifwCmd += " " + this->toplevel + "/repository";
    cmCPackLogger(cmCPackLog::LOG_VERBOSE, "Execute: " << ifwCmd << std::endl);
    std::string output;
    int retVal = 1;
    cmCPackLogger(cmCPackLog::LOG_OUTPUT, "- Generate repository"
                    << std::endl);
    bool res = cmSystemTools::RunSingleCommand(ifwCmd.c_str(), &output,
                                               &output, &retVal, CM_NULLPTR,
                                               this->GeneratorVerbose, 0);
    if (!res || retVal) {
      cmGeneratedFileStream ofs(ifwTmpFile.c_str());
      ofs << "# Run command: " << ifwCmd << std::endl
          << "# Output:" << std::endl
          << output << std::endl;
      cmCPackLogger(cmCPackLog::LOG_ERROR, "Problem running IFW command: "
                      << ifwCmd << std::endl
                      << "Please check " << ifwTmpFile << " for errors"
                      << std::endl);
      return 0;
    }

    if (!Repository.RepositoryUpdate.empty() &&
        !Repository.PatchUpdatesXml()) {
      cmCPackLogger(cmCPackLog::LOG_WARNING, "Problem patch IFW \"Updates\" "
                      << "file: " << this->toplevel + "/repository/Updates.xml"
                      << std::endl);
    }

    cmCPackLogger(cmCPackLog::LOG_OUTPUT, "- repository: "
                    << this->toplevel << "/repository generated" << std::endl);
  }

  // Run binary creator
  {
    std::string ifwCmd = BinCreator;
    ifwCmd += " -c " + this->toplevel + "/config/config.xml";

    if (!Installer.Resources.empty()) {
      ifwCmd += " -r ";
      std::vector<std::string>::iterator it = Installer.Resources.begin();
      std::string path = this->toplevel + "/resources/";
      ifwCmd += path + *it;
      ++it;
      while (it != Installer.Resources.end()) {
        ifwCmd += "," + path + *it;
        ++it;
      }
    }

    ifwCmd += " -p " + this->toplevel + "/packages";

    if (!PkgsDirsVector.empty()) {
      for (std::vector<std::string>::iterator it = PkgsDirsVector.begin();
           it != PkgsDirsVector.end(); ++it) {
        ifwCmd += " -p " + *it;
      }
    }

    if (OnlineOnly) {
      ifwCmd += " --online-only";
    } else if (!DownloadedPackages.empty() &&
               !Installer.RemoteRepositories.empty()) {
      ifwCmd += " -e ";
      std::set<cmCPackIFWPackage*>::iterator it = DownloadedPackages.begin();
      ifwCmd += (*it)->Name;
      ++it;
      while (it != DownloadedPackages.end()) {
        ifwCmd += "," + (*it)->Name;
        ++it;
      }
    } else if (!DependentPackages.empty()) {
      ifwCmd += " -i ";
      // Binary
      std::set<cmCPackIFWPackage*>::iterator bit = BinaryPackages.begin();
      while (bit != BinaryPackages.end()) {
        ifwCmd += (*bit)->Name + ",";
        ++bit;
      }
      // Depend
      DependenceMap::iterator it = DependentPackages.begin();
      ifwCmd += it->second.Name;
      ++it;
      while (it != DependentPackages.end()) {
        ifwCmd += "," + it->second.Name;
        ++it;
      }
    }
    // TODO: set correct name for multipackages
    if (!this->packageFileNames.empty()) {
      ifwCmd += " " + packageFileNames[0];
    } else {
      ifwCmd += " installer";
    }
    cmCPackLogger(cmCPackLog::LOG_VERBOSE, "Execute: " << ifwCmd << std::endl);
    std::string output;
    int retVal = 1;
    cmCPackLogger(cmCPackLog::LOG_OUTPUT, "- Generate package" << std::endl);
    bool res = cmSystemTools::RunSingleCommand(ifwCmd.c_str(), &output,
                                               &output, &retVal, CM_NULLPTR,
                                               this->GeneratorVerbose, 0);
    if (!res || retVal) {
      cmGeneratedFileStream ofs(ifwTmpFile.c_str());
      ofs << "# Run command: " << ifwCmd << std::endl
          << "# Output:" << std::endl
          << output << std::endl;
      cmCPackLogger(cmCPackLog::LOG_ERROR, "Problem running IFW command: "
                      << ifwCmd << std::endl
                      << "Please check " << ifwTmpFile << " for errors"
                      << std::endl);
      return 0;
    }
  }

  return 1;
}

const char* cmCPackIFWGenerator::GetPackagingInstallPrefix()
{
  const char* defPrefix = cmCPackGenerator::GetPackagingInstallPrefix();

  std::string tmpPref = defPrefix ? defPrefix : "";

  if (this->Components.empty()) {
    tmpPref += "packages/" + GetRootPackageName() + "/data";
  }

  this->SetOption("CPACK_IFW_PACKAGING_INSTALL_PREFIX", tmpPref.c_str());

  return this->GetOption("CPACK_IFW_PACKAGING_INSTALL_PREFIX");
}

const char* cmCPackIFWGenerator::GetOutputExtension()
{
  return ExecutableSuffix.c_str();
}

int cmCPackIFWGenerator::InitializeInternal()
{
  // Search Qt Installer Framework tools

  const std::string BinCreatorOpt = "CPACK_IFW_BINARYCREATOR_EXECUTABLE";
  const std::string RepoGenOpt = "CPACK_IFW_REPOGEN_EXECUTABLE";
  const std::string FrameworkVersionOpt = "CPACK_IFW_FRAMEWORK_VERSION";

  if (!this->IsSet(BinCreatorOpt) || !this->IsSet(RepoGenOpt) ||
      !this->IsSet(FrameworkVersionOpt)) {
    this->ReadListFile("CPackIFW.cmake");
  }

  // Look 'binarycreator' executable (needs)

  const char* BinCreatorStr = this->GetOption(BinCreatorOpt);
  if (!BinCreatorStr || cmSystemTools::IsNOTFOUND(BinCreatorStr)) {
    BinCreator = "";
  } else {
    BinCreator = BinCreatorStr;
  }

  if (BinCreator.empty()) {
    cmCPackLogger(cmCPackLog::LOG_ERROR,
                  "Cannot find QtIFW compiler \"binarycreator\": "
                  "likely it is not installed, or not in your PATH"
                    << std::endl);
    return 0;
  }

  // Look 'repogen' executable (optional)

  const char* RepoGenStr = this->GetOption(RepoGenOpt);
  if (!RepoGenStr || cmSystemTools::IsNOTFOUND(RepoGenStr)) {
    RepoGen = "";
  } else {
    RepoGen = RepoGenStr;
  }

  // Framework version
  if (const char* FrameworkVersionSrt = this->GetOption(FrameworkVersionOpt)) {
    FrameworkVersion = FrameworkVersionSrt;
  } else {
    FrameworkVersion = "1.9.9";
  }

  // Variables that Change Behavior

  // Resolve duplicate names
  ResolveDuplicateNames = this->IsOn("CPACK_IFW_RESOLVE_DUPLICATE_NAMES");

  // Additional packages dirs
  PkgsDirsVector.clear();
  if (const char* dirs = this->GetOption("CPACK_IFW_PACKAGES_DIRECTORIES")) {
    cmSystemTools::ExpandListArgument(dirs, PkgsDirsVector);
  }

  // Installer
  Installer.Generator = this;
  Installer.ConfigureFromOptions();

  // Repository
  Repository.Generator = this;
  Repository.Name = "Unspecified";
  if (const char* site = this->GetOption("CPACK_DOWNLOAD_SITE")) {
    Repository.Url = site;
    Installer.RemoteRepositories.push_back(&Repository);
  }

  // Repositories
  if (const char* RepoAllStr = this->GetOption("CPACK_IFW_REPOSITORIES_ALL")) {
    std::vector<std::string> RepoAllVector;
    cmSystemTools::ExpandListArgument(RepoAllStr, RepoAllVector);
    for (std::vector<std::string>::iterator rit = RepoAllVector.begin();
         rit != RepoAllVector.end(); ++rit) {
      GetRepository(*rit);
    }
  }

  if (const char* ifwDownloadAll = this->GetOption("CPACK_IFW_DOWNLOAD_ALL")) {
    OnlineOnly = cmSystemTools::IsOn(ifwDownloadAll);
  } else if (const char* cpackDownloadAll =
               this->GetOption("CPACK_DOWNLOAD_ALL")) {
    OnlineOnly = cmSystemTools::IsOn(cpackDownloadAll);
  } else {
    OnlineOnly = false;
  }

  if (!Installer.RemoteRepositories.empty() && RepoGen.empty()) {
    cmCPackLogger(cmCPackLog::LOG_ERROR,
                  "Cannot find QtIFW repository generator \"repogen\": "
                  "likely it is not installed, or not in your PATH"
                    << std::endl);
    return 0;
  }

  // Executable suffix
  if (const char* optExeSuffix = this->GetOption("CMAKE_EXECUTABLE_SUFFIX")) {
    ExecutableSuffix = optExeSuffix;
    if (ExecutableSuffix.empty()) {
      std::string sysName(this->GetOption("CMAKE_SYSTEM_NAME"));
      if (sysName == "Linux") {
        ExecutableSuffix = ".run";
      }
    }
  } else {
    ExecutableSuffix = cmCPackGenerator::GetOutputExtension();
  }

  return this->Superclass::InitializeInternal();
}

std::string cmCPackIFWGenerator::GetComponentInstallDirNameSuffix(
  const std::string& componentName)
{
  const std::string prefix = "packages/";
  const std::string suffix = "/data";

  if (componentPackageMethod == ONE_PACKAGE) {
    return std::string(prefix + GetRootPackageName() + suffix);
  }

  return prefix + GetComponentPackageName(&Components[componentName]) + suffix;
}

cmCPackComponent* cmCPackIFWGenerator::GetComponent(
  const std::string& projectName, const std::string& componentName)
{
  ComponentsMap::iterator cit = Components.find(componentName);
  if (cit != Components.end()) {
    return &(cit->second);
  }

  cmCPackComponent* component =
    cmCPackGenerator::GetComponent(projectName, componentName);
  if (!component) {
    return component;
  }

  std::string name = GetComponentPackageName(component);
  PackagesMap::iterator pit = Packages.find(name);
  if (pit != Packages.end()) {
    return component;
  }

  cmCPackIFWPackage* package = &Packages[name];
  package->Name = name;
  package->Generator = this;
  if (package->ConfigureFromComponent(component)) {
    package->Installer = &Installer;
    Installer.Packages.insert(
      std::pair<std::string, cmCPackIFWPackage*>(name, package));
    ComponentPackages.insert(
      std::pair<cmCPackComponent*, cmCPackIFWPackage*>(component, package));
    if (component->IsDownloaded) {
      DownloadedPackages.insert(package);
    } else {
      BinaryPackages.insert(package);
    }
  } else {
    Packages.erase(name);
    cmCPackLogger(cmCPackLog::LOG_ERROR, "Cannot configure package \""
                    << name << "\" for component \"" << component->Name << "\""
                    << std::endl);
  }

  return component;
}

cmCPackComponentGroup* cmCPackIFWGenerator::GetComponentGroup(
  const std::string& projectName, const std::string& groupName)
{
  cmCPackComponentGroup* group =
    cmCPackGenerator::GetComponentGroup(projectName, groupName);
  if (!group) {
    return group;
  }

  std::string name = GetGroupPackageName(group);
  PackagesMap::iterator pit = Packages.find(name);
  if (pit != Packages.end()) {
    return group;
  }

  cmCPackIFWPackage* package = &Packages[name];
  package->Name = name;
  package->Generator = this;
  if (package->ConfigureFromGroup(group)) {
    package->Installer = &Installer;
    Installer.Packages.insert(
      std::pair<std::string, cmCPackIFWPackage*>(name, package));
    GroupPackages.insert(
      std::pair<cmCPackComponentGroup*, cmCPackIFWPackage*>(group, package));
    BinaryPackages.insert(package);
  } else {
    Packages.erase(name);
    cmCPackLogger(cmCPackLog::LOG_ERROR, "Cannot configure package \""
                    << name << "\" for component group \"" << group->Name
                    << "\"" << std::endl);
  }
  return group;
}

enum cmCPackGenerator::CPackSetDestdirSupport
cmCPackIFWGenerator::SupportsSetDestdir() const
{
  return cmCPackGenerator::SETDESTDIR_SHOULD_NOT_BE_USED;
}

bool cmCPackIFWGenerator::SupportsAbsoluteDestination() const
{
  return false;
}

bool cmCPackIFWGenerator::SupportsComponentInstallation() const
{
  return true;
}

bool cmCPackIFWGenerator::IsOnePackage() const
{
  return componentPackageMethod == ONE_PACKAGE;
}

std::string cmCPackIFWGenerator::GetRootPackageName()
{
  // Default value
  std::string name = "root";
  if (const char* optIFW_PACKAGE_GROUP =
        this->GetOption("CPACK_IFW_PACKAGE_GROUP")) {
    // Configure from root group
    cmCPackIFWPackage package;
    package.Generator = this;
    package.ConfigureFromGroup(optIFW_PACKAGE_GROUP);
    name = package.Name;
  } else if (const char* optIFW_PACKAGE_NAME =
               this->GetOption("CPACK_IFW_PACKAGE_NAME")) {
    // Configure from root package name
    name = optIFW_PACKAGE_NAME;
  } else if (const char* optPACKAGE_NAME =
               this->GetOption("CPACK_PACKAGE_NAME")) {
    // Configure from package name
    name = optPACKAGE_NAME;
  }
  return name;
}

std::string cmCPackIFWGenerator::GetGroupPackageName(
  cmCPackComponentGroup* group) const
{
  std::string name;
  if (!group) {
    return name;
  }
  if (cmCPackIFWPackage* package = GetGroupPackage(group)) {
    return package->Name;
  }
  const char* option =
    GetOption("CPACK_IFW_COMPONENT_GROUP_" +
              cmsys::SystemTools::UpperCase(group->Name) + "_NAME");
  name = option ? option : group->Name;
  if (group->ParentGroup) {
    cmCPackIFWPackage* package = GetGroupPackage(group->ParentGroup);
    bool dot = !ResolveDuplicateNames;
    if (dot && name.substr(0, package->Name.size()) == package->Name) {
      dot = false;
    }
    if (dot) {
      name = package->Name + "." + name;
    }
  }
  return name;
}

std::string cmCPackIFWGenerator::GetComponentPackageName(
  cmCPackComponent* component) const
{
  std::string name;
  if (!component) {
    return name;
  }
  if (cmCPackIFWPackage* package = GetComponentPackage(component)) {
    return package->Name;
  }
  std::string prefix = "CPACK_IFW_COMPONENT_" +
    cmsys::SystemTools::UpperCase(component->Name) + "_";
  const char* option = GetOption(prefix + "NAME");
  name = option ? option : component->Name;
  if (component->Group) {
    cmCPackIFWPackage* package = GetGroupPackage(component->Group);
    if ((componentPackageMethod == ONE_PACKAGE_PER_GROUP) ||
        IsOn(prefix + "COMMON")) {
      return package->Name;
    }
    bool dot = !ResolveDuplicateNames;
    if (dot && name.substr(0, package->Name.size()) == package->Name) {
      dot = false;
    }
    if (dot) {
      name = package->Name + "." + name;
    }
  }
  return name;
}

cmCPackIFWPackage* cmCPackIFWGenerator::GetGroupPackage(
  cmCPackComponentGroup* group) const
{
  std::map<cmCPackComponentGroup*, cmCPackIFWPackage*>::const_iterator pit =
    GroupPackages.find(group);
  return pit != GroupPackages.end() ? pit->second : CM_NULLPTR;
}

cmCPackIFWPackage* cmCPackIFWGenerator::GetComponentPackage(
  cmCPackComponent* component) const
{
  std::map<cmCPackComponent*, cmCPackIFWPackage*>::const_iterator pit =
    ComponentPackages.find(component);
  return pit != ComponentPackages.end() ? pit->second : CM_NULLPTR;
}

cmCPackIFWRepository* cmCPackIFWGenerator::GetRepository(
  const std::string& repositoryName)
{
  RepositoriesMap::iterator rit = Repositories.find(repositoryName);
  if (rit != Repositories.end()) {
    return &(rit->second);
  }

  cmCPackIFWRepository* repository = &Repositories[repositoryName];
  repository->Name = repositoryName;
  repository->Generator = this;
  if (repository->ConfigureFromOptions()) {
    if (repository->Update == cmCPackIFWRepository::None) {
      Installer.RemoteRepositories.push_back(repository);
    } else {
      Repository.RepositoryUpdate.push_back(repository);
    }
  } else {
    Repositories.erase(repositoryName);
    repository = CM_NULLPTR;
    cmCPackLogger(cmCPackLog::LOG_WARNING, "Invalid repository \""
                    << repositoryName << "\""
                    << " configuration. Repository will be skipped."
                    << std::endl);
  }
  return repository;
}

void cmCPackIFWGenerator::WriteGeneratedByToStrim(cmXMLWriter& xout)
{
  std::ostringstream comment;
  comment << "Generated by CPack " << CMake_VERSION << " IFW generator "
          << "for QtIFW ";
  if (IsVersionLess("2.0")) {
    comment << "less 2.0";
  } else {
    comment << FrameworkVersion;
  }
  comment << " tools at " << cmTimestamp().CurrentTime("", true);
  xout.Comment(comment.str().c_str());
}