/*============================================================================
  CMake - Cross Platform Makefile Generator
  Copyright 2015 Geoffrey Viola <geoffrey.viola@asirobots.com>

  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 "cmGlobalGhsMultiGenerator.h"
#include "cmLocalGhsMultiGenerator.h"
#include "cmMakefile.h"
#include "cmVersion.h"
#include "cmGeneratedFileStream.h"
#include "cmGhsMultiTargetGenerator.h"
#include <cmsys/SystemTools.hxx>
#include <cmAlgorithms.h>

const char *cmGlobalGhsMultiGenerator::FILE_EXTENSION = ".gpj";
const char *cmGlobalGhsMultiGenerator::DEFAULT_MAKE_PROGRAM = "gbuild";

cmGlobalGhsMultiGenerator::cmGlobalGhsMultiGenerator(cmake* cm)
  : cmGlobalGenerator(cm), OSDirRelative(false)
{
  this->GhsBuildCommandInitialized = false;
}

cmGlobalGhsMultiGenerator::~cmGlobalGhsMultiGenerator()
{
  cmDeleteAll(TargetFolderBuildStreams);
}

cmLocalGenerator *
cmGlobalGhsMultiGenerator::CreateLocalGenerator(cmMakefile* mf)
{
  return new cmLocalGhsMultiGenerator(this, mf);
}

void cmGlobalGhsMultiGenerator::GetDocumentation(cmDocumentationEntry &entry)
{
  entry.Name = GetActualName();
  entry.Brief =
    "Generates Green Hills MULTI files (experimental, work-in-progress).";
}

void cmGlobalGhsMultiGenerator::EnableLanguage(
  std::vector<std::string> const &l, cmMakefile *mf, bool optional)
{
  mf->AddDefinition("CMAKE_SYSTEM_NAME", "GHS-MULTI");
  mf->AddDefinition("CMAKE_SYSTEM_PROCESSOR", "ARM");

  const std::string ghsCompRoot(GetCompRoot());
  mf->AddDefinition("GHS_COMP_ROOT", ghsCompRoot.c_str());
  std::string ghsCompRootStart =
    0 == ghsCompRootStart.size() ? "" : ghsCompRoot + "/";
  mf->AddDefinition("CMAKE_C_COMPILER",
                    std::string(ghsCompRootStart + "ccarm.exe").c_str());
  mf->AddDefinition("CMAKE_C_COMPILER_ID_RUN", "TRUE");
  mf->AddDefinition("CMAKE_C_COMPILER_ID", "GHS");
  mf->AddDefinition("CMAKE_C_COMPILER_FORCED", "TRUE");

  mf->AddDefinition("CMAKE_CXX_COMPILER",
                    std::string(ghsCompRootStart + "cxarm.exe").c_str());
  mf->AddDefinition("CMAKE_CXX_COMPILER_ID_RUN", "TRUE");
  mf->AddDefinition("CMAKE_CXX_COMPILER_ID", "GHS");
  mf->AddDefinition("CMAKE_CXX_COMPILER_FORCED", "TRUE");

  if (!ghsCompRoot.empty())
    {
    static const char *compPreFix = "comp_";
    std::string compFilename =
      cmsys::SystemTools::FindLastString(ghsCompRoot.c_str(), compPreFix);
    cmsys::SystemTools::ReplaceString(compFilename, compPreFix, "");
    mf->AddDefinition("CMAKE_SYSTEM_VERSION", compFilename.c_str());
    }

  mf->AddDefinition("GHSMULTI", "1"); // identifier for user CMake files
  this->cmGlobalGenerator::EnableLanguage(l, mf, optional);
}

void cmGlobalGhsMultiGenerator::FindMakeProgram(cmMakefile *mf)
{
  // The GHS generator knows how to lookup its build tool
  // directly instead of needing a helper module to do it, so we
  // do not actually need to put CMAKE_MAKE_PROGRAM into the cache.
  if (cmSystemTools::IsOff(mf->GetDefinition("CMAKE_MAKE_PROGRAM")))
    {
    mf->AddDefinition("CMAKE_MAKE_PROGRAM",
                      this->GetGhsBuildCommand().c_str());
    }
}

std::string const &cmGlobalGhsMultiGenerator::GetGhsBuildCommand()
{
  if (!this->GhsBuildCommandInitialized)
    {
    this->GhsBuildCommandInitialized = true;
    this->GhsBuildCommand = this->FindGhsBuildCommand();
    }
  return this->GhsBuildCommand;
}

std::string cmGlobalGhsMultiGenerator::FindGhsBuildCommand()
{
  std::vector<std::string> userPaths;
  userPaths.push_back(this->GetCompRoot());
  std::string makeProgram =
    cmSystemTools::FindProgram(DEFAULT_MAKE_PROGRAM, userPaths);
  if (makeProgram.empty())
    {
    makeProgram = DEFAULT_MAKE_PROGRAM;
    }
  return makeProgram;
}

std::string cmGlobalGhsMultiGenerator::GetCompRoot()
{
  std::string output;

  const std::vector<std::string>
    potentialDirsHardPaths(GetCompRootHardPaths());
  const std::vector<std::string> potentialDirsRegistry(GetCompRootRegistry());

  std::vector<std::string> potentialDirsComplete;
  potentialDirsComplete.insert(potentialDirsComplete.end(),
                               potentialDirsHardPaths.begin(),
                               potentialDirsHardPaths.end());
  potentialDirsComplete.insert(potentialDirsComplete.end(),
                               potentialDirsRegistry.begin(),
                               potentialDirsRegistry.end());

  // Use latest version
  std::string outputDirName;
  for (std::vector<std::string>::const_iterator potentialDirsCompleteIt =
         potentialDirsComplete.begin();
       potentialDirsCompleteIt != potentialDirsComplete.end();
       ++potentialDirsCompleteIt)
    {
    const std::string dirName(
      cmsys::SystemTools::GetFilenameName(*potentialDirsCompleteIt));
    if (dirName.compare(outputDirName) > 0)
      {
      output = *potentialDirsCompleteIt;
      outputDirName = dirName;
      }
    }

  return output;
}

std::vector<std::string> cmGlobalGhsMultiGenerator::GetCompRootHardPaths()
{
  std::vector<std::string> output;
  cmSystemTools::Glob("C:/ghs", "comp_[^;]+", output);
  for (std::vector<std::string>::iterator outputIt = output.begin();
       outputIt != output.end(); ++outputIt)
    {
    *outputIt = "C:/ghs/" + *outputIt;
    }
  return output;
}

std::vector<std::string> cmGlobalGhsMultiGenerator::GetCompRootRegistry()
{
  std::vector<std::string> output(2);
  cmsys::SystemTools::ReadRegistryValue(
    "HKEY_LOCAL_"
    "MACHINE\\SOFTWARE\\Wow6432Node\\Microsoft\\"
    "Windows\\CurrentVersion\\Uninstall\\"
    "GreenHillsSoftwared771f1b4;InstallLocation",
    output[0]);
  cmsys::SystemTools::ReadRegistryValue(
    "HKEY_LOCAL_"
    "MACHINE\\SOFTWARE\\Wow6432Node\\Microsoft\\"
    "Windows\\CurrentVersion\\Uninstall\\"
    "GreenHillsSoftware9881cef6;InstallLocation",
    output[1]);
  return output;
}

void cmGlobalGhsMultiGenerator::OpenBuildFileStream(
  std::string const &filepath, cmGeneratedFileStream **filestream)
{
  // Get a stream where to generate things.
  if (NULL == *filestream)
    {
    *filestream = new cmGeneratedFileStream(filepath.c_str());
    if (NULL != *filestream)
      {
      OpenBuildFileStream(*filestream);
      }
    }
}

void cmGlobalGhsMultiGenerator::OpenBuildFileStream(
  cmGeneratedFileStream *filestream)
{
  *filestream << "#!gbuild" << std::endl;
}

void cmGlobalGhsMultiGenerator::OpenBuildFileStream()
{
  // Compute GHS MULTI's build file path.
  std::string buildFilePath =
    this->GetCMakeInstance()->GetHomeOutputDirectory();
  buildFilePath += "/";
  buildFilePath += "default";
  buildFilePath += FILE_EXTENSION;

  this->Open(std::string(""), buildFilePath, &this->TargetFolderBuildStreams);
  OpenBuildFileStream(GetBuildFileStream());

  char const *osDir =
    this->GetCMakeInstance()->GetCacheDefinition("GHS_OS_DIR");
  if (NULL == osDir)
    {
    osDir = "";
    cmSystemTools::Error("GHS_OS_DIR cache variable must be set");
    }
  else
    {
    this->GetCMakeInstance()->MarkCliAsUsed("GHS_OS_DIR");
    }
  std::string fOSDir(this->trimQuotes(osDir));
  cmSystemTools::ReplaceString(fOSDir, "\\", "/");
  if (!fOSDir.empty() && ('c' == fOSDir[0] || 'C' == fOSDir[0]))
    {
    this->OSDirRelative = false;
    }
  else
    {
    this->OSDirRelative = true;
    }

  char const *bspName =
    this->GetCMakeInstance()->GetCacheDefinition("GHS_BSP_NAME");
  if (NULL == bspName)
    {
    bspName = "";
    cmSystemTools::Error("GHS_BSP_NAME cache variable must be set");
    }
  else
    {
    this->GetCMakeInstance()->MarkCliAsUsed("GHS_BSP_NAME");
    }
  std::string fBspName(this->trimQuotes(bspName));
  cmSystemTools::ReplaceString(fBspName, "\\", "/");
  this->WriteMacros();
  this->WriteHighLevelDirectives();

  GhsMultiGpj::WriteGpjTag(GhsMultiGpj::PROJECT, this->GetBuildFileStream());
  this->WriteDisclaimer(this->GetBuildFileStream());
  *this->GetBuildFileStream() << "# Top Level Project File" << std::endl;
  if (!fBspName.empty())
    {
    *this->GetBuildFileStream() << "    -bsp " << fBspName << std::endl;
    }
  this->WriteCompilerOptions(fOSDir);
}

void cmGlobalGhsMultiGenerator::CloseBuildFileStream(
  cmGeneratedFileStream **filestream)
{
  if (filestream)
    {
    delete *filestream;
    *filestream = NULL;
    }
  else
    {
    cmSystemTools::Error("Build file stream was not open.");
    }
}

void cmGlobalGhsMultiGenerator::Generate()
{
  this->cmGlobalGenerator::Generate();

  if (!this->LocalGenerators.empty())
    {
    this->OpenBuildFileStream();

    // Build all the folder build files
    for (unsigned int i = 0; i < this->LocalGenerators.size(); ++i)
      {
      cmLocalGhsMultiGenerator *lg =
        static_cast<cmLocalGhsMultiGenerator *>(this->LocalGenerators[i]);
      cmGeneratorTargetsType tgts = lg->GetMakefile()->GetGeneratorTargets();
      this->UpdateBuildFiles(&tgts);
      }
    }

  cmDeleteAll(TargetFolderBuildStreams);
  this->TargetFolderBuildStreams.clear();
}

void cmGlobalGhsMultiGenerator::GenerateBuildCommand(
  std::vector<std::string> &makeCommand, const std::string &makeProgram,
  const std::string & /*projectName*/, const std::string & /*projectDir*/,
  const std::string &targetName, const std::string & /*config*/,
  bool /*fast*/, bool /*verbose*/,
  std::vector<std::string> const &makeOptions)
{
  makeCommand.push_back(
    this->SelectMakeProgram(makeProgram, this->GetGhsBuildCommand())
    );

  makeCommand.insert(makeCommand.end(),
                     makeOptions.begin(), makeOptions.end());
  if (!targetName.empty())
    {
    if (targetName == "clean")
      {
      makeCommand.push_back("-clean");
      }
    else
      {
      makeCommand.push_back(targetName);
      }
    }
}

void cmGlobalGhsMultiGenerator::WriteMacros()
{
  char const *ghsGpjMacros =
    this->GetCMakeInstance()->GetCacheDefinition("GHS_GPJ_MACROS");
  if (NULL != ghsGpjMacros)
    {
    std::vector<std::string> expandedList;
    cmSystemTools::ExpandListArgument(std::string(ghsGpjMacros), expandedList);
    for (std::vector<std::string>::const_iterator expandedListI =
           expandedList.begin();
         expandedListI != expandedList.end(); ++expandedListI)
      {
      *this->GetBuildFileStream() << "macro " << *expandedListI << std::endl;
      }
    }
}

void cmGlobalGhsMultiGenerator::WriteHighLevelDirectives()
{
  *this->GetBuildFileStream() << "primaryTarget=arm_integrity.tgt"
                              << std::endl;
  char const *const customization =
    this->GetCMakeInstance()->GetCacheDefinition("GHS_CUSTOMIZATION");
  if (NULL != customization && strlen(customization) > 0)
    {
    *this->GetBuildFileStream() << "customization="
                                << trimQuotes(customization)
                                << std::endl;
    this->GetCMakeInstance()->MarkCliAsUsed("GHS_CUSTOMIZATION");
    }
}

void cmGlobalGhsMultiGenerator::WriteCompilerOptions(std::string const &fOSDir)
{
  *this->GetBuildFileStream() << "    -os_dir=\"" << fOSDir << "\""
                              << std::endl;
}

void cmGlobalGhsMultiGenerator::WriteDisclaimer(std::ostream *os)
{
  (*os) << "#" << std::endl
        << "# CMAKE generated file: DO NOT EDIT!" << std::endl
        << "# Generated by \"" << GetActualName() << "\""
        << " Generator, CMake Version " << cmVersion::GetMajorVersion() << "."
        << cmVersion::GetMinorVersion() << std::endl
        << "#" << std::endl;
}

void cmGlobalGhsMultiGenerator::AddFilesUpToPath(
  cmGeneratedFileStream *mainBuildFile,
  std::map<std::string, cmGeneratedFileStream *> *targetFolderBuildStreams,
  char const *homeOutputDirectory, std::string const &path,
  GhsMultiGpj::Types projType, std::string const &relPath)
{
  std::string workingPath(path);
  cmSystemTools::ConvertToUnixSlashes(workingPath);
  std::vector<cmsys::String> splitPath =
    cmSystemTools::SplitString(workingPath);
  std::string workingRelPath(relPath);
  cmSystemTools::ConvertToUnixSlashes(workingRelPath);
  if (!workingRelPath.empty())
    {
    workingRelPath += "/";
    }
  std::string pathUpTo;
  for (std::vector<cmsys::String>::const_iterator splitPathI =
         splitPath.begin();
       splitPath.end() != splitPathI; ++splitPathI)
    {
    pathUpTo += *splitPathI;
    if (targetFolderBuildStreams->end() ==
        targetFolderBuildStreams->find(pathUpTo))
      {
      AddFilesUpToPathNewBuildFile(
        mainBuildFile, targetFolderBuildStreams, homeOutputDirectory,
        pathUpTo, splitPath.begin() == splitPathI, workingRelPath, projType);
      }
    AddFilesUpToPathAppendNextFile(targetFolderBuildStreams, pathUpTo,
                                   splitPathI, splitPath.end(), projType);
    pathUpTo += "/";
    }
}

void cmGlobalGhsMultiGenerator::Open(
  std::string const &mapKeyName, std::string const &fileName,
  std::map<std::string, cmGeneratedFileStream *> *fileMap)
{
  if (fileMap->end() == fileMap->find(fileName))
    {
    cmGeneratedFileStream *temp(new cmGeneratedFileStream);
    temp->open(fileName.c_str());
    (*fileMap)[mapKeyName] = temp;
    }
}

void cmGlobalGhsMultiGenerator::AddFilesUpToPathNewBuildFile(
  cmGeneratedFileStream *mainBuildFile,
  std::map<std::string, cmGeneratedFileStream *> *targetFolderBuildStreams,
  char const *homeOutputDirectory, std::string const &pathUpTo,
  bool const isFirst, std::string const &relPath,
  GhsMultiGpj::Types const projType)
{
  // create folders up to file path
  std::string absPath = std::string(homeOutputDirectory) + "/" + relPath;
  std::string newPath = absPath + pathUpTo;
  if (!cmSystemTools::FileExists(newPath.c_str()))
    {
    cmSystemTools::MakeDirectory(newPath.c_str());
    }

  // Write out to filename for first time
  std::string relFilename(GetFileNameFromPath(pathUpTo));
  std::string absFilename = absPath + relFilename;
  Open(pathUpTo, absFilename, targetFolderBuildStreams);
  OpenBuildFileStream((*targetFolderBuildStreams)[pathUpTo]);
  GhsMultiGpj::WriteGpjTag(projType, (*targetFolderBuildStreams)[pathUpTo]);
  WriteDisclaimer((*targetFolderBuildStreams)[pathUpTo]);

  // Add to main build file
  if (isFirst)
    {
    *mainBuildFile << relFilename << " ";
    GhsMultiGpj::WriteGpjTag(projType, mainBuildFile);
    }
}

void cmGlobalGhsMultiGenerator::AddFilesUpToPathAppendNextFile(
  std::map<std::string, cmGeneratedFileStream *> *targetFolderBuildStreams,
  std::string const &pathUpTo,
  std::vector<cmsys::String>::const_iterator splitPathI,
  std::vector<cmsys::String>::const_iterator end,
  GhsMultiGpj::Types const projType)
{
  std::vector<cmsys::String>::const_iterator splitPathNextI = splitPathI + 1;
  if (end != splitPathNextI &&
      targetFolderBuildStreams->end() ==
      targetFolderBuildStreams->find(pathUpTo + "/" + *splitPathNextI))
    {
    std::string nextFilename(*splitPathNextI);
    nextFilename = GetFileNameFromPath(nextFilename);
    *(*targetFolderBuildStreams)[pathUpTo] << nextFilename << " ";
    GhsMultiGpj::WriteGpjTag(projType, (*targetFolderBuildStreams)[pathUpTo]);
    }
}

std::string
cmGlobalGhsMultiGenerator::GetFileNameFromPath(std::string const &path)
{
  std::string output(path);
  if (!path.empty())
    {
    cmSystemTools::ConvertToUnixSlashes(output);
    std::vector<cmsys::String> splitPath = cmSystemTools::SplitString(output);
    output += "/" + splitPath.back() + FILE_EXTENSION;
    }
  return output;
}

void cmGlobalGhsMultiGenerator::UpdateBuildFiles(
  cmGeneratorTargetsType *tgts)
{
  for (cmGeneratorTargetsType::iterator tgtsI = tgts->begin();
       tgtsI != tgts->end(); ++tgtsI)
    {
    const cmTarget *tgt(tgtsI->first);
    if (IsTgtForBuild(tgt))
      {
      char const *rawFolderName = tgtsI->first->GetProperty("FOLDER");
      if (NULL == rawFolderName)
        {
        rawFolderName = "";
        }
      std::string folderName(rawFolderName);
      if (this->TargetFolderBuildStreams.end() ==
          this->TargetFolderBuildStreams.find(folderName))
        {
        this->AddFilesUpToPath(
          GetBuildFileStream(), &this->TargetFolderBuildStreams,
          this->GetCMakeInstance()->GetHomeOutputDirectory(), folderName,
          GhsMultiGpj::PROJECT);
        }
      std::vector<cmsys::String> splitPath = cmSystemTools::SplitString(
            cmGhsMultiTargetGenerator::GetRelBuildFileName(tgt));
      std::string foldNameRelBuildFile(*(splitPath.end() - 2) + "/" +
                                       splitPath.back());
      *this->TargetFolderBuildStreams[folderName] << foldNameRelBuildFile
                                                  << " ";
      GhsMultiGpj::WriteGpjTag(cmGhsMultiTargetGenerator::GetGpjTag(tgt),
                               this->TargetFolderBuildStreams[folderName]);
      }
    }
}

bool cmGlobalGhsMultiGenerator::IsTgtForBuild(const cmTarget *tgt)
{
  const std::string config =
    tgt->GetMakefile()->GetSafeDefinition("CMAKE_BUILD_TYPE");
  std::vector<cmSourceFile *> tgtSources;
  tgt->GetSourceFiles(tgtSources, config);
  bool tgtInBuild = true;
  char const *excludeFromAll = tgt->GetProperty("EXCLUDE_FROM_ALL");
  if (NULL != excludeFromAll && '1' == excludeFromAll[0] &&
    '\0' == excludeFromAll[1])
  {
    tgtInBuild = false;
  }
  return !tgtSources.empty() && tgtInBuild;
}

std::string cmGlobalGhsMultiGenerator::trimQuotes(std::string const &str)
{
  std::string result;
  result.reserve(str.size());
  for (const char *ch = str.c_str(); *ch != '\0'; ++ch)
    {
    if (*ch != '"')
      {
      result += *ch;
      }
    }
  return result;
}
