/*============================================================================
  CMake - Cross Platform Makefile Generator
  Copyright 2000-2012 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 "cmCPackWIXGenerator.h"

#include <cmSystemTools.h>
#include <cmGeneratedFileStream.h>
#include <CPack/cmCPackLog.h>
#include <CPack/cmCPackComponentGroup.h>

#include "cmWIXSourceWriter.h"
#include "cmWIXRichTextFormatWriter.h"

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

#include <rpc.h> // for GUID generation

int cmCPackWIXGenerator::InitializeInternal()
{
  componentPackageMethod = ONE_PACKAGE;

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

bool cmCPackWIXGenerator::RunWiXCommand(const std::string& command)
{
  std::string cpackTopLevel;
  if(!RequireOption("CPACK_TOPLEVEL_DIRECTORY", cpackTopLevel))
    {
    return false;
    }

  std::string logFileName = cpackTopLevel + "/wix.log";

  cmCPackLogger(cmCPackLog::LOG_DEBUG,
    "Running WiX command: " << command << std::endl);

  std::string output;

  int returnValue = 0;
  bool status = cmSystemTools::RunSingleCommand(command.c_str(), &output,
    &returnValue, 0, cmSystemTools::OUTPUT_NONE);

  std::ofstream logFile(logFileName.c_str(), std::ios::app);
  logFile << command << std::endl;
  logFile << output;
  logFile.close();

  if(!status || returnValue)
    {
    cmCPackLogger(cmCPackLog::LOG_ERROR,
      "Problem running WiX candle. "
      "Please check '" << logFileName << "' for errors." << std::endl);

    return false;
    }

  return true;
}

bool cmCPackWIXGenerator::RunCandleCommand(
  const std::string& sourceFile, const std::string& objectFile)
{
  std::string executable;
  if(!RequireOption("CPACK_WIX_CANDLE_EXECUTABLE", executable))
    {
    return false;
    }

  std::stringstream command;
  command << QuotePath(executable);
  command << " -nologo";
  command << " -arch " << GetArchitecture();
  command << " -out " << QuotePath(objectFile);
  command << " " << QuotePath(sourceFile);

  return RunWiXCommand(command.str());
}

bool cmCPackWIXGenerator::RunLightCommand(const std::string& objectFiles)
{
  std::string executable;
  if(!RequireOption("CPACK_WIX_LIGHT_EXECUTABLE", executable))
    {
    return false;
    }

  std::stringstream command;
  command << QuotePath(executable);
  command << " -nologo";
  command << " -out " << QuotePath(packageFileNames.at(0));
  command << " -ext WixUIExtension";
  const char* const cultures = GetOption("CPACK_WIX_CULTURES");
  if(cultures)
    {
    command << " -cultures:" << cultures;
    }
  command << " " << objectFiles;

  return RunWiXCommand(command.str());
}

int cmCPackWIXGenerator::PackageFiles()
{
  if(!PackageFilesImpl() || cmSystemTools::GetErrorOccuredFlag())
    {
    cmCPackLogger(cmCPackLog::LOG_ERROR,
      "Fatal WiX Generator Error" << std::endl);
    return false;
    }

  return true;
}

bool cmCPackWIXGenerator::InitializeWiXConfiguration()
{
  if(!ReadListFile("CPackWIX.cmake"))
    {
    cmCPackLogger(cmCPackLog::LOG_ERROR,
      "Error while executing CPackWIX.cmake" << std::endl);
    return false;
    }

  if(GetOption("CPACK_WIX_PRODUCT_GUID") == 0)
    {
    std::string guid = GenerateGUID();
    SetOption("CPACK_WIX_PRODUCT_GUID", guid.c_str());

    cmCPackLogger(cmCPackLog::LOG_VERBOSE,
      "CPACK_WIX_PRODUCT_GUID implicitly set to " << guid << " . "
      << std::endl);
    }

  if(GetOption("CPACK_WIX_UPGRADE_GUID") == 0)
    {
    std::string guid = GenerateGUID();
    SetOption("CPACK_WIX_UPGRADE_GUID", guid.c_str());

    cmCPackLogger(cmCPackLog::LOG_WARNING,
      "CPACK_WIX_UPGRADE_GUID implicitly set to " << guid << " . "
      "Please refer to the documentation on how and why "
      "you might want to set this explicitly." << std::endl);
    }

  std::string cpackTopLevel;
  if(!RequireOption("CPACK_TOPLEVEL_DIRECTORY", cpackTopLevel))
    {
    return false;
    }

  if(GetOption("CPACK_WIX_LICENSE_RTF") == 0)
    {
    std::string licenseFilename = cpackTopLevel + "/License.rtf";
    SetOption("CPACK_WIX_LICENSE_RTF", licenseFilename.c_str());

    if(!CreateLicenseFile())
      {
      return false;
      }
    }

  return true;
}

bool cmCPackWIXGenerator::PackageFilesImpl()
{
  if(!InitializeWiXConfiguration())
    {
    return false;
    }

  if(!CreateWiXVariablesIncludeFile())
    {
    return false;
    }

  if(!CreateWiXSourceFiles())
    {
    return false;
    }

  std::stringstream objectFiles;
  for(size_t i = 0; i < wixSources.size(); ++i)
    {
    const std::string& sourceFilename = wixSources[i];

    std::string objectFilename =
      cmSystemTools::GetFilenameWithoutExtension(sourceFilename) + ".wixobj";

    if(!RunCandleCommand(sourceFilename, objectFilename))
      {
      return false;
      }

    objectFiles << " " << QuotePath(objectFilename);
    }

  return RunLightCommand(objectFiles.str());
}

bool cmCPackWIXGenerator::CreateWiXVariablesIncludeFile()
{
  std::string cpackTopLevel;
  if(!RequireOption("CPACK_TOPLEVEL_DIRECTORY", cpackTopLevel))
    {
    return false;
    }

  std::string includeFilename =
    cpackTopLevel + "/cpack_variables.wxi";

  cmWIXSourceWriter includeFile(Logger, includeFilename, true);
  CopyDefinition(includeFile, "CPACK_WIX_PRODUCT_GUID");
  CopyDefinition(includeFile, "CPACK_WIX_UPGRADE_GUID");
  CopyDefinition(includeFile, "CPACK_PACKAGE_VENDOR");
  CopyDefinition(includeFile, "CPACK_PACKAGE_NAME");
  CopyDefinition(includeFile, "CPACK_PACKAGE_VERSION");
  CopyDefinition(includeFile, "CPACK_WIX_LICENSE_RTF");
  CopyDefinition(includeFile, "CPACK_WIX_PRODUCT_ICON");
  CopyDefinition(includeFile, "CPACK_WIX_UI_BANNER");
  CopyDefinition(includeFile, "CPACK_WIX_UI_DIALOG");
  SetOptionIfNotSet("CPACK_WIX_PROGRAM_MENU_FOLDER",
    GetOption("CPACK_PACKAGE_NAME"));
  CopyDefinition(includeFile, "CPACK_WIX_PROGRAM_MENU_FOLDER");

  return true;
}

void cmCPackWIXGenerator::CopyDefinition(
  cmWIXSourceWriter &source, const std::string &name)
{
  const char* value = GetOption(name.c_str());
  if(value)
    {
    AddDefinition(source, name, value);
    }
}

void cmCPackWIXGenerator::AddDefinition(cmWIXSourceWriter& source,
  const std::string& name, const std::string& value)
{
  std::stringstream tmp;
  tmp << name << "=\"" << value << '"';

  source.AddProcessingInstruction("define",
    cmWIXSourceWriter::WindowsCodepageToUtf8(tmp.str()));
}

bool cmCPackWIXGenerator::CreateWiXSourceFiles()
{
  std::string cpackTopLevel;
  if(!RequireOption("CPACK_TOPLEVEL_DIRECTORY", cpackTopLevel))
    {
    return false;
    }

  std::string directoryDefinitionsFilename =
    cpackTopLevel + "/directories.wxs";

  wixSources.push_back(directoryDefinitionsFilename);

  cmWIXSourceWriter directoryDefinitions(Logger, directoryDefinitionsFilename);
  directoryDefinitions.BeginElement("Fragment");

  directoryDefinitions.BeginElement("Directory");
  directoryDefinitions.AddAttribute("Id", "TARGETDIR");
  directoryDefinitions.AddAttribute("Name", "SourceDir");

  directoryDefinitions.BeginElement("Directory");
  if(GetArchitecture() == "x86")
    {
    directoryDefinitions.AddAttribute("Id", "ProgramFilesFolder");
    }
  else
    {
    directoryDefinitions.AddAttribute("Id", "ProgramFiles64Folder");
    }

  std::vector<std::string> install_root;

  std::string tmp;
  if(!RequireOption("CPACK_PACKAGE_INSTALL_DIRECTORY", tmp))
    {
    return false;
    }

  cmSystemTools::SplitPath(tmp.c_str(), install_root);

  if(!install_root.empty() && install_root.back().empty())
    {
    install_root.pop_back();
    }

  for(size_t i = 1; i < install_root.size(); ++i)
    {
    directoryDefinitions.BeginElement("Directory");

    if(i == install_root.size() - 1)
      {
      directoryDefinitions.AddAttribute("Id", "INSTALL_ROOT");
      }
    else
      {
      std::stringstream ss;
      ss << "INSTALL_PREFIX_" << i;
      directoryDefinitions.AddAttribute("Id", ss.str());
      }

    directoryDefinitions.AddAttribute("Name", install_root[i]);
  }

  size_t directoryCounter = 0;
  size_t fileCounter = 0;

  std::string fileDefinitionsFilename =
    cpackTopLevel + "/files.wxs";

  wixSources.push_back(fileDefinitionsFilename);

  cmWIXSourceWriter fileDefinitions(Logger, fileDefinitionsFilename);
  fileDefinitions.BeginElement("Fragment");

  std::string featureDefinitionsFilename =
      cpackTopLevel +"/features.wxs";

  wixSources.push_back(featureDefinitionsFilename);

  cmWIXSourceWriter featureDefinitions(Logger, featureDefinitionsFilename);
  featureDefinitions.BeginElement("Fragment");

  featureDefinitions.BeginElement("Feature");
  featureDefinitions.AddAttribute("Id", "ProductFeature");
  featureDefinitions.AddAttribute("Title", Name);
  featureDefinitions.AddAttribute("Level", "1");
  featureDefinitions.EndElement();

  featureDefinitions.BeginElement("FeatureRef");
  featureDefinitions.AddAttribute("Id", "ProductFeature");

  const char *cpackPackageExecutables = GetOption("CPACK_PACKAGE_EXECUTABLES");
  std::vector<std::string> cpackPkgExecutables;
  std::string regKey;
  if ( cpackPackageExecutables )
    {
    cmSystemTools::ExpandListArgument(cpackPackageExecutables,
      cpackPkgExecutables);
    if ( cpackPkgExecutables.size() % 2 != 0 )
      {
      cmCPackLogger(cmCPackLog::LOG_ERROR,
        "CPACK_PACKAGE_EXECUTABLES should contain pairs of <executable> and "
        "<icon name>." << std::endl);
      cpackPkgExecutables.clear();
      }

    const char *cpackVendor = GetOption("CPACK_PACKAGE_VENDOR");
    const char *cpackPkgName = GetOption("CPACK_PACKAGE_NAME");
    if (!cpackVendor || !cpackPkgName)
      {
      cmCPackLogger(cmCPackLog::LOG_WARNING, "CPACK_PACKAGE_VENDOR and "
                    "CPACK_PACKAGE_NAME must be defined for shortcut creation" << std::endl);
      cpackPkgExecutables.clear();
      }
    else
      {
        regKey = std::string("Software/") + cpackVendor + "/" + cpackPkgName;
      }
    }

  std::vector<std::string> dirIdExecutables;
  AddDirectoryAndFileDefinitons(
    toplevel, "INSTALL_ROOT",
    directoryDefinitions, fileDefinitions, featureDefinitions,
    directoryCounter, fileCounter, cpackPkgExecutables, dirIdExecutables);

  directoryDefinitions.EndElement();
  directoryDefinitions.EndElement();

  if (dirIdExecutables.size() > 0 && dirIdExecutables.size() % 3 == 0)
    {
    fileDefinitions.BeginElement("DirectoryRef");
    fileDefinitions.AddAttribute("Id", "PROGRAM_MENU_FOLDER");
    fileDefinitions.BeginElement("Component");
    fileDefinitions.AddAttribute("Id", "SHORTCUT");
    fileDefinitions.AddAttribute("Guid", "*");

    std::vector<std::string>::iterator it;
    for ( it = dirIdExecutables.begin() ;
          it != dirIdExecutables.end();
          ++it)
      {
      std::string fileName = *it++;
      std::string iconName = *it++;
      std::string directoryId = *it;

      fileDefinitions.BeginElement("Shortcut");
      std::string shortcutName = fileName; // the iconName is mor likely to contain blanks early on
      std::string::size_type const dotPos = shortcutName.find('.');
      if(std::string::npos == dotPos)
        { shortcutName = shortcutName.substr(0, dotPos); }
      fileDefinitions.AddAttribute("Id", "SHORTCUT_" + shortcutName);
      fileDefinitions.AddAttribute("Name", iconName);
      std::string target = "[" + directoryId + "]" + fileName;
      fileDefinitions.AddAttribute("Target", target);
      fileDefinitions.AddAttribute("WorkingDirectory", directoryId);
      fileDefinitions.EndElement();
      }
    fileDefinitions.BeginElement("Shortcut");
    fileDefinitions.AddAttribute("Id", "UNINSTALL");
    std::string pkgName = GetOption("CPACK_PACKAGE_NAME");
    fileDefinitions.AddAttribute("Name", "Uninstall " + pkgName);
    fileDefinitions.AddAttribute("Description", "Uninstalls " + pkgName);
    fileDefinitions.AddAttribute("Target", "[SystemFolder]msiexec.exe");
    fileDefinitions.AddAttribute("Arguments", "/x [ProductCode]");
    fileDefinitions.EndElement();
    fileDefinitions.BeginElement("RemoveFolder");
    fileDefinitions.AddAttribute("Id", "PROGRAM_MENU_FOLDER");
    fileDefinitions.AddAttribute("On", "uninstall");
    fileDefinitions.EndElement();
    fileDefinitions.BeginElement("RegistryValue");
    fileDefinitions.AddAttribute("Root", "HKCU");
    fileDefinitions.AddAttribute("Key", regKey);
    fileDefinitions.AddAttribute("Name", "installed");
    fileDefinitions.AddAttribute("Type", "integer");
    fileDefinitions.AddAttribute("Value", "1");
    fileDefinitions.AddAttribute("KeyPath", "yes");

    featureDefinitions.BeginElement("ComponentRef");
    featureDefinitions.AddAttribute("Id", "SHORTCUT");
    featureDefinitions.EndElement();
    directoryDefinitions.BeginElement("Directory");
    directoryDefinitions.AddAttribute("Id", "ProgramMenuFolder");
    directoryDefinitions.BeginElement("Directory");
    directoryDefinitions.AddAttribute("Id", "PROGRAM_MENU_FOLDER");
    const char *startMenuFolder = GetOption("CPACK_WIX_PROGRAM_MENU_FOLDER");
    directoryDefinitions.AddAttribute("Name", startMenuFolder);
  }

  featureDefinitions.EndElement();
  featureDefinitions.EndElement();
  fileDefinitions.EndElement();
  directoryDefinitions.EndElement();

  std::string wixTemplate = FindTemplate("WIX.template.in");
  if(GetOption("CPACK_WIX_TEMPLATE") != 0)
    {
    wixTemplate = GetOption("CPACK_WIX_TEMPLATE");
    }
  if(wixTemplate.empty())
    {
    cmCPackLogger(cmCPackLog::LOG_ERROR,
      "Could not find CPack WiX template file WIX.template.in" << std::endl);
    return false;
    }

  std::string mainSourceFilePath = cpackTopLevel + "/main.wxs";

  if(!ConfigureFile(wixTemplate.c_str(), mainSourceFilePath .c_str()))
    {
    cmCPackLogger(cmCPackLog::LOG_ERROR,
      "Failed creating '" << mainSourceFilePath  <<
      "'' from template." << std::endl);

    return false;
    }

  wixSources.push_back(mainSourceFilePath);

  return true;
}

bool cmCPackWIXGenerator::CreateLicenseFile()
{
  std::string licenseSourceFilename;
  if(!RequireOption("CPACK_RESOURCE_FILE_LICENSE", licenseSourceFilename))
    {
    return false;
    }

  std::string licenseDestinationFilename;
  if(!RequireOption("CPACK_WIX_LICENSE_RTF", licenseDestinationFilename))
    {
    return false;
    }

  std::string extension = GetRightmostExtension(licenseSourceFilename);

  if(extension == ".rtf")
    {
    cmSystemTools::CopyAFile(
      licenseSourceFilename.c_str(),
      licenseDestinationFilename.c_str());
    }
  else if(extension == ".txt")
    {
    cmWIXRichTextFormatWriter rtfWriter(licenseDestinationFilename);

    std::ifstream licenseSource(licenseSourceFilename.c_str());

    std::string line;
    while(std::getline(licenseSource, line))
      {
      rtfWriter.AddText(line);
      rtfWriter.AddText("\n");
      }
    }
  else
    {
    cmCPackLogger(cmCPackLog::LOG_ERROR,
      "unsupported WiX License file extension '" <<
      extension << "'" << std::endl);

    return false;
    }

  return true;
}

void cmCPackWIXGenerator::AddDirectoryAndFileDefinitons(
  const std::string& topdir,
  const std::string& directoryId,
  cmWIXSourceWriter& directoryDefinitions,
  cmWIXSourceWriter& fileDefinitions,
  cmWIXSourceWriter& featureDefinitions,
  size_t& directoryCounter,
  size_t& fileCounter,
  const std::vector<std::string>& pkgExecutables,
  std::vector<std::string>& dirIdExecutables)
{
  cmsys::Directory dir;
  dir.Load(topdir.c_str());

  for(size_t i = 0; i < dir.GetNumberOfFiles(); ++i)
    {
    std::string fileName = dir.GetFile(static_cast<unsigned long>(i));

    if(fileName == "." || fileName == "..")
      {
      continue;
      }

    std::string fullPath = topdir + "/" + fileName;

    if(cmSystemTools::FileIsDirectory(fullPath.c_str()))
      {
      std::stringstream tmp;
      tmp << "DIR_ID_" << ++directoryCounter;
      std::string subDirectoryId = tmp.str();

      directoryDefinitions.BeginElement("Directory");
      directoryDefinitions.AddAttribute("Id", subDirectoryId);
      directoryDefinitions.AddAttribute("Name", fileName);

      AddDirectoryAndFileDefinitons(
        fullPath, subDirectoryId,
        directoryDefinitions,
        fileDefinitions,
        featureDefinitions,
        directoryCounter,
        fileCounter,
        pkgExecutables,
        dirIdExecutables);
      directoryDefinitions.EndElement();
      }
    else
      {
      std::stringstream tmp;
      tmp << "_ID_" << ++fileCounter;
      std::string idSuffix = tmp.str();

      std::string componentId = std::string("CMP") + idSuffix;
      std::string fileId = std::string("FILE") + idSuffix;

      fileDefinitions.BeginElement("DirectoryRef");
      fileDefinitions.AddAttribute("Id", directoryId);

      fileDefinitions.BeginElement("Component");
      fileDefinitions.AddAttribute("Id", componentId);
      fileDefinitions.AddAttribute("Guid", "*");

      fileDefinitions.BeginElement("File");
      fileDefinitions.AddAttribute("Id", fileId);
      fileDefinitions.AddAttribute("Source", fullPath);
      fileDefinitions.AddAttribute("KeyPath", "yes");

      fileDefinitions.EndElement();
      fileDefinitions.EndElement();
      fileDefinitions.EndElement();

      featureDefinitions.BeginElement("ComponentRef");
      featureDefinitions.AddAttribute("Id", componentId);
      featureDefinitions.EndElement();

      std::vector<std::string>::const_iterator it;
      for (it = pkgExecutables.begin() ;
           it != pkgExecutables.end() ;
           ++it)
        {
        std::string execName = *it++;
        std::string iconName = *it;

        if (cmSystemTools::LowerCase(fileName) ==
            cmSystemTools::LowerCase(execName) + ".exe")
          {
            dirIdExecutables.push_back(fileName);
            dirIdExecutables.push_back(iconName);
            dirIdExecutables.push_back(directoryId);
          }
        }
      }
    }
}

bool cmCPackWIXGenerator::RequireOption(
  const std::string& name, std::string &value) const
{
  const char* tmp = GetOption(name.c_str());
  if(tmp)
    {
    value = tmp;

    return true;
    }
  else
    {
    cmCPackLogger(cmCPackLog::LOG_ERROR,
      "Required variable " << name << " not set" << std::endl);

    return false;
    }
}

std::string cmCPackWIXGenerator::GetArchitecture() const
{
  std::string void_p_size;
  RequireOption("CPACK_WIX_SIZEOF_VOID_P", void_p_size);

  if(void_p_size == "8")
    {
    return "x64";
    }
  else
    {
    return "x86";
    }
}

std::string cmCPackWIXGenerator::GenerateGUID()
{
  UUID guid;
  UuidCreate(&guid);

  unsigned char *tmp = 0;
  UuidToString(&guid, &tmp);

  std::string result(reinterpret_cast<char*>(tmp));
  RpcStringFree(&tmp);

  return cmSystemTools::UpperCase(result);
}

std::string cmCPackWIXGenerator::QuotePath(const std::string& path)
{
  return std::string("\"") + path + '"';
}

std::string cmCPackWIXGenerator::GetRightmostExtension(
  const std::string& filename)
{
  std::string extension;

  std::string::size_type i = filename.rfind(".");
  if(i != std::string::npos)
    {
    extension = filename.substr(i);
    }

  return cmSystemTools::LowerCase(extension);
}
