/* 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;
}
