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

#include <cstring>
#include <sstream>
#include <vector>

#include <cmext/string_view>

#include "cmCTest.h"
#include "cmCTestConfigureHandler.h"
#include "cmGlobalGenerator.h"
#include "cmMakefile.h"
#include "cmProperty.h"
#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"
#include "cmake.h"

void cmCTestConfigureCommand::BindArguments()
{
  cmCTestHandlerCommand::BindArguments();
  Bind("OPTIONS"_s, Options);
}

cmCTestGenericHandler* cmCTestConfigureCommand::InitializeHandler()
{
  std::vector<std::string> options;

  if (!Options.empty()) {
    cmExpandList(Options, options);
  }

  if (CTest->GetCTestConfiguration("BuildDirectory").empty()) {
    SetError(
      "Build directory not specified. Either use BUILD "
      "argument to CTEST_CONFIGURE command or set CTEST_BINARY_DIRECTORY "
      "variable");
    return nullptr;
  }

  cmProp ctestConfigureCommand =
    Makefile->GetDefinition("CTEST_CONFIGURE_COMMAND");

  if (cmNonempty(ctestConfigureCommand)) {
    CTest->SetCTestConfiguration("ConfigureCommand", *ctestConfigureCommand,
                                 Quiet);
  } else {
    cmProp cmakeGeneratorName =
      Makefile->GetDefinition("CTEST_CMAKE_GENERATOR");
    if (cmNonempty(cmakeGeneratorName)) {
      const std::string& source_dir =
        CTest->GetCTestConfiguration("SourceDirectory");
      if (source_dir.empty()) {
        SetError(
          "Source directory not specified. Either use SOURCE "
          "argument to CTEST_CONFIGURE command or set CTEST_SOURCE_DIRECTORY "
          "variable");
        return nullptr;
      }

      const std::string cmakelists_file = source_dir + "/CMakeLists.txt";
      if (!cmSystemTools::FileExists(cmakelists_file)) {
        std::ostringstream e;
        e << "CMakeLists.txt file does not exist [" << cmakelists_file << "]";
        SetError(e.str());
        return nullptr;
      }

      bool multiConfig = false;
      bool cmakeBuildTypeInOptions = false;

      auto gg = Makefile->GetCMakeInstance()->CreateGlobalGenerator(
        *cmakeGeneratorName);
      if (gg) {
        multiConfig = gg->IsMultiConfig();
        gg.reset();
      }

      std::string cmakeConfigureCommand =
        cmStrCat('"', cmSystemTools::GetCMakeCommand(), '"');

      for (std::string const& option : options) {
        cmakeConfigureCommand += " \"";
        cmakeConfigureCommand += option;
        cmakeConfigureCommand += "\"";

        if ((nullptr != strstr(option.c_str(), "CMAKE_BUILD_TYPE=")) ||
            (nullptr != strstr(option.c_str(), "CMAKE_BUILD_TYPE:STRING="))) {
          cmakeBuildTypeInOptions = true;
        }
      }

      if (!multiConfig && !cmakeBuildTypeInOptions &&
          !CTest->GetConfigType().empty()) {
        cmakeConfigureCommand += " \"-DCMAKE_BUILD_TYPE:STRING=";
        cmakeConfigureCommand += CTest->GetConfigType();
        cmakeConfigureCommand += "\"";
      }

      if (Makefile->IsOn("CTEST_USE_LAUNCHERS")) {
        cmakeConfigureCommand += " \"-DCTEST_USE_LAUNCHERS:BOOL=TRUE\"";
      }

      cmakeConfigureCommand += " \"-G";
      cmakeConfigureCommand += *cmakeGeneratorName;
      cmakeConfigureCommand += "\"";

      cmProp cmakeGeneratorPlatform =
        Makefile->GetDefinition("CTEST_CMAKE_GENERATOR_PLATFORM");
      if (cmNonempty(cmakeGeneratorPlatform)) {
        cmakeConfigureCommand += " \"-A";
        cmakeConfigureCommand += *cmakeGeneratorPlatform;
        cmakeConfigureCommand += "\"";
      }

      cmProp cmakeGeneratorToolset =
        Makefile->GetDefinition("CTEST_CMAKE_GENERATOR_TOOLSET");
      if (cmNonempty(cmakeGeneratorToolset)) {
        cmakeConfigureCommand += " \"-T";
        cmakeConfigureCommand += *cmakeGeneratorToolset;
        cmakeConfigureCommand += "\"";
      }

      cmakeConfigureCommand += " \"";
      cmakeConfigureCommand += source_dir;
      cmakeConfigureCommand += "\"";

      CTest->SetCTestConfiguration("ConfigureCommand", cmakeConfigureCommand,
                                   Quiet);
    } else {
      SetError(
        "Configure command is not specified. If this is a "
        "\"built with CMake\" project, set CTEST_CMAKE_GENERATOR. If not, "
        "set CTEST_CONFIGURE_COMMAND.");
      return nullptr;
    }
  }

  if (cmProp labelsForSubprojects =
        Makefile->GetDefinition("CTEST_LABELS_FOR_SUBPROJECTS")) {
    CTest->SetCTestConfiguration("LabelsForSubprojects", *labelsForSubprojects,
                                 Quiet);
  }

  cmCTestConfigureHandler* handler = CTest->GetConfigureHandler();
  handler->Initialize();
  handler->SetQuiet(Quiet);
  return handler;
}
