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

#include <sstream>

#include "cmCPackGenerator.h"
#include "cmCPackLog.h"
#include "cmDuration.h"
#include "cmGeneratedFileStream.h"
#include "cmSystemTools.h"
#include "cm_sys_stat.h"

cmCPackOSXX11Generator::cmCPackOSXX11Generator() {}

cmCPackOSXX11Generator::~cmCPackOSXX11Generator() {}

int
cmCPackOSXX11Generator::PackageFiles()
{
    // TODO: Use toplevel ?
    //       It is used! Is this an obsolete comment?

    const char* cpackPackageExecutables =
        this->GetOption("CPACK_PACKAGE_EXECUTABLES");
    if(cpackPackageExecutables)
    {
        cmCPackLogger(cmCPackLog::LOG_DEBUG,
                      "The cpackPackageExecutables: " << cpackPackageExecutables
                                                      << "." << std::endl);
        std::ostringstream       str;
        std::ostringstream       deleteStr;
        std::vector<std::string> cpackPackageExecutablesVector;
        cmSystemTools::ExpandListArgument(cpackPackageExecutables,
                                          cpackPackageExecutablesVector);
        if(cpackPackageExecutablesVector.size() % 2 != 0)
        {
            cmCPackLogger(cmCPackLog::LOG_ERROR,
                          "CPACK_PACKAGE_EXECUTABLES should contain pairs of "
                          "<executable> and "
                          "<icon name>."
                              << std::endl);
            return 0;
        }
        std::vector<std::string>::iterator it;
        for(it = cpackPackageExecutablesVector.begin();
            it != cpackPackageExecutablesVector.end(); ++it)
        {
            std::string cpackExecutableName = *it;
            ++it;
            this->SetOptionIfNotSet("CPACK_EXECUTABLE_NAME",
                                    cpackExecutableName.c_str());
        }
    }

    // Disk image directories
    std::string diskImageDirectory = toplevel;
    std::string diskImageBackgroundImageDir =
        diskImageDirectory + "/.background";

    // App bundle directories
    std::string packageDirFileName = toplevel;
    packageDirFileName += "/";
    packageDirFileName += this->GetOption("CPACK_PACKAGE_FILE_NAME");
    packageDirFileName += ".app";
    std::string contentsDirectory  = packageDirFileName + "/Contents";
    std::string resourcesDirectory = contentsDirectory + "/Resources";
    std::string appDirectory       = contentsDirectory + "/MacOS";
    std::string scriptDirectory    = resourcesDirectory + "/Scripts";
    std::string resourceFileName   = this->GetOption("CPACK_PACKAGE_FILE_NAME");
    resourceFileName += ".rsrc";

    const char* dir      = resourcesDirectory.c_str();
    const char* appdir   = appDirectory.c_str();
    const char* scrDir   = scriptDirectory.c_str();
    const char* contDir  = contentsDirectory.c_str();
    const char* rsrcFile = resourceFileName.c_str();
    const char* iconFile = this->GetOption("CPACK_PACKAGE_ICON");
    if(iconFile)
    {
        std::string iconFileName =
            cmsys::SystemTools::GetFilenameName(iconFile);
        if(!cmSystemTools::FileExists(iconFile))
        {
            cmCPackLogger(cmCPackLog::LOG_ERROR,
                          "Cannot find icon file: "
                              << iconFile
                              << ". Please check CPACK_PACKAGE_ICON setting."
                              << std::endl);
            return 0;
        }
        std::string destFileName = resourcesDirectory + "/" + iconFileName;
        this->ConfigureFile(iconFile, destFileName.c_str(), true);
        this->SetOptionIfNotSet("CPACK_APPLE_GUI_ICON", iconFileName.c_str());
    }

    std::string applicationsLinkName = diskImageDirectory + "/Applications";
    cmSystemTools::CreateSymlink("/Applications", applicationsLinkName);

    if(!this->CopyResourcePlistFile("VolumeIcon.icns", diskImageDirectory,
                                    ".VolumeIcon.icns", true) ||
       !this->CopyResourcePlistFile("DS_Store", diskImageDirectory, ".DS_Store",
                                    true) ||
       !this->CopyResourcePlistFile("background.png",
                                    diskImageBackgroundImageDir,
                                    "background.png", true) ||
       !this->CopyResourcePlistFile("RuntimeScript", dir) ||
       !this->CopyResourcePlistFile("OSXX11.Info.plist", contDir,
                                    "Info.plist") ||
       !this->CopyResourcePlistFile("OSXX11.main.scpt", scrDir, "main.scpt",
                                    true) ||
       !this->CopyResourcePlistFile("OSXScriptLauncher.rsrc", dir, rsrcFile,
                                    true) ||
       !this->CopyResourcePlistFile("OSXScriptLauncher", appdir,
                                    this->GetOption("CPACK_PACKAGE_FILE_NAME"),
                                    true))
    {
        cmCPackLogger(cmCPackLog::LOG_ERROR,
                      "Problem copying the resource files" << std::endl);
        return 0;
    }

    // Two of the files need to have execute permission, so ensure they do:
    std::string runTimeScript = dir;
    runTimeScript += "/";
    runTimeScript += "RuntimeScript";

    std::string appScriptName = appdir;
    appScriptName += "/";
    appScriptName += this->GetOption("CPACK_PACKAGE_FILE_NAME");

    mode_t mode;
    if(cmsys::SystemTools::GetPermissions(runTimeScript.c_str(), mode))
    {
        mode |= (S_IXUSR | S_IXGRP | S_IXOTH);
        cmsys::SystemTools::SetPermissions(runTimeScript.c_str(), mode);
        cmCPackLogger(cmCPackLog::LOG_OUTPUT,
                      "Setting: " << runTimeScript << " to permission: " << mode
                                  << std::endl);
    }

    if(cmsys::SystemTools::GetPermissions(appScriptName.c_str(), mode))
    {
        mode |= (S_IXUSR | S_IXGRP | S_IXOTH);
        cmsys::SystemTools::SetPermissions(appScriptName.c_str(), mode);
        cmCPackLogger(cmCPackLog::LOG_OUTPUT,
                      "Setting: " << appScriptName << " to permission: " << mode
                                  << std::endl);
    }

    std::string output;
    std::string tmpFile = this->GetOption("CPACK_TOPLEVEL_DIRECTORY");
    tmpFile += "/hdiutilOutput.log";
    std::ostringstream dmgCmd;
    dmgCmd << "\"" << this->GetOption("CPACK_INSTALLER_PROGRAM_DISK_IMAGE")
           << "\" create -ov -fs HFS+ -format UDZO -srcfolder \""
           << diskImageDirectory << "\" \"" << packageFileNames[0] << "\"";
    cmCPackLogger(cmCPackLog::LOG_VERBOSE, "Compress disk image using command: "
                                               << dmgCmd.str() << std::endl);
    // since we get random dashboard failures with this one
    // try running it more than once
    int  retVal   = 1;
    int  numTries = 10;
    bool res      = false;
    while(numTries > 0)
    {
        res = cmSystemTools::RunSingleCommand(
            dmgCmd.str().c_str(), &output, &output, &retVal, nullptr,
            this->GeneratorVerbose, cmDuration::zero());
        if(res && !retVal)
        {
            numTries = -1;
            break;
        }
        cmSystemTools::Delay(500);
        numTries--;
    }
    if(!res || retVal)
    {
        cmGeneratedFileStream ofs(tmpFile.c_str());
        ofs << "# Run command: " << dmgCmd.str() << std::endl
            << "# Output:" << std::endl
            << output << std::endl;
        cmCPackLogger(cmCPackLog::LOG_ERROR, "Problem running hdiutil command: "
                                                 << dmgCmd.str() << std::endl
                                                 << "Please check " << tmpFile
                                                 << " for errors" << std::endl);
        return 0;
    }

    return 1;
}

int
cmCPackOSXX11Generator::InitializeInternal()
{
    cmCPackLogger(cmCPackLog::LOG_DEBUG,
                  "cmCPackOSXX11Generator::Initialize()" << std::endl);
    std::vector<std::string> path;
    std::string pkgPath = cmSystemTools::FindProgram("hdiutil", path, false);
    if(pkgPath.empty())
    {
        cmCPackLogger(cmCPackLog::LOG_ERROR,
                      "Cannot find hdiutil compiler" << std::endl);
        return 0;
    }
    this->SetOptionIfNotSet("CPACK_INSTALLER_PROGRAM_DISK_IMAGE",
                            pkgPath.c_str());

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

/*
bool cmCPackOSXX11Generator::CopyCreateResourceFile(const std::string& name)
{
  std::string uname = cmSystemTools::UpperCase(name);
  std::string cpackVar = "CPACK_RESOURCE_FILE_" + uname;
  const char* inFileName = this->GetOption(cpackVar.c_str());
  if ( !inFileName )
    {
    cmCPackLogger(cmCPackLog::LOG_ERROR, "CPack option: " << cpackVar.c_str()
                  << " not specified. It should point to "
                  << (name ? name : "(NULL)")
                  << ".rtf, " << name
                  << ".html, or " << name << ".txt file" << std::endl);
    return false;
    }
  if ( !cmSystemTools::FileExists(inFileName) )
    {
    cmCPackLogger(cmCPackLog::LOG_ERROR, "Cannot find "
                  << (name ? name : "(NULL)")
                  << " resource file: " << inFileName << std::endl);
    return false;
    }
  std::string ext = cmSystemTools::GetFilenameLastExtension(inFileName);
  if ( ext != ".rtfd" && ext != ".rtf" && ext != ".html" && ext != ".txt" )
    {
    cmCPackLogger(cmCPackLog::LOG_ERROR, "Bad file extension specified: "
      << ext << ". Currently only .rtfd, .rtf, .html, and .txt files allowed."
      << std::endl);
    return false;
    }

  std::string destFileName = this->GetOption("CPACK_TOPLEVEL_DIRECTORY");
  destFileName += "/Resources/";
  destFileName += name + ext;


  cmCPackLogger(cmCPackLog::LOG_VERBOSE, "Configure file: "
                << (inFileName ? inFileName : "(NULL)")
                << " to " << destFileName << std::endl);
  this->ConfigureFile(inFileName, destFileName.c_str());
  return true;
}
*/

bool
cmCPackOSXX11Generator::CopyResourcePlistFile(
    const std::string& name, const std::string& dir,
    const char* outputFileName /* = 0 */, bool copyOnly /* = false */)
{
    std::string inFName = "CPack.";
    inFName += name;
    inFName += ".in";
    std::string inFileName = this->FindTemplate(inFName.c_str());
    if(inFileName.empty())
    {
        cmCPackLogger(cmCPackLog::LOG_ERROR,
                      "Cannot find input file: " << inFName << std::endl);
        return false;
    }

    if(!outputFileName)
    {
        outputFileName = name.c_str();
    }

    std::string destFileName = dir;
    destFileName += "/";
    destFileName += outputFileName;

    cmCPackLogger(cmCPackLog::LOG_VERBOSE,
                  "Configure file: " << inFileName << " to " << destFileName
                                     << std::endl);
    this->ConfigureFile(inFileName.c_str(), destFileName.c_str(), copyOnly);
    return true;
}

const char*
cmCPackOSXX11Generator::GetPackagingInstallPrefix()
{
    this->InstallPrefix = "/";
    this->InstallPrefix += this->GetOption("CPACK_PACKAGE_FILE_NAME");
    this->InstallPrefix += ".app/Contents/Resources";
    return this->InstallPrefix.c_str();
}
