/* 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 "cmCPackComponentGroup.h"
#include "cmCPackGenerator.h"
#include "cmCPackIFWCommon.h"
#include "cmCPackIFWInstaller.h"
#include "cmCPackIFWPackage.h"
#include "cmCPackIFWRepository.h"
#include "cmCPackLog.h"  // IWYU pragma: keep
#include "cmDuration.h"
#include "cmGeneratedFileStream.h"
#include "cmSystemTools.h"

#include <sstream>
#include <utility>

cmCPackIFWGenerator::cmCPackIFWGenerator() { this->Generator = this; }

cmCPackIFWGenerator::~cmCPackIFWGenerator() {}

int
cmCPackIFWGenerator::PackageFiles()
{
    cmCPackIFWLogger(OUTPUT, "- Configuration" << std::endl);

    // Installer configuragion
    this->Installer.GenerateInstallerFile();

    // Packages configuration
    this->Installer.GeneratePackageFiles();

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

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

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

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

        if(!this->PkgsDirsVector.empty())
        {
            for(std::string const& it : this->PkgsDirsVector)
            {
                ifwCmd += " -p " + it;
            }
        }

        if(!this->RepoDirsVector.empty())
        {
            if(!this->IsVersionLess("3.1"))
            {
                for(std::string const& rd : this->RepoDirsVector)
                {
                    ifwCmd += " --repository " + rd;
                }
            } else
            {
                cmCPackIFWLogger(
                    WARNING,
                    "The \"CPACK_IFW_REPOSITORIES_DIRECTORIES\" "
                        << "variable is set, but content will be skipped, "
                        << "because this feature available only since "
                        << "QtIFW 3.1. Please update your QtIFW instance."
                        << std::endl);
            }
        }

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

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

        cmCPackIFWLogger(OUTPUT, "- repository: " << this->toplevel
                                                  << "/repository generated"
                                                  << std::endl);
    }

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

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

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

        if(!this->PkgsDirsVector.empty())
        {
            for(std::string const& it : this->PkgsDirsVector)
            {
                ifwCmd += " -p " + it;
            }
        }

        if(!this->RepoDirsVector.empty())
        {
            if(!this->IsVersionLess("3.1"))
            {
                for(std::string const& rd : this->RepoDirsVector)
                {
                    ifwCmd += " --repository " + rd;
                }
            } else
            {
                cmCPackIFWLogger(
                    WARNING,
                    "The \"CPACK_IFW_REPOSITORIES_DIRECTORIES\" "
                        << "variable is set, but content will be skipped, "
                        << "because this feature available only since "
                        << "QtIFW 3.1. Please update your QtIFW instance."
                        << std::endl);
            }
        }

        if(this->OnlineOnly)
        {
            ifwCmd += " --online-only";
        } else if(!this->DownloadedPackages.empty() &&
                  !this->Installer.RemoteRepositories.empty())
        {
            ifwCmd += " -e ";
            std::set<cmCPackIFWPackage*>::iterator it =
                this->DownloadedPackages.begin();
            ifwCmd += (*it)->Name;
            ++it;
            while(it != this->DownloadedPackages.end())
            {
                ifwCmd += "," + (*it)->Name;
                ++it;
            }
        } else if(!this->DependentPackages.empty())
        {
            ifwCmd += " -i ";
            // Binary
            std::set<cmCPackIFWPackage*>::iterator bit =
                this->BinaryPackages.begin();
            while(bit != this->BinaryPackages.end())
            {
                ifwCmd += (*bit)->Name + ",";
                ++bit;
            }
            // Depend
            DependenceMap::iterator it = this->DependentPackages.begin();
            ifwCmd += it->second.Name;
            ++it;
            while(it != this->DependentPackages.end())
            {
                ifwCmd += "," + it->second.Name;
                ++it;
            }
        }
        // TODO: set correct name for multipackages
        if(!this->packageFileNames.empty())
        {
            ifwCmd += " " + this->packageFileNames[0];
        } else
        {
            ifwCmd += " installer";
            ifwCmd += this->OutputExtension;
        }
        cmCPackIFWLogger(VERBOSE, "Execute: " << ifwCmd << std::endl);
        std::string output;
        int         retVal = 1;
        cmCPackIFWLogger(OUTPUT, "- Generate package" << std::endl);
        bool res = cmSystemTools::RunSingleCommand(
            ifwCmd.c_str(), &output, &output, &retVal, nullptr,
            this->GeneratorVerbose, cmDuration::zero());
        if(!res || retVal)
        {
            cmGeneratedFileStream ofs(ifwTmpFile);
            ofs << "# Run command: " << ifwCmd << std::endl
                << "# Output:" << std::endl
                << output << std::endl;
            cmCPackIFWLogger(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 = this->cmCPackGenerator::GetPackagingInstallPrefix();

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

    if(this->Components.empty())
    {
        tmpPref += "packages/" + this->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 this->OutputExtension.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))
    {
        this->BinCreator.clear();
    } else
    {
        this->BinCreator = BinCreatorStr;
    }

    if(this->BinCreator.empty())
    {
        cmCPackIFWLogger(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))
    {
        this->RepoGen.clear();
    } else
    {
        this->RepoGen = RepoGenStr;
    }

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

    // Variables that Change Behavior

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

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

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

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

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

    // Repositories
    if(const char* RepoAllStr = this->GetOption("CPACK_IFW_REPOSITORIES_ALL"))
    {
        std::vector<std::string> RepoAllVector;
        cmSystemTools::ExpandListArgument(RepoAllStr, RepoAllVector);
        for(std::string const& r : RepoAllVector)
        {
            this->GetRepository(r);
        }
    }

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

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

    // Executable suffix
    std::string exeSuffix(this->GetOption("CMAKE_EXECUTABLE_SUFFIX"));
    std::string sysName(this->GetOption("CMAKE_SYSTEM_NAME"));
    if(sysName == "Linux")
    {
        this->ExecutableSuffix = ".run";
    } else if(sysName == "Windows")
    {
        this->ExecutableSuffix = ".exe";
    } else if(sysName == "Darwin")
    {
        this->ExecutableSuffix = ".app";
    } else
    {
        this->ExecutableSuffix = exeSuffix;
    }

    // Output extension
    if(const char* optOutExt =
           this->GetOption("CPACK_IFW_PACKAGE_FILE_EXTENSION"))
    {
        this->OutputExtension = optOutExt;
    } else if(sysName == "Darwin")
    {
        this->OutputExtension = ".dmg";
    } else
    {
        this->OutputExtension = this->ExecutableSuffix;
    }
    if(this->OutputExtension.empty())
    {
        this->OutputExtension = this->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(this->componentPackageMethod == this->ONE_PACKAGE)
    {
        return std::string(prefix + this->GetRootPackageName() + suffix);
    }

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

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

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

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

    cmCPackIFWPackage* package = &this->Packages[name];
    package->Name              = name;
    package->Generator         = this;
    if(package->ConfigureFromComponent(component))
    {
        package->Installer = &this->Installer;
        this->Installer.Packages.insert(
            std::pair<std::string, cmCPackIFWPackage*>(name, package));
        this->ComponentPackages.insert(
            std::pair<cmCPackComponent*, cmCPackIFWPackage*>(component,
                                                             package));
        if(component->IsDownloaded)
        {
            this->DownloadedPackages.insert(package);
        } else
        {
            this->BinaryPackages.insert(package);
        }
    } else
    {
        this->Packages.erase(name);
        cmCPackIFWLogger(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 =
        this->cmCPackGenerator::GetComponentGroup(projectName, groupName);
    if(!group)
    {
        return group;
    }

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

    cmCPackIFWPackage* package = &this->Packages[name];
    package->Name              = name;
    package->Generator         = this;
    if(package->ConfigureFromGroup(group))
    {
        package->Installer = &this->Installer;
        this->Installer.Packages.insert(
            std::pair<std::string, cmCPackIFWPackage*>(name, package));
        this->GroupPackages.insert(
            std::pair<cmCPackComponentGroup*, cmCPackIFWPackage*>(group,
                                                                  package));
        this->BinaryPackages.insert(package);
    } else
    {
        this->Packages.erase(name);
        cmCPackIFWLogger(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 this->componentPackageMethod == cmCPackGenerator::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 = this->GetGroupPackage(group))
    {
        return package->Name;
    }
    const char* option =
        this->GetOption("CPACK_IFW_COMPONENT_GROUP_" +
                        cmsys::SystemTools::UpperCase(group->Name) + "_NAME");
    name = option ? option : group->Name;
    if(group->ParentGroup)
    {
        cmCPackIFWPackage* package = this->GetGroupPackage(group->ParentGroup);
        bool               dot     = !this->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 = this->GetComponentPackage(component))
    {
        return package->Name;
    }
    std::string prefix = "CPACK_IFW_COMPONENT_" +
                         cmsys::SystemTools::UpperCase(component->Name) + "_";
    const char* option = this->GetOption(prefix + "NAME");
    name               = option ? option : component->Name;
    if(component->Group)
    {
        cmCPackIFWPackage* package = this->GetGroupPackage(component->Group);
        if((this->componentPackageMethod ==
            cmCPackGenerator::ONE_PACKAGE_PER_GROUP) ||
           this->IsOn(prefix + "COMMON"))
        {
            return package->Name;
        }
        bool dot = !this->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 =
        this->GroupPackages.find(group);
    return pit != this->GroupPackages.end() ? pit->second : nullptr;
}

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

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

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