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

#include "cmExecutionStatus.h"
#include "cmMakefile.h"
#include "cmStateDirectory.h"
#include "cmStateSnapshot.h"
#include "cmSystemTools.h"
#include "cmake.h"

namespace
{
enum IncludeGuardScope
{
    VARIABLE,
    DIRECTORY,
    GLOBAL
};

std::string
GetIncludeGuardVariableName(std::string const& filePath)
{
    std::string result = "__INCGUARD_";
#ifdef CMAKE_BUILD_WITH_CMAKE
    result += cmSystemTools::ComputeStringMD5(filePath);
#else
    result += cmSystemTools::MakeCidentifier(filePath);
#endif
    result += "__";
    return result;
}

bool
CheckIncludeGuardIsSet(cmMakefile* mf, std::string const& includeGuardVar)
{
    if(mf->GetProperty(includeGuardVar))
    {
        return true;
    }
    cmStateSnapshot dirSnapshot =
        mf->GetStateSnapshot().GetBuildsystemDirectoryParent();
    while(dirSnapshot.GetState())
    {
        cmStateDirectory stateDir = dirSnapshot.GetDirectory();
        if(stateDir.GetProperty(includeGuardVar))
        {
            return true;
        }
        dirSnapshot = dirSnapshot.GetBuildsystemDirectoryParent();
    }
    return false;
}

}  // anonymous namespace

// cmIncludeGuardCommand
bool
cmIncludeGuardCommand::InitialPass(std::vector<std::string> const& args,
                                   cmExecutionStatus&              status)
{
    if(args.size() > 1)
    {
        this->SetError(
            "given an invalid number of arguments. The command takes at "
            "most 1 argument.");
        return false;
    }

    IncludeGuardScope scope = VARIABLE;

    if(!args.empty())
    {
        std::string const& arg = args[0];
        if(arg == "DIRECTORY")
        {
            scope = DIRECTORY;
        } else if(arg == "GLOBAL")
        {
            scope = GLOBAL;
        } else
        {
            this->SetError("given an invalid scope: " + arg);
            return false;
        }
    }

    std::string includeGuardVar = GetIncludeGuardVariableName(
        this->Makefile->GetDefinition("CMAKE_CURRENT_LIST_FILE"));

    cmMakefile* const mf = this->Makefile;

    switch(scope)
    {
        case VARIABLE:
            if(mf->IsDefinitionSet(includeGuardVar))
            {
                status.SetReturnInvoked();
                return true;
            }
            mf->AddDefinition(includeGuardVar, true);
            break;
        case DIRECTORY:
            if(CheckIncludeGuardIsSet(mf, includeGuardVar))
            {
                status.SetReturnInvoked();
                return true;
            }
            mf->SetProperty(includeGuardVar, "TRUE");
            break;
        case GLOBAL:
            cmake* const cm = mf->GetCMakeInstance();
            if(cm->GetProperty(includeGuardVar))
            {
                status.SetReturnInvoked();
                return true;
            }
            cm->SetProperty(includeGuardVar, "TRUE");
            break;
    }

    return true;
}
