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

#include <sstream>

#include "cmMakefile.h"
#include "cmPolicies.h"
#include "cmState.h"
#include "cmStateTypes.h"
#include "cmake.h"

class cmExecutionStatus;

// cmCMakePolicyCommand
bool
cmCMakePolicyCommand::InitialPass(std::vector<std::string> const& args,
                                  cmExecutionStatus&)
{
    if(args.empty())
    {
        this->SetError("requires at least one argument.");
        return false;
    }

    if(args[0] == "SET")
    {
        return this->HandleSetMode(args);
    }
    if(args[0] == "GET")
    {
        return this->HandleGetMode(args);
    }
    if(args[0] == "PUSH")
    {
        if(args.size() > 1)
        {
            this->SetError("PUSH may not be given additional arguments.");
            return false;
        }
        this->Makefile->PushPolicy();
        return true;
    }
    if(args[0] == "POP")
    {
        if(args.size() > 1)
        {
            this->SetError("POP may not be given additional arguments.");
            return false;
        }
        this->Makefile->PopPolicy();
        return true;
    }
    if(args[0] == "VERSION")
    {
        return this->HandleVersionMode(args);
    }

    std::ostringstream e;
    e << "given unknown first argument \"" << args[0] << "\"";
    this->SetError(e.str());
    return false;
}

bool
cmCMakePolicyCommand::HandleSetMode(std::vector<std::string> const& args)
{
    if(args.size() != 3)
    {
        this->SetError("SET must be given exactly 2 additional arguments.");
        return false;
    }

    cmPolicies::PolicyStatus status;
    if(args[2] == "OLD")
    {
        status = cmPolicies::OLD;
    } else if(args[2] == "NEW")
    {
        status = cmPolicies::NEW;
    } else
    {
        std::ostringstream e;
        e << "SET given unrecognized policy status \"" << args[2] << "\"";
        this->SetError(e.str());
        return false;
    }

    if(!this->Makefile->SetPolicy(args[1].c_str(), status))
    {
        this->SetError("SET failed to set policy.");
        return false;
    }
    if(args[1] == "CMP0001" &&
       (status == cmPolicies::WARN || status == cmPolicies::OLD))
    {
        if(!(this->Makefile->GetState()->GetInitializedCacheValue(
               "CMAKE_BACKWARDS_COMPATIBILITY")))
        {
            // Set it to 2.4 because that is the last version where the
            // variable had meaning.
            this->Makefile->AddCacheDefinition(
                "CMAKE_BACKWARDS_COMPATIBILITY", "2.4",
                "For backwards compatibility, what version of CMake "
                "commands and "
                "syntax should this version of CMake try to support.",
                cmStateEnums::STRING);
        }
    }
    return true;
}

bool
cmCMakePolicyCommand::HandleGetMode(std::vector<std::string> const& args)
{
    bool parent_scope = false;
    if(args.size() == 4 && args[3] == "PARENT_SCOPE")
    {
        // Undocumented PARENT_SCOPE option for use within CMake.
        parent_scope = true;
    } else if(args.size() != 3)
    {
        this->SetError("GET must be given exactly 2 additional arguments.");
        return false;
    }

    // Get arguments.
    std::string const& id  = args[1];
    std::string const& var = args[2];

    // Lookup the policy number.
    cmPolicies::PolicyID pid;
    if(!cmPolicies::GetPolicyID(id.c_str(), pid))
    {
        std::ostringstream e;
        e << "GET given policy \"" << id << "\" which is not known to this "
          << "version of CMake.";
        this->SetError(e.str());
        return false;
    }

    // Lookup the policy setting.
    cmPolicies::PolicyStatus status =
        this->Makefile->GetPolicyStatus(pid, parent_scope);
    switch(status)
    {
        case cmPolicies::OLD:
            // Report that the policy is set to OLD.
            this->Makefile->AddDefinition(var, "OLD");
            break;
        case cmPolicies::WARN:
            // Report that the policy is not set.
            this->Makefile->AddDefinition(var, "");
            break;
        case cmPolicies::NEW:
            // Report that the policy is set to NEW.
            this->Makefile->AddDefinition(var, "NEW");
            break;
        case cmPolicies::REQUIRED_IF_USED:
        case cmPolicies::REQUIRED_ALWAYS:
            // The policy is required to be set before anything needs it.
            {
                std::ostringstream e;
                e << cmPolicies::GetRequiredPolicyError(pid) << "\n"
                  << "The call to cmake_policy(GET " << id
                  << " ...) at which this "
                  << "error appears requests the policy, and this version of "
                     "CMake "
                  << "requires that the policy be set to NEW before it is "
                     "checked.";
                this->Makefile->IssueMessage(cmake::FATAL_ERROR, e.str());
            }
    }

    return true;
}

bool
cmCMakePolicyCommand::HandleVersionMode(std::vector<std::string> const& args)
{
    if(args.size() <= 1)
    {
        this->SetError("VERSION not given an argument");
        return false;
    }
    if(args.size() >= 3)
    {
        this->SetError("VERSION given too many arguments");
        return false;
    }
    std::string const& version_string = args[1];

    // Separate the <min> version and any trailing ...<max> component.
    std::string::size_type const dd          = version_string.find("...");
    std::string const            version_min = version_string.substr(0, dd);
    std::string const            version_max =
        dd != std::string::npos
            ? version_string.substr(dd + 3, std::string::npos)
            : std::string();
    if(dd != std::string::npos && (version_min.empty() || version_max.empty()))
    {
        std::ostringstream e;
        e << "VERSION \"" << version_string
          << "\" does not have a version on both sides of \"...\".";
        this->SetError(e.str());
        return false;
    }

    this->Makefile->SetPolicyVersion(version_min, version_max);
    return true;
}
