/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
   file Copyright.txt or https://cmake.org/licensing for details.  */
#include "cmGlobalUVisionGenerator.h"

#include "cmsys/SystemTools.hxx"

#include "cmAlgorithms.h"
#include "cmDocumentationEntry.h"
#include "cmGeneratedFileStream.h"
#include "cmGeneratorTarget.h"
#include "cmLocalUVisionGenerator.h"
#include "cmMakefile.h"
#include "cmState.h"
#include "cmUVisionTargetGenerator.h"
#include "cmVersion.h"
#include "cmake.h"

#define CMAKE_CHECK_BUILD_SYSTEM_TARGET "ZERO_CHECK"

cmGlobalUVisionGenerator::cmGlobalUVisionGenerator(cmake* cm)
  : cmGlobalGenerator(cm)
{
  cm->GetState()->SetUvMultiIDE(true);
}

cmGlobalUVisionGenerator::~cmGlobalUVisionGenerator()
{
}

void cmGlobalUVisionGenerator::AddExtraIDETargets()
{
  // make sure extra targets are added before calling
  // the parent generate which will call trace depends
  for (auto& keyVal : this->ProjectMap) {
    cmLocalGenerator* root = keyVal.second[0];
    // add ALL_BUILD, INSTALL, etc
    this->AddExtraTargets(root, keyVal.second);
  }
}

void cmGlobalUVisionGenerator::AddExtraTargets(
  cmLocalGenerator* root, std::vector<cmLocalGenerator*>& gens)
{
  cmMakefile* mf = root->GetMakefile();

  bool regenerate = !this->GlobalSettingIsOn("CMAKE_SUPPRESS_REGENERATION");
  bool isTopLevel =
    !root->GetStateSnapshot().GetBuildsystemDirectoryParent().IsValid();
  if (regenerate && isTopLevel) {
    cmake* cm = this->GetCMakeInstance();

    cmCustomCommandLine verifyCommandLine;
    verifyCommandLine.push_back(cmSystemTools::GetCMakeCommand());
    verifyCommandLine.push_back("-P");
    verifyCommandLine.push_back(cm->GetGlobVerifyScript());
    cmCustomCommandLines verifyCommandLines;
    verifyCommandLines.push_back(verifyCommandLine);
    std::vector<std::string> byproducts;
    byproducts.push_back(cm->GetGlobVerifyStamp());
    const char* no_working_directory = nullptr;
    std::vector<std::string> no_depends;
    cmCustomCommandLines noCommandLines;

    cmTarget* tgt = mf->AddUtilityCommand(
      CMAKE_CHECK_BUILD_SYSTEM_TARGET, cmMakefile::TargetOrigin::Generator,
      false, no_working_directory, no_depends, noCommandLines);
    mf->AddNewTarget(cmStateEnums::UTILITY, CMAKE_CHECK_BUILD_SYSTEM_TARGET);
    mf->AddCustomCommandToTarget(CMAKE_CHECK_BUILD_SYSTEM_TARGET, byproducts,
                                 no_depends, verifyCommandLines,
                                 cmTarget::PRE_BUILD, "Checking File Globs",
                                 no_working_directory, false);
  }
}
cmLocalGenerator* cmGlobalUVisionGenerator::CreateLocalGenerator(
  cmMakefile* mf)
{

  return new cmLocalUVisionGenerator(this, mf);
}

void cmGlobalUVisionGenerator::GetDocumentation(cmDocumentationEntry& entry)
{
  entry.Name = GetActualName();
  entry.Brief = "Generates uVision Multi-Project-Workspace (experimental, "
                "work-in-progress).";
}

void cmGlobalUVisionGenerator::ComputeTargetObjectDirectory(
  cmGeneratorTarget* gt) const
{
  // Compute full path to object file directory for this target.
  std::string dir;
  dir += gt->LocalGenerator->GetCurrentBinaryDirectory();
  dir += "/";
  dir += gt->LocalGenerator->GetTargetDirectory(gt);
  dir += "/";
  gt->ObjectDirectory = dir;
}

void cmGlobalUVisionGenerator::EnableLanguage(
  std::vector<std::string> const& l, cmMakefile* mf, bool optional)
{
  mf->AddDefinition("CMAKE_SYSTEM_NAME", "uv_mwp");
  mf->AddDefinition("UV_MULTI", "1"); // identifier for user CMake files
  this->cmGlobalGenerator::EnableLanguage(l, mf, optional);
}

bool cmGlobalUVisionGenerator::FindMakeProgram(cmMakefile* /*mf*/)
{
  // the Keil IDE does not provide separate make tool outside the IDE --> we
  // are ignoring this here
  return true;
}

void cmGlobalUVisionGenerator::Generate()
{
  // first do the superclass method
  this->cmGlobalGenerator::Generate();

  // now the sub-projects are built, only the root-project/root-dir will get a
  // multi-project-file
  for (auto& projectPair : this->ProjectMap) {
    if (projectPair.second[0]->GetMakefile()->GetRecursionDepth() == 0) {
      this->OutputTopLevelProject(projectPair.second[0], projectPair.second);
    }
  }
}

void cmGlobalUVisionGenerator::OutputTopLevelProject(
  cmLocalGenerator* root, std::vector<cmLocalGenerator*>& generators)
{
  if (generators.empty()) {
    return;
  }

  std::string fname = root->GetCurrentBinaryDirectory();
  fname += "/";
  fname += root->GetProjectName();
  fname += ".uvmpw";

  cmGeneratedFileStream fout(fname.c_str());
  fout.SetCopyIfDifferent(true);

  fout << "<?xml version =\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>\n"
          "<ProjectWorkspace xmlns:xsi "
          "=\"http://www.w3.org/2001/XMLSchema-instance\" "
          "xsi:noNamespaceSchemaLocation=\"project_mpw.xsd\">"
          "<SchemaVersion>1.0</SchemaVersion>\n"
          "<Header>### uVision Project, (C)Keil Software</Header>\n"
          "<WorkspaceName> WorkSpace</WorkspaceName> \n";
  for (auto& projectName : this->m_fullSubProjectNames) {
    fout << "<project>\n";
    fout << "<PathAndName>" << projectName << "</PathAndName>\n";
    fout << "<NodeIsExpanded>1</NodeIsExpanded >\n";
    fout << "<NodeIsCheckedInBatchBuild>1</NodeIsCheckedInBatchBuild >\n";
    fout << "</project>\n";
  }
  fout << "</ProjectWorkspace>\n";
  fout.Close();
}

bool cmGlobalUVisionGenerator::TargetCompare::operator()(
  cmGeneratorTarget const* l, cmGeneratorTarget const* r) const
{
  // Make sure a given named target is ordered first,
  // e.g. to set ALL_BUILD as the default active project.
  // When the empty string is named this is a no-op.
  if (r->GetName() == this->First) {
    return false;
  }
  if (l->GetName() == this->First) {
    return true;
  }
  return l->GetName() < r->GetName();
}

cmGlobalUVisionGenerator::OrderedTargetDependSet::OrderedTargetDependSet(
  TargetDependSet const& targets, std::string const& first)
  : derived(TargetCompare(first))
{
  this->insert(targets.begin(), targets.end());
}
