/*=========================================================================

  Program:   CMake - Cross-Platform Makefile Generator
  Module:    $RCSfile$
  Language:  C++
  Date:      $Date$
  Version:   $Revision$

  Copyright (c) 2002 Kitware, Inc., Insight Consortium.  All rights reserved.
  See Copyright.txt or http://www.cmake.org/HTML/Copyright.html for details.

     This software is distributed WITHOUT ANY WARRANTY; without even
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
     PURPOSE.  See the above copyright notices for more information.

=========================================================================*/
#include "cmInstallTargetGenerator.h"

#include "cmGlobalGenerator.h"
#include "cmLocalGenerator.h"
#include "cmMakefile.h"
#include "cmTarget.h"
#include "cmake.h"

//----------------------------------------------------------------------------
cmInstallTargetGenerator
::cmInstallTargetGenerator(cmTarget& t, const char* dest, bool implib,
                           const char* permissions,
                           std::vector<std::string> const& configurations,
                           const char* component):
  Target(&t), Destination(dest), ImportLibrary(implib),
  Permissions(permissions), Configurations(configurations),
  Component(component)
{
  this->Target->SetHaveInstallRule(true);
}

//----------------------------------------------------------------------------
cmInstallTargetGenerator
::~cmInstallTargetGenerator()
{
}

//----------------------------------------------------------------------------
void cmInstallTargetGenerator::GenerateScript(std::ostream& os)
{
  // Compute the build tree directory from which to copy the target.
  std::string fromDir;
  if(this->Target->NeedRelinkBeforeInstall())
    {
    fromDir = this->Target->GetMakefile()->GetStartOutputDirectory();
    fromDir += cmake::GetCMakeFilesDirectory();
    fromDir += "/CMakeRelink.dir/";
    }
  else
    {
    fromDir = this->Target->GetDirectory();
    fromDir += "/";
    }

  // Write variable settings to do per-configuration references.
  this->PrepareScriptReference(os, this->Target, "BUILD", true, false);

  // Create the per-configuration reference.
  std::string fromName = this->GetScriptReference(this->Target, "BUILD",
                                                  false);
  std::string fromFile = fromDir;
  fromFile += fromName;

  // Choose the final destination.  This may be modified for certain
  // target types.
  std::string destination = this->Destination;

  // Setup special properties for some target types.
  std::string props;
  const char* properties = 0;
  cmTarget::TargetType type = this->Target->GetType();
  switch(type)
    {
    case cmTarget::SHARED_LIBRARY:
      {
      // Add shared library installation properties if this platform
      // supports them.
      const char* lib_version = this->Target->GetProperty("VERSION");
      const char* lib_soversion = this->Target->GetProperty("SOVERSION");
      if(!this->Target->GetMakefile()
         ->GetDefinition("CMAKE_SHARED_LIBRARY_SONAME_C_FLAG"))
        {
        // Versioning is supported only for shared libraries and modules,
        // and then only when the platform supports an soname flag.
        lib_version = 0;
        lib_soversion = 0;
        }
      if(lib_version)
        {
        props += " VERSION ";
        props += lib_version;
        }
      if(lib_soversion)
        {
        props += " SOVERSION ";
        props += lib_soversion;
        }
      properties = props.c_str();
      }
      break;
    case cmTarget::EXECUTABLE:
      {
      // Add executable installation properties if this platform
      // supports them.
#if defined(_WIN32) && !defined(__CYGWIN__)
      const char* exe_version = 0;
#else
      const char* exe_version = this->Target->GetProperty("VERSION");
#endif
      if(exe_version)
        {
        props += " VERSION ";
        props += exe_version;
        properties = props.c_str();
        }

      // Handle OSX Bundles.
      if(this->Target->GetMakefile()->IsOn("APPLE") &&
         this->Target->GetPropertyAsBool("MACOSX_BUNDLE"))
        {
        // Compute the source locations of the bundle executable and
        // Info.plist file.
        this->PrepareScriptReference(os, this->Target, "INSTALL",
                                     false, false);
        fromFile += ".app";
        type = cmTarget::INSTALL_DIRECTORY;
        }
      }
      break;
    case cmTarget::STATIC_LIBRARY:
    case cmTarget::MODULE_LIBRARY:
      // Nothing special for modules or static libraries.
      break;
    default:
      break;
    }

  // An import library looks like a static library.
  if(this->ImportLibrary)
    {
    type = cmTarget::STATIC_LIBRARY;
    }

  // Write code to install the target file.
  this->AddInstallRule(os, destination.c_str(), type, fromFile.c_str(),
                       this->ImportLibrary, properties,
                       this->Permissions.c_str(),
                       this->Configurations,
                       this->Component.c_str());

  // Fix the install_name settings in installed binaries.
  if(type == cmTarget::SHARED_LIBRARY ||
     type == cmTarget::MODULE_LIBRARY ||
     type == cmTarget::EXECUTABLE)
    {
    this->AddInstallNamePatchRule(os, destination.c_str());
    }
}

//----------------------------------------------------------------------------
void
cmInstallTargetGenerator
::PrepareScriptReference(std::ostream& os, cmTarget* target,
                         const char* place, bool useConfigDir,
                         bool useSOName)
{
  // If the target name may vary with the configuration type then
  // store all possible names ahead of time in variables.
  std::string fname;
  for(std::vector<std::string>::const_iterator i =
        this->ConfigurationTypes->begin();
      i != this->ConfigurationTypes->end(); ++i)
    {
    // Initialize the name.
    fname = "";

    if(useConfigDir)
      {
      // Start with the configuration's subdirectory.
      target->GetMakefile()->GetLocalGenerator()->GetGlobalGenerator()->
        AppendDirectoryForConfig("", i->c_str(), "/", fname);
      }

    // Compute the name of the library.
    std::string targetName;
    std::string targetNameSO;
    std::string targetNameReal;
    std::string targetNameImport;
    target->GetLibraryNames(targetName, targetNameSO, targetNameReal,
                            targetNameImport, i->c_str());
    if(this->ImportLibrary)
      {
      // Use the import library name.
      fname += targetNameImport;
      }
    else if(useSOName)
      {
      // Use the soname.
      fname += targetNameSO;
      }
    else
      {
      // Use the canonical name.
      fname += targetName;
      }

    // Set a variable with the target name for this configuration.
    os << "SET(" << target->GetName() << "_" << place
       << (this->ImportLibrary? "_IMPNAME_" : "_NAME_") << *i
       << " \"" << fname << "\")\n";
    }
}

//----------------------------------------------------------------------------
std::string cmInstallTargetGenerator::GetScriptReference(cmTarget* target,
                                                         const char* place,
                                                         bool useSOName)
{
  if(this->ConfigurationTypes->empty())
    {
    // Reference the target by its one configuration name.
    std::string targetName;
    std::string targetNameSO;
    std::string targetNameReal;
    std::string targetNameImport;
    target->GetLibraryNames(targetName, targetNameSO, targetNameReal,
                            targetNameImport, this->ConfigurationName);
    if(this->ImportLibrary)
      {
      // Use the import library name.
      return targetNameImport;
      }
    else if(useSOName)
      {
      // Use the soname.
      return targetNameSO;
      }
    else
      {
      // Use the canonical name.
      return targetName;
      }
    }
  else
    {
    // Reference the target using the per-configuration variable.
    std::string ref = "${";
    ref += target->GetName();
    if(this->ImportLibrary)
      {
      ref += "_";
      ref += place;
      ref += "_IMPNAME_";
      }
    else
      {
      ref += "_";
      ref += place;
      ref += "_NAME_";
      }
    ref += "${CMAKE_INSTALL_CONFIG_NAME}}";
    return ref;
    }
}

//----------------------------------------------------------------------------
void cmInstallTargetGenerator
::AddInstallNamePatchRule(std::ostream& os,
                                                       const char* destination)
{
  // Build a map of build-tree install_name to install-tree install_name for
  // shared libraries linked to this target.
  std::map<cmStdString, cmStdString> install_name_remap;
  cmTarget::LinkLibraryType linkType = cmTarget::OPTIMIZED;
  const char* config = this->ConfigurationName;
  if(config && cmSystemTools::UpperCase(config) == "DEBUG")
    {
    linkType = cmTarget::DEBUG;
    }
  // TODO: Merge with ComputeLinkInformation.
  const cmTarget::LinkLibraryVectorType& inLibs = 
    this->Target->GetLinkLibraries();
  for(cmTarget::LinkLibraryVectorType::const_iterator j = inLibs.begin();
      j != inLibs.end(); ++j)
    {
    std::string lib = j->first;
    if((this->Target->GetType() == cmTarget::EXECUTABLE ||
        lib != this->Target->GetName()) &&
       (j->second == cmTarget::GENERAL || j->second == linkType))
      {
      if(cmTarget* tgt = this->Target->GetMakefile()->
         GetLocalGenerator()->GetGlobalGenerator()->
         FindTarget(0, lib.c_str()))
        {
        if(tgt->GetType() == cmTarget::SHARED_LIBRARY)
          {
          // If the build tree and install tree use different path
          // components of the install_name field then we need to create a
          // mapping to be applied after installation.
          std::string for_build = tgt->GetInstallNameDirForBuildTree(config);
          std::string for_install = 
            tgt->GetInstallNameDirForInstallTree(config);
          if(for_build != for_install)
            {
            // Map from the build-tree install_name.
            this->PrepareScriptReference(os, tgt, "REMAP_FROM",
                                         !for_build.empty(), true);
            for_build += this->GetScriptReference(tgt, "REMAP_FROM", true);

            // Map to the install-tree install_name.
            this->PrepareScriptReference(os, tgt, "REMAP_TO",
                                         false, true);
            for_install += this->GetScriptReference(tgt, "REMAP_TO", true);

            // Store the mapping entry.
            install_name_remap[for_build] = for_install;
            }
          }
        }
      }
    }

  // Edit the install_name of the target itself if necessary.
  this->PrepareScriptReference(os, this->Target, "REMAPPED", false, true);
  std::string new_id;
  if(this->Target->GetType() == cmTarget::SHARED_LIBRARY)
    {
    std::string for_build = 
      this->Target->GetInstallNameDirForBuildTree(config);
    std::string for_install = 
      this->Target->GetInstallNameDirForInstallTree(config);
    if(for_build != for_install)
      {
      // Prepare to refer to the install-tree install_name.
      new_id = for_install;
      new_id += this->GetScriptReference(this->Target, "REMAPPED", true);
      }
    }

  // Write a rule to run install_name_tool to set the install-tree
  // install_name value and references.
  if(!new_id.empty() || !install_name_remap.empty())
    {
    std::string component_test = "NOT CMAKE_INSTALL_COMPONENT OR "
      "\"${CMAKE_INSTALL_COMPONENT}\" MATCHES \"^(";
    component_test += this->Component;
    component_test += ")$\"";
    os << "IF(" << component_test << ")\n";
    os << "  EXECUTE_PROCESS(COMMAND install_name_tool";
    if(!new_id.empty())
      {
      os << "\n    -id \"" << new_id << "\"";
      }
    for(std::map<cmStdString, cmStdString>::const_iterator
          i = install_name_remap.begin();
        i != install_name_remap.end(); ++i)
      {
      os << "\n    -change \"" << i->first << "\" \"" << i->second << "\"";
      }
    os << "\n    \"$ENV{DESTDIR}" << destination << "/"
       << this->GetScriptReference(this->Target, "REMAPPED", true) << "\")\n";
    os << "ENDIF(" << component_test << ")\n";
    }
}
