/*============================================================================
  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 "cmCPackArchiveGenerator.h"

#include "cmake.h"
#include "cmGlobalGenerator.h"
#include "cmLocalGenerator.h"
#include "cmSystemTools.h"
#include "cmMakefile.h"
#include "cmGeneratedFileStream.h"
#include "cmCPackLog.h"
#include <errno.h>

#include <cmsys/SystemTools.hxx>
#include <cmsys/Directory.hxx>
#include <cm_libarchive.h>

//----------------------------------------------------------------------
cmCPackArchiveGenerator::cmCPackArchiveGenerator(cmArchiveWrite::Compress t,
  cmArchiveWrite::Type at)
{
  this->Compress = t;
  this->Archive = at;
}

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

//----------------------------------------------------------------------
int cmCPackArchiveGenerator::InitializeInternal()
{
  this->SetOptionIfNotSet("CPACK_INCLUDE_TOPLEVEL_DIRECTORY", "1");
  return this->Superclass::InitializeInternal();
}
//----------------------------------------------------------------------
int cmCPackArchiveGenerator::addOneComponentToArchive(cmArchiveWrite& archive,
                             cmCPackComponent* component)
{
  cmCPackLogger(cmCPackLog::LOG_VERBOSE, "   - packaging component: "
      << component->Name
      << std::endl);
  // Add the files of this component to the archive
  std::string localToplevel(this->GetOption("CPACK_TEMPORARY_DIRECTORY"));
  localToplevel += "/"+ component->Name;
  std::string dir = cmSystemTools::GetCurrentWorkingDirectory();
  // Change to local toplevel
  cmSystemTools::ChangeDirectory(localToplevel.c_str());
  std::string filePrefix;
  if (this->IsOn("CPACK_COMPONENT_INCLUDE_TOPLEVEL_DIRECTORY"))
    {
    filePrefix = this->GetOption("CPACK_PACKAGE_FILE_NAME");
    filePrefix += "/";
    }
  std::vector<std::string>::const_iterator fileIt;
  for (fileIt = component->Files.begin(); fileIt != component->Files.end();
       ++fileIt )
    {
    std::string rp = filePrefix + *fileIt;
    cmCPackLogger(cmCPackLog::LOG_DEBUG,"Adding file: "
                  << rp << std::endl);
    archive.Add(rp);
    if (!archive)
      {
      cmCPackLogger(cmCPackLog::LOG_ERROR, "ERROR while packaging files: "
            << archive.GetError()
            << std::endl);
      return 0;
      }
    }
  // Go back to previous dir
  cmSystemTools::ChangeDirectory(dir.c_str());
  return 1;
}

/*
 * The macro will open/create a file 'filename'
 * an declare and open the associated
 * cmArchiveWrite 'archive' object.
 */
#define DECLARE_AND_OPEN_ARCHIVE(filename,archive) \
cmGeneratedFileStream gf; \
gf.Open(filename.c_str(), false, true); \
if (!GenerateHeader(&gf)) \
  { \
   cmCPackLogger(cmCPackLog::LOG_ERROR, \
    "Problem to generate Header for archive < "     \
            << filename \
            << ">." << std::endl); \
    return 0; \
  } \
cmArchiveWrite archive(gf,this->Compress, this->Archive); \
if (!archive) \
  { \
  cmCPackLogger(cmCPackLog::LOG_ERROR, "Problem to create archive < " \
     << filename \
     << ">. ERROR =" \
     << archive.GetError() \
     << std::endl); \
  return 0; \
  }

//----------------------------------------------------------------------
int cmCPackArchiveGenerator::PackageComponents(bool ignoreGroup)
{
  packageFileNames.clear();
  // The default behavior is to have one package by component group
  // unless CPACK_COMPONENTS_IGNORE_GROUP is specified.
  if (!ignoreGroup)
    {
    std::map<std::string, cmCPackComponentGroup>::iterator compGIt;
    for (compGIt=this->ComponentGroups.begin();
        compGIt!=this->ComponentGroups.end(); ++compGIt)
      {
      cmCPackLogger(cmCPackLog::LOG_VERBOSE, "Packaging component group: "
          << compGIt->first
          << std::endl);
      // Begin the archive for this group
      std::string packageFileName= std::string(toplevel);
      packageFileName += "/"+
       GetComponentPackageFileName(this->GetOption("CPACK_PACKAGE_FILE_NAME"),
                                   compGIt->first,
                                   true)
         + this->GetOutputExtension();
      // open a block in order to automatically close archive
      // at the end of the block
      {
        DECLARE_AND_OPEN_ARCHIVE(packageFileName,archive);
        // now iterate over the component of this group
        std::vector<cmCPackComponent*>::iterator compIt;
        for (compIt=(compGIt->second).Components.begin();
            compIt!=(compGIt->second).Components.end();
            ++compIt)
          {
          // Add the files of this component to the archive
          addOneComponentToArchive(archive,*compIt);
          }
      }
      // add the generated package to package file names list
      packageFileNames.push_back(packageFileName);
      }
    // Handle Orphan components (components not belonging to any groups)
    std::map<std::string, cmCPackComponent>::iterator compIt;
    for (compIt=this->Components.begin();
         compIt!=this->Components.end(); ++compIt )
      {
      // Does the component belong to a group?
      if (compIt->second.Group==NULL)
        {
        cmCPackLogger(cmCPackLog::LOG_VERBOSE,
            "Component <"
              << compIt->second.Name
              << "> does not belong to any group, package it separately."
              << std::endl);
        std::string localToplevel(
          this->GetOption("CPACK_TEMPORARY_DIRECTORY")
                                 );
        std::string packageFileName = std::string(toplevel);

        localToplevel += "/"+ compIt->first;
        packageFileName += "/"+
        GetComponentPackageFileName(this->GetOption("CPACK_PACKAGE_FILE_NAME"),
                                    compIt->first,
                                    false)
                              + this->GetOutputExtension();
        {
          DECLARE_AND_OPEN_ARCHIVE(packageFileName,archive);
          // Add the files of this component to the archive
          addOneComponentToArchive(archive,&(compIt->second));
        }
        // add the generated package to package file names list
        packageFileNames.push_back(packageFileName);
        }
      }
    }
  // CPACK_COMPONENTS_IGNORE_GROUPS is set
  // We build 1 package per component
  else
    {
    std::map<std::string, cmCPackComponent>::iterator compIt;
    for (compIt=this->Components.begin();
         compIt!=this->Components.end(); ++compIt )
      {
      std::string localToplevel(this->GetOption("CPACK_TEMPORARY_DIRECTORY"));
      std::string packageFileName = std::string(toplevel);

      localToplevel += "/"+ compIt->first;
      packageFileName += "/"+
       GetComponentPackageFileName(this->GetOption("CPACK_PACKAGE_FILE_NAME"),
                                   compIt->first,
                                   false)
        + this->GetOutputExtension();
      {
        DECLARE_AND_OPEN_ARCHIVE(packageFileName,archive);
        // Add the files of this component to the archive
        addOneComponentToArchive(archive,&(compIt->second));
      }
      // add the generated package to package file names list
      packageFileNames.push_back(packageFileName);
      }
    }
  return 1;
}

//----------------------------------------------------------------------
int cmCPackArchiveGenerator::PackageComponentsAllInOne()
{
  // reset the package file names
  packageFileNames.clear();
  packageFileNames.push_back(std::string(toplevel));
  packageFileNames[0] += "/"
    +std::string(this->GetOption("CPACK_PACKAGE_FILE_NAME"))
    + this->GetOutputExtension();
  cmCPackLogger(cmCPackLog::LOG_VERBOSE,
                "Packaging all groups in one package..."
                "(CPACK_COMPONENTS_ALL_GROUPS_IN_ONE_PACKAGE is set)"
      << std::endl);
  DECLARE_AND_OPEN_ARCHIVE(packageFileNames[0],archive);

  // The ALL COMPONENTS in ONE package case
  std::map<std::string, cmCPackComponent>::iterator compIt;
  for (compIt=this->Components.begin();compIt!=this->Components.end();
      ++compIt )
    {
    // Add the files of this component to the archive
    addOneComponentToArchive(archive,&(compIt->second));
    }

  // archive goes out of scope so it will finalized and closed.
  return 1;
}

//----------------------------------------------------------------------
int cmCPackArchiveGenerator::PackageFiles()
{
  cmCPackLogger(cmCPackLog::LOG_DEBUG, "Toplevel: "
                << toplevel << std::endl);

  if (WantsComponentInstallation()) {
    // CASE 1 : COMPONENT ALL-IN-ONE package
    // If ALL COMPONENTS in ONE package has been requested
    // then the package file is unique and should be open here.
    if (componentPackageMethod == ONE_PACKAGE)
      {
      return PackageComponentsAllInOne();
      }
    // CASE 2 : COMPONENT CLASSICAL package(s) (i.e. not all-in-one)
    // There will be 1 package for each component group
    // however one may require to ignore component group and
    // in this case you'll get 1 package for each component.
    else
      {
      return PackageComponents(componentPackageMethod ==
                               ONE_PACKAGE_PER_COMPONENT);
      }
  }

  // CASE 3 : NON COMPONENT package.
  DECLARE_AND_OPEN_ARCHIVE(packageFileNames[0],archive);
  std::vector<std::string>::const_iterator fileIt;
  std::string dir = cmSystemTools::GetCurrentWorkingDirectory();
  cmSystemTools::ChangeDirectory(toplevel.c_str());
  for ( fileIt = files.begin(); fileIt != files.end(); ++ fileIt )
    {
    // Get the relative path to the file
    std::string rp = cmSystemTools::RelativePath(toplevel.c_str(),
                                                 fileIt->c_str());
    archive.Add(rp);
    if(!archive)
      {
      cmCPackLogger(cmCPackLog::LOG_ERROR, "Problem while adding file< "
          << *fileIt
          << "> to archive <"
          << packageFileNames[0] << "> .ERROR ="
          << archive.GetError()
          << std::endl);
      return 0;
      }
    }
  cmSystemTools::ChangeDirectory(dir.c_str());
  // The destructor of cmArchiveWrite will close and finish the write
  return 1;
}

//----------------------------------------------------------------------
int cmCPackArchiveGenerator::GenerateHeader(std::ostream*)
{
  return 1;
}

bool cmCPackArchiveGenerator::SupportsComponentInstallation() const {
  // The Component installation support should only
  // be activated if explicitly requested by the user
  // (for backward compatibility reason)
  if (IsOn("CPACK_ARCHIVE_COMPONENT_INSTALL"))
    {
    return true;
    }
  else
    {
    return false;
    }
}
