/*============================================================================
  CMake - Cross Platform Makefile Generator
  Copyright 2000-2009 Kitware, Inc., Insight Software Consortium

  Distributed under the OSI-approved BSD License (the "License");
  see accompanying file Copyright.txt for details.

  This software is distributed WITHOUT ANY WARRANTY; without even the
  implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  See the License for more information.
============================================================================*/
#include "cmExportInstallFileGenerator.h"

#include "cmGeneratedFileStream.h"
#include "cmInstallExportGenerator.h"
#include "cmInstallTargetGenerator.h"

//----------------------------------------------------------------------------
cmExportInstallFileGenerator
::cmExportInstallFileGenerator(cmInstallExportGenerator* iegen):
  InstallExportGenerator(iegen)
{
}

//----------------------------------------------------------------------------
std::string cmExportInstallFileGenerator::GetConfigImportFileGlob()
{
  std::string glob = this->FileBase;
  glob += "-*";
  glob += this->FileExt;
  return glob;
}

//----------------------------------------------------------------------------
bool cmExportInstallFileGenerator::GenerateMainFile(std::ostream& os)
{
  // Create all the imported targets.
  for(std::vector<cmTargetExport*>::const_iterator
        tei = this->ExportSet->begin();
      tei != this->ExportSet->end(); ++tei)
    {
    cmTargetExport* te = *tei;
    if(this->ExportedTargets.insert(te->Target).second)
      {
      this->GenerateImportTargetCode(os, te->Target);
      }
    else
      {
      cmOStringStream e;
      e << "INSTALL(EXPORT \"" << this->Name << "\" ...) "
        << "includes target \"" << te->Target->GetName()
        << "\" more than once in the export set.";
      cmSystemTools::Error(e.str().c_str());
      return false;
      }
    }

  // Now load per-configuration properties for them.
  os << "# Load information for each installed configuration.\n"
     << "GET_FILENAME_COMPONENT(_DIR \"${CMAKE_CURRENT_LIST_FILE}\" PATH)\n"
     << "FILE(GLOB CONFIG_FILES \"${_DIR}/"
     << this->GetConfigImportFileGlob() << "\")\n"
     << "FOREACH(f ${CONFIG_FILES})\n"
     << "  INCLUDE(${f})\n"
     << "ENDFOREACH(f)\n"
     << "\n";

  // Generate an import file for each configuration.
  bool result = true;
  for(std::vector<std::string>::const_iterator
        ci = this->Configurations.begin();
      ci != this->Configurations.end(); ++ci)
    {
    if(!this->GenerateImportFileConfig(ci->c_str()))
      {
      result = false;
      }
    }
  return result;
}

//----------------------------------------------------------------------------
bool
cmExportInstallFileGenerator::GenerateImportFileConfig(const char* config)
{
  // Skip configurations not enabled for this export.
  if(!this->InstallExportGenerator->InstallsForConfig(config))
    {
    return true;
    }

  // Construct the name of the file to generate.
  std::string fileName = this->FileDir;
  fileName += "/";
  fileName += this->FileBase;
  fileName += "-";
  if(config && *config)
    {
    fileName += cmSystemTools::LowerCase(config);
    }
  else
    {
    fileName += "noconfig";
    }
  fileName += this->FileExt;

  // Open the output file to generate it.
  cmGeneratedFileStream exportFileStream(fileName.c_str(), true);
  if(!exportFileStream)
    {
    std::string se = cmSystemTools::GetLastSystemError();
    cmOStringStream e;
    e << "cannot write to file \"" << fileName.c_str()
      << "\": " << se;
    cmSystemTools::Error(e.str().c_str());
    return false;
    }
  std::ostream& os = exportFileStream;

  // Start with the import file header.
  this->GenerateImportHeaderCode(os, config);

  // Generate the per-config target information.
  this->GenerateImportConfig(os, config);

  // End with the import file footer.
  this->GenerateImportFooterCode(os);

  // Record this per-config import file.
  this->ConfigImportFiles[config] = fileName;

  return true;
}

//----------------------------------------------------------------------------
void
cmExportInstallFileGenerator
::GenerateImportTargetsConfig(std::ostream& os,
                              const char* config, std::string const& suffix)
{
  // Add code to compute the installation prefix relative to the
  // import file location.
  const char* installDest = this->InstallExportGenerator->GetDestination();
  if(!cmSystemTools::FileIsFullPath(installDest))
    {
    std::string dest = installDest;
    os << "# Compute the installation prefix relative to this file.\n"
       << "GET_FILENAME_COMPONENT(_IMPORT_PREFIX "
       << "\"${CMAKE_CURRENT_LIST_FILE}\" PATH)\n";
    while(!dest.empty())
      {
      os <<
        "GET_FILENAME_COMPONENT(_IMPORT_PREFIX \"${_IMPORT_PREFIX}\" PATH)\n";
      dest = cmSystemTools::GetFilenamePath(dest);
      }
    os << "\n";

    // Import location properties may reference this variable.
    this->ImportPrefix = "${_IMPORT_PREFIX}/";
    }

  // Add each target in the set to the export.
  for(std::vector<cmTargetExport*>::const_iterator
        tei = this->ExportSet->begin();
      tei != this->ExportSet->end(); ++tei)
    {
    // Collect import properties for this target.
    cmTargetExport* te = *tei;
    ImportPropertyMap properties;
    std::set<std::string> importedLocations;
    this->SetImportLocationProperty(config, suffix, te->ArchiveGenerator,
                                    properties, importedLocations);
    this->SetImportLocationProperty(config, suffix, te->LibraryGenerator,
                                    properties, importedLocations);
    this->SetImportLocationProperty(config, suffix,
                                    te->RuntimeGenerator, properties,
                                    importedLocations);
    this->SetImportLocationProperty(config, suffix, te->FrameworkGenerator,
                                    properties, importedLocations);
    this->SetImportLocationProperty(config, suffix, te->BundleGenerator,
                                    properties, importedLocations);

    // If any file location was set for the target add it to the
    // import file.
    if(!properties.empty())
      {
      // Get the rest of the target details.
      this->SetImportDetailProperties(config, suffix,
                                      te->Target, properties);

      // TOOD: PUBLIC_HEADER_LOCATION
      // This should wait until the build feature propagation stuff
      // is done.  Then this can be a propagated include directory.
      // this->GenerateImportProperty(config, te->HeaderGenerator,
      //                              properties);

      // Generate code in the export file.
      this->GenerateImportPropertyCode(os, config, te->Target, properties);
      this->GenerateImportedFileChecksCode(os, te->Target, properties,
                                           importedLocations);
      }
    }

  this->GenerateImportedFileCheckLoop(os);

  // Cleanup the import prefix variable.
  if(!this->ImportPrefix.empty())
    {
    os << "# Cleanup temporary variables.\n"
       << "SET(_IMPORT_PREFIX)\n"
       << "\n";
    }
}

//----------------------------------------------------------------------------
void
cmExportInstallFileGenerator
::SetImportLocationProperty(const char* config, std::string const& suffix,
                            cmInstallTargetGenerator* itgen,
                            ImportPropertyMap& properties,
                            std::set<std::string>& importedLocations
                           )
{
  // Skip rules that do not match this configuration.
  if(!(itgen && itgen->InstallsForConfig(config)))
    {
    return;
    }

  // Get the target to be installed.
  cmTarget* target = itgen->GetTarget();

  // Construct the installed location of the target.
  std::string dest = itgen->GetDestination();
  std::string value;
  if(!cmSystemTools::FileIsFullPath(dest.c_str()))
    {
    // The target is installed relative to the installation prefix.
    if(this->ImportPrefix.empty())
      {
      this->ComplainAboutImportPrefix(itgen);
      }
    value = this->ImportPrefix;
    }
  value += dest;
  value += "/";

  if(itgen->IsImportLibrary())
    {
    // Construct the property name.
    std::string prop = "IMPORTED_IMPLIB";
    prop += suffix;

    // Append the installed file name.
    value += itgen->GetInstallFilename(target, config,
                                       cmInstallTargetGenerator::NameImplib);

    // Store the property.
    properties[prop] = value;
    importedLocations.insert(prop);
    }
  else
    {
    // Construct the property name.
    std::string prop = "IMPORTED_LOCATION";
    prop += suffix;

    // Append the installed file name.
    if(target->IsFrameworkOnApple())
      {
      value += itgen->GetInstallFilename(target, config);
      value += ".framework/";
      value += itgen->GetInstallFilename(target, config);
      }
    else if(target->IsCFBundleOnApple())
      {
      const char *ext = target->GetProperty("BUNDLE_EXTENSION");
      if (!ext)
        {
        ext = "bundle";
        }

      value += itgen->GetInstallFilename(target, config);
      value += ".";
      value += ext;
      value += "/";
      value += itgen->GetInstallFilename(target, config);
      }
    else if(target->IsAppBundleOnApple())
      {
      value += itgen->GetInstallFilename(target, config);
      value += ".app/Contents/MacOS/";
      value += itgen->GetInstallFilename(target, config);
      }
    else
      {
      value += itgen->GetInstallFilename(target, config,
                                         cmInstallTargetGenerator::NameReal);
      }

    // Store the property.
    properties[prop] = value;
    importedLocations.insert(prop);
    }
}

//----------------------------------------------------------------------------
void
cmExportInstallFileGenerator
::ComplainAboutImportPrefix(cmInstallTargetGenerator* itgen)
{
  const char* installDest = this->InstallExportGenerator->GetDestination();
  cmOStringStream e;
  e << "INSTALL(EXPORT \"" << this->Name << "\") given absolute "
    << "DESTINATION \"" << installDest << "\" but the export "
    << "references an installation of target \""
    << itgen->GetTarget()->GetName() << "\" which has relative "
    << "DESTINATION \"" << itgen->GetDestination() << "\".";
  cmSystemTools::Error(e.str().c_str());
}

//----------------------------------------------------------------------------
void
cmExportInstallFileGenerator
::ComplainAboutMissingTarget(cmTarget* depender, cmTarget* dependee)
{
  cmOStringStream e;
  e << "INSTALL(EXPORT \"" << this->Name << "\" ...) "
    << "includes target \"" << depender->GetName()
    << "\" which requires target \"" << dependee->GetName()
    << "\" that is not in the export set.";
  cmSystemTools::Error(e.str().c_str());
}
