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

#include <string.h>

#include "cmMakefile.h"
#include "cmState.h"
#include "cmStateTypes.h"
#include "cmSystemTools.h"

class cmExecutionStatus;

// cmUtilitySourceCommand
bool cmUtilitySourceCommand::InitialPass(std::vector<std::string> const& args,
                                         cmExecutionStatus&)
{
  if (args.size() < 3) {
    this->SetError("called with incorrect number of arguments");
    return false;
  }

  std::vector<std::string>::const_iterator arg = args.begin();

  // The first argument is the cache entry name.
  std::string const& cacheEntry = *arg++;
  const char* cacheValue = this->Makefile->GetDefinition(cacheEntry);
  // If it exists already and appears up to date then we are done.  If
  // the string contains "(IntDir)" but that is not the
  // CMAKE_CFG_INTDIR setting then the value is out of date.
  std::string const& intDir =
    this->Makefile->GetRequiredDefinition("CMAKE_CFG_INTDIR");

  bool haveCacheValue = false;
  if (this->Makefile->IsOn("CMAKE_CROSSCOMPILING")) {
    haveCacheValue = (cacheValue != nullptr);
    if (!haveCacheValue) {
      std::string msg = "UTILITY_SOURCE is used in cross compiling mode for ";
      msg += cacheEntry;
      msg += ". If your intention is to run this executable, you need to "
             "preload the cache with the full path to a version of that "
             "program, which runs on this build machine.";
      cmSystemTools::Message(msg.c_str(), "Warning");
    }
  } else {
    cmState* state = this->Makefile->GetState();
    haveCacheValue = (cacheValue &&
                      (strstr(cacheValue, "(IntDir)") == nullptr ||
                       (intDir == "$(IntDir)")) &&
                      (state->GetCacheMajorVersion() != 0 &&
                       state->GetCacheMinorVersion() != 0));
  }

  if (haveCacheValue) {
    return true;
  }

  // The second argument is the utility's executable name, which will be
  // needed later.
  std::string const& utilityName = *arg++;

  // The third argument specifies the relative directory of the source
  // of the utility.
  std::string const& relativeSource = *arg++;
  std::string utilitySource = this->Makefile->GetCurrentSourceDirectory();
  utilitySource = utilitySource + "/" + relativeSource;

  // If the directory doesn't exist, the source has not been included.
  if (!cmSystemTools::FileExists(utilitySource)) {
    return true;
  }

  // Make sure all the files exist in the source directory.
  while (arg != args.end()) {
    std::string file = utilitySource + "/" + *arg++;
    if (!cmSystemTools::FileExists(file)) {
      return true;
    }
  }

  // The source exists.
  std::string cmakeCFGout =
    this->Makefile->GetRequiredDefinition("CMAKE_CFG_INTDIR");
  std::string utilityDirectory = this->Makefile->GetCurrentBinaryDirectory();
  std::string exePath;
  if (this->Makefile->GetDefinition("EXECUTABLE_OUTPUT_PATH")) {
    exePath = this->Makefile->GetDefinition("EXECUTABLE_OUTPUT_PATH");
  }
  if (!exePath.empty()) {
    utilityDirectory = exePath;
  } else {
    utilityDirectory += "/" + relativeSource;
  }

  // Construct the cache entry for the executable's location.
  std::string utilityExecutable = utilityDirectory + "/" + cmakeCFGout + "/" +
    utilityName + this->Makefile->GetDefinition("CMAKE_EXECUTABLE_SUFFIX");

  // make sure we remove any /./ in the name
  cmSystemTools::ReplaceString(utilityExecutable, "/./", "/");

  // Enter the value into the cache.
  this->Makefile->AddCacheDefinition(cacheEntry, utilityExecutable.c_str(),
                                     "Path to an internal program.",
                                     cmStateEnums::FILEPATH);
  // add a value into the cache that maps from the
  // full path to the name of the project
  cmSystemTools::ConvertToUnixSlashes(utilityExecutable);
  this->Makefile->AddCacheDefinition(utilityExecutable, utilityName.c_str(),
                                     "Executable to project name.",
                                     cmStateEnums::INTERNAL);

  return true;
}
