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

#include <memory>
#include <string>
#include <vector>

#include <cm3p/json/value.h>

#include "cmFileAPI.h"
#include "cmGlobalGenerator.h"
#include "cmMakefile.h"
#include "cmProperty.h"
#include "cmState.h"
#include "cmStringAlgorithms.h"
#include "cmake.h"

namespace {

struct ToolchainVariable
{
  std::string ObjectKey;
  std::string VariableSuffix;
  bool IsList;
};

class Toolchains
{
  cmFileAPI& FileAPI;
  unsigned long Version;

  static const std::vector<ToolchainVariable> CompilerVariables;
  static const std::vector<ToolchainVariable> CompilerImplicitVariables;
  static const ToolchainVariable SourceFileExtensionsVariable;

  Json::Value DumpToolchains();
  Json::Value DumpToolchain(std::string const& lang);
  Json::Value DumpToolchainVariables(
    cmMakefile const* mf, std::string const& lang,
    std::vector<ToolchainVariable> const& variables);
  void DumpToolchainVariable(cmMakefile const* mf, Json::Value& object,
                             std::string const& lang,
                             ToolchainVariable const& variable);

public:
  Toolchains(cmFileAPI& fileAPI, unsigned long version);
  Json::Value Dump();
};

const std::vector<ToolchainVariable> Toolchains::CompilerVariables{
  { "path", "COMPILER", false },
  { "id", "COMPILER_ID", false },
  { "version", "COMPILER_VERSION", false },
  { "target", "COMPILER_TARGET", false },
};

const std::vector<ToolchainVariable> Toolchains::CompilerImplicitVariables{
  { "includeDirectories", "IMPLICIT_INCLUDE_DIRECTORIES", true },
  { "linkDirectories", "IMPLICIT_LINK_DIRECTORIES", true },
  { "linkFrameworkDirectories", "IMPLICIT_LINK_FRAMEWORK_DIRECTORIES", true },
  { "linkLibraries", "IMPLICIT_LINK_LIBRARIES", true },
};

const ToolchainVariable Toolchains::SourceFileExtensionsVariable{
  "sourceFileExtensions", "SOURCE_FILE_EXTENSIONS", true
};

Toolchains::Toolchains(cmFileAPI& fileAPI, unsigned long version)
  : FileAPI(fileAPI)
  , Version(version)
{
  static_cast<void>(this->Version);
}

Json::Value Toolchains::Dump()
{
  Json::Value toolchains = Json::objectValue;
  toolchains["toolchains"] = this->DumpToolchains();
  return toolchains;
}

Json::Value Toolchains::DumpToolchains()
{
  Json::Value toolchains = Json::arrayValue;

  for (std::string const& lang :
       this->FileAPI.GetCMakeInstance()->GetState()->GetEnabledLanguages()) {
    toolchains.append(this->DumpToolchain(lang));
  }

  return toolchains;
}

Json::Value Toolchains::DumpToolchain(std::string const& lang)
{
  const auto& mf =
    this->FileAPI.GetCMakeInstance()->GetGlobalGenerator()->GetMakefiles()[0];
  Json::Value toolchain = Json::objectValue;
  toolchain["language"] = lang;
  toolchain["compiler"] =
    this->DumpToolchainVariables(mf.get(), lang, CompilerVariables);
  toolchain["compiler"]["implicit"] =
    this->DumpToolchainVariables(mf.get(), lang, CompilerImplicitVariables);
  this->DumpToolchainVariable(mf.get(), toolchain, lang,
                              SourceFileExtensionsVariable);
  return toolchain;
}

Json::Value Toolchains::DumpToolchainVariables(
  cmMakefile const* mf, std::string const& lang,
  std::vector<ToolchainVariable> const& variables)
{
  Json::Value object = Json::objectValue;
  for (const auto& variable : variables) {
    this->DumpToolchainVariable(mf, object, lang, variable);
  }
  return object;
}

void Toolchains::DumpToolchainVariable(cmMakefile const* mf,
                                       Json::Value& object,
                                       std::string const& lang,
                                       ToolchainVariable const& variable)
{
  std::string const variableName =
    cmStrCat("CMAKE_", lang, "_", variable.VariableSuffix);

  if (variable.IsList) {
    std::vector<std::string> values;
    if (mf->GetDefExpandList(variableName, values)) {
      Json::Value jsonArray = Json::arrayValue;
      for (std::string const& value : values) {
        jsonArray.append(value);
      }
      object[variable.ObjectKey] = jsonArray;
    }
  } else {
    cmProp def = mf->GetDefinition(variableName);
    if (def) {
      object[variable.ObjectKey] = *def;
    }
  }
}
}

Json::Value cmFileAPIToolchainsDump(cmFileAPI& fileAPI, unsigned long version)
{
  Toolchains toolchains(fileAPI, version);
  return toolchains.Dump();
}
