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

#include <cassert>

#include "cmGeneratorTarget.h"
#include "cmLocalGenerator.h"
#include "cmMakefile.h"
#include "cmStateTypes.h"
#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"
#include "cmTarget.h"
#include "cmValue.h"

class cmSourceFile;

cmOSXBundleGenerator::cmOSXBundleGenerator(cmGeneratorTarget* target)
  : GT(target)
  , Makefile(target->Target->GetMakefile())
  , LocalGenerator(target->GetLocalGenerator())
{
  if (this->MustSkip()) {
    return;
  }
}

bool cmOSXBundleGenerator::MustSkip()
{
  return !this->GT->HaveWellDefinedOutputFiles();
}

void cmOSXBundleGenerator::CreateAppBundle(std::string const& targetName,
                                           std::string& outpath,
                                           std::string const& config)
{
  if (this->MustSkip()) {
    return;
  }

  // Compute bundle directory names.
  std::string out = cmStrCat(
    outpath, '/',
    this->GT->GetAppBundleDirectory(config, cmGeneratorTarget::FullLevel));
  cmSystemTools::MakeDirectory(out);
  this->Makefile->AddCMakeOutputFile(out);

  // Configure the Info.plist file.  Note that it needs the executable name
  // to be set.
  std::string plist = cmStrCat(
    outpath, '/',
    this->GT->GetAppBundleDirectory(config, cmGeneratorTarget::ContentLevel),
    "/Info.plist");
  this->LocalGenerator->GenerateAppleInfoPList(this->GT, targetName, plist);
  this->Makefile->AddCMakeOutputFile(plist);
  outpath = out;
}

void cmOSXBundleGenerator::CreateFramework(
  std::string const& targetName, std::string const& outpath,
  std::string const& config, cmOSXBundleGenerator::SkipParts skipParts)
{
  if (this->MustSkip()) {
    return;
  }

  assert(this->MacContentFolders);

  // Compute the location of the top-level foo.framework directory.
  std::string contentdir = cmStrCat(
    outpath, '/',
    this->GT->GetFrameworkDirectory(config, cmGeneratorTarget::ContentLevel),
    '/');

  std::string newoutpath = outpath + "/" +
    this->GT->GetFrameworkDirectory(config, cmGeneratorTarget::FullLevel);

  std::string frameworkVersion = this->GT->GetFrameworkVersion();

  std::string name = cmSystemTools::GetFilenameName(targetName);
  if (!skipParts.InfoPlist) {
    // Configure the Info.plist file
    std::string plist = newoutpath;
    if (!this->Makefile->PlatformIsAppleEmbedded()) {
      // Put the Info.plist file into the Resources directory.
      this->MacContentFolders->insert("Resources");
      plist += "/Resources";
    }
    plist += "/Info.plist";
    this->LocalGenerator->GenerateFrameworkInfoPList(this->GT, name, plist);
  }

  // Generate Versions directory only for MacOSX frameworks
  if (this->Makefile->PlatformIsAppleEmbedded()) {
    return;
  }

  // TODO: Use the cmMakefileTargetGenerator::ExtraFiles vector to
  // drive rules to create these files at build time.
  std::string oldName;
  std::string newName;

  // Make foo.framework/Versions
  std::string versions = cmStrCat(contentdir, "Versions");
  cmSystemTools::MakeDirectory(versions);

  // Make foo.framework/Versions/version
  cmSystemTools::MakeDirectory(newoutpath);

  // Current -> version
  oldName = frameworkVersion;
  newName = cmStrCat(versions, "/Current");
  cmSystemTools::RemoveFile(newName);
  cmSystemTools::CreateSymlink(oldName, newName);
  this->Makefile->AddCMakeOutputFile(newName);

  // foo -> Versions/Current/foo
  oldName = cmStrCat("Versions/Current/", name);
  newName = cmStrCat(contentdir, name);
  cmSystemTools::RemoveFile(newName);
  cmSystemTools::CreateSymlink(oldName, newName);
  this->Makefile->AddCMakeOutputFile(newName);

  if (!skipParts.TextStubs) {
    // foo.tbd -> Versions/Current/foo.tbd
    cmValue tbdSuffix =
      this->Makefile->GetDefinition("CMAKE_APPLE_IMPORT_FILE_SUFFIX");
    oldName = cmStrCat("Versions/Current/", name, tbdSuffix);
    newName = cmStrCat(contentdir, name, tbdSuffix);
    cmSystemTools::RemoveFile(newName);
    cmSystemTools::CreateSymlink(oldName, newName);
    this->Makefile->AddCMakeOutputFile(newName);
  }

  // Resources -> Versions/Current/Resources
  if (this->MacContentFolders->find("Resources") !=
      this->MacContentFolders->end()) {
    oldName = "Versions/Current/Resources";
    newName = cmStrCat(contentdir, "Resources");
    cmSystemTools::RemoveFile(newName);
    cmSystemTools::CreateSymlink(oldName, newName);
    this->Makefile->AddCMakeOutputFile(newName);
  }

  // Headers -> Versions/Current/Headers
  if (this->MacContentFolders->find("Headers") !=
      this->MacContentFolders->end()) {
    oldName = "Versions/Current/Headers";
    newName = cmStrCat(contentdir, "Headers");
    cmSystemTools::RemoveFile(newName);
    cmSystemTools::CreateSymlink(oldName, newName);
    this->Makefile->AddCMakeOutputFile(newName);
  }

  // PrivateHeaders -> Versions/Current/PrivateHeaders
  if (this->MacContentFolders->find("PrivateHeaders") !=
      this->MacContentFolders->end()) {
    oldName = "Versions/Current/PrivateHeaders";
    newName = cmStrCat(contentdir, "PrivateHeaders");
    cmSystemTools::RemoveFile(newName);
    cmSystemTools::CreateSymlink(oldName, newName);
    this->Makefile->AddCMakeOutputFile(newName);
  }
}

void cmOSXBundleGenerator::CreateCFBundle(std::string const& targetName,
                                          std::string const& root,
                                          std::string const& config)
{
  if (this->MustSkip()) {
    return;
  }

  // Compute bundle directory names.
  std::string out = cmStrCat(
    root, '/',
    this->GT->GetCFBundleDirectory(config, cmGeneratorTarget::FullLevel));
  cmSystemTools::MakeDirectory(out);
  this->Makefile->AddCMakeOutputFile(out);

  // Configure the Info.plist file.  Note that it needs the executable name
  // to be set.
  std::string plist = cmStrCat(
    root, '/',
    this->GT->GetCFBundleDirectory(config, cmGeneratorTarget::ContentLevel),
    "/Info.plist");
  std::string name = cmSystemTools::GetFilenameName(targetName);
  this->LocalGenerator->GenerateAppleInfoPList(this->GT, name, plist);
  this->Makefile->AddCMakeOutputFile(plist);
}

void cmOSXBundleGenerator::GenerateMacOSXContentStatements(
  std::vector<cmSourceFile const*> const& sources,
  MacOSXContentGeneratorType* generator, std::string const& config)
{
  if (this->MustSkip()) {
    return;
  }

  for (cmSourceFile const* source : sources) {
    cmGeneratorTarget::SourceFileFlags tsFlags =
      this->GT->GetTargetSourceFileFlags(source);
    if (tsFlags.Type != cmGeneratorTarget::SourceFileTypeNormal) {
      (*generator)(*source, tsFlags.MacFolder, config);
    }
  }
}

std::string cmOSXBundleGenerator::InitMacOSXContentDirectory(
  char const* pkgloc, std::string const& config)
{
  // Construct the full path to the content subdirectory.

  std::string macdir = cmStrCat(this->GT->GetMacContentDirectory(
                                  config, cmStateEnums::RuntimeBinaryArtifact),
                                '/', pkgloc);
  cmSystemTools::MakeDirectory(macdir);

  // Record use of this content location.  Only the first level
  // directory is needed.
  {
    std::string loc = pkgloc;
    loc = loc.substr(0, loc.find('/'));
    this->MacContentFolders->insert(loc);
  }

  return macdir;
}
