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

#include "cmStateSnapshot.h"

#include <algorithm>
#include <assert.h>
#include <iterator>
#include <string>

#include "cmAlgorithms.h"
#include "cmDefinitions.h"
#include "cmListFileCache.h"
#include "cmPropertyMap.h"
#include "cmState.h"
#include "cmStateDirectory.h"
#include "cmStatePrivate.h"
#include "cmVersion.h"
#include "cmake.h"

#if !defined(_WIN32)
#    include <sys/utsname.h>
#endif

#if defined(__CYGWIN__)
#    include "cmSystemTools.h"
#endif

cmStateSnapshot::cmStateSnapshot(cmState* state)
: State(state)
, Position()
{}

std::vector<cmStateSnapshot>
cmStateSnapshot::GetChildren()
{
    return this->Position->BuildSystemDirectory->Children;
}

cmStateSnapshot::cmStateSnapshot(cmState*                    state,
                                 cmStateDetail::PositionType position)
: State(state)
, Position(position)
{}

cmStateEnums::SnapshotType
cmStateSnapshot::GetType() const
{
    return this->Position->SnapshotType;
}

void
cmStateSnapshot::SetListFile(const std::string& listfile)
{
    *this->Position->ExecutionListFile = listfile;
}

std::string
cmStateSnapshot::GetExecutionListFile() const
{
    return *this->Position->ExecutionListFile;
}

bool
cmStateSnapshot::IsValid() const
{
    return this->State && this->Position.IsValid()
               ? this->Position != this->State->SnapshotData.Root()
               : false;
}

cmStateSnapshot
cmStateSnapshot::GetBuildsystemDirectoryParent() const
{
    cmStateSnapshot snapshot;
    if(!this->State || this->Position == this->State->SnapshotData.Root())
    {
        return snapshot;
    }
    cmStateDetail::PositionType parentPos = this->Position->DirectoryParent;
    if(parentPos != this->State->SnapshotData.Root())
    {
        snapshot = cmStateSnapshot(
            this->State, parentPos->BuildSystemDirectory->DirectoryEnd);
    }

    return snapshot;
}

cmStateSnapshot
cmStateSnapshot::GetCallStackParent() const
{
    assert(this->State);
    assert(this->Position != this->State->SnapshotData.Root());

    cmStateSnapshot             snapshot;
    cmStateDetail::PositionType parentPos = this->Position;
    while(parentPos->SnapshotType == cmStateEnums::PolicyScopeType ||
          parentPos->SnapshotType == cmStateEnums::VariableScopeType)
    {
        ++parentPos;
    }
    if(parentPos->SnapshotType == cmStateEnums::BuildsystemDirectoryType ||
       parentPos->SnapshotType == cmStateEnums::BaseType)
    {
        return snapshot;
    }

    ++parentPos;
    while(parentPos->SnapshotType == cmStateEnums::PolicyScopeType ||
          parentPos->SnapshotType == cmStateEnums::VariableScopeType)
    {
        ++parentPos;
    }

    if(parentPos == this->State->SnapshotData.Root())
    {
        return snapshot;
    }

    snapshot = cmStateSnapshot(this->State, parentPos);
    return snapshot;
}

cmStateSnapshot
cmStateSnapshot::GetCallStackBottom() const
{
    assert(this->State);
    assert(this->Position != this->State->SnapshotData.Root());

    cmStateDetail::PositionType pos = this->Position;
    while(pos->SnapshotType != cmStateEnums::BaseType &&
          pos->SnapshotType != cmStateEnums::BuildsystemDirectoryType &&
          pos != this->State->SnapshotData.Root())
    {
        ++pos;
    }
    return cmStateSnapshot(this->State, pos);
}

void
cmStateSnapshot::PushPolicy(cmPolicies::PolicyMap const& entry, bool weak)
{
    cmStateDetail::PositionType pos = this->Position;
    pos->Policies                   = this->State->PolicyStack.Push(
        pos->Policies, cmStateDetail::PolicyStackEntry(entry, weak));
}

bool
cmStateSnapshot::PopPolicy()
{
    cmStateDetail::PositionType pos = this->Position;
    if(pos->Policies == pos->PolicyScope)
    {
        return false;
    }
    pos->Policies = this->State->PolicyStack.Pop(pos->Policies);
    return true;
}

bool
cmStateSnapshot::CanPopPolicyScope()
{
    return this->Position->Policies == this->Position->PolicyScope;
}

void
cmStateSnapshot::SetPolicy(cmPolicies::PolicyID     id,
                           cmPolicies::PolicyStatus status)
{
    // Update the policy stack from the top to the top-most strong entry.
    bool previous_was_weak = true;
    for(cmLinkedTree<cmStateDetail::PolicyStackEntry>::iterator psi =
            this->Position->Policies;
        previous_was_weak && psi != this->Position->PolicyRoot; ++psi)
    {
        psi->Set(id, status);
        previous_was_weak = psi->Weak;
    }
}

cmPolicies::PolicyStatus
cmStateSnapshot::GetPolicy(cmPolicies::PolicyID id, bool parent_scope) const
{
    cmPolicies::PolicyStatus status = cmPolicies::GetPolicyStatus(id);

    if(status == cmPolicies::REQUIRED_ALWAYS ||
       status == cmPolicies::REQUIRED_IF_USED)
    {
        return status;
    }

    cmLinkedTree<cmStateDetail::BuildsystemDirectoryStateType>::iterator dir =
        this->Position->BuildSystemDirectory;

    while(true)
    {
        assert(dir.IsValid());
        cmLinkedTree<cmStateDetail::PolicyStackEntry>::iterator leaf =
            dir->DirectoryEnd->Policies;
        cmLinkedTree<cmStateDetail::PolicyStackEntry>::iterator root =
            dir->DirectoryEnd->PolicyRoot;
        for(; leaf != root; ++leaf)
        {
            if(parent_scope)
            {
                parent_scope = false;
                continue;
            }
            if(leaf->IsDefined(id))
            {
                status = leaf->Get(id);
                return status;
            }
        }
        cmStateDetail::PositionType e = dir->DirectoryEnd;
        cmStateDetail::PositionType p = e->DirectoryParent;
        if(p == this->State->SnapshotData.Root())
        {
            break;
        }
        dir = p->BuildSystemDirectory;
    }
    return status;
}

bool
cmStateSnapshot::HasDefinedPolicyCMP0011()
{
    return !this->Position->Policies->IsEmpty();
}

std::string const*
cmStateSnapshot::GetDefinition(std::string const& name) const
{
    assert(this->Position->Vars.IsValid());
    return cmDefinitions::Get(name, this->Position->Vars, this->Position->Root);
}

bool
cmStateSnapshot::IsInitialized(std::string const& name) const
{
    return cmDefinitions::HasKey(name, this->Position->Vars,
                                 this->Position->Root);
}

void
cmStateSnapshot::SetDefinition(std::string const& name,
                               std::string const& value)
{
    this->Position->Vars->Set(name, value.c_str());
}

void
cmStateSnapshot::RemoveDefinition(std::string const& name)
{
    this->Position->Vars->Set(name, nullptr);
}

std::vector<std::string>
cmStateSnapshot::UnusedKeys() const
{
    return this->Position->Vars->UnusedKeys();
}

std::vector<std::string>
cmStateSnapshot::ClosureKeys() const
{
    return cmDefinitions::ClosureKeys(this->Position->Vars,
                                      this->Position->Root);
}

bool
cmStateSnapshot::RaiseScope(std::string const& var, const char* varDef)
{
    if(this->Position->ScopeParent == this->Position->DirectoryParent)
    {
        cmStateSnapshot parentDir = this->GetBuildsystemDirectoryParent();
        if(!parentDir.IsValid())
        {
            return false;
        }
        // Update the definition in the parent directory top scope.  This
        // directory's scope was initialized by the closure of the parent
        // scope, so we do not need to localize the definition first.
        if(varDef)
        {
            parentDir.SetDefinition(var, varDef);
        } else
        {
            parentDir.RemoveDefinition(var);
        }
        return true;
    }
    // First localize the definition in the current scope.
    cmDefinitions::Raise(var, this->Position->Vars, this->Position->Root);

    // Now update the definition in the parent scope.
    this->Position->Parent->Set(var, varDef);
    return true;
}

template <typename T, typename U, typename V>
void
InitializeContentFromParent(T& parentContent, T& thisContent,
                            U& parentBacktraces, U& thisBacktraces,
                            V& contentEndPosition)
{
    std::vector<std::string>::const_iterator parentBegin =
        parentContent.begin();
    std::vector<std::string>::const_iterator parentEnd = parentContent.end();

    std::vector<std::string>::const_reverse_iterator parentRbegin =
        cmMakeReverseIterator(parentEnd);
    std::vector<std::string>::const_reverse_iterator parentRend =
        parentContent.rend();
    parentRbegin = std::find(parentRbegin, parentRend, cmPropertySentinal);
    std::vector<std::string>::const_iterator parentIt = parentRbegin.base();

    thisContent = std::vector<std::string>(parentIt, parentEnd);

    std::vector<cmListFileBacktrace>::const_iterator btIt =
        parentBacktraces.begin() + std::distance(parentBegin, parentIt);
    std::vector<cmListFileBacktrace>::const_iterator btEnd =
        parentBacktraces.end();

    thisBacktraces = std::vector<cmListFileBacktrace>(btIt, btEnd);

    contentEndPosition = thisContent.size();
}

void
cmStateSnapshot::SetDefaultDefinitions()
{
/* Up to CMake 2.4 here only WIN32, UNIX and APPLE were set.
  With CMake must separate between target and host platform. In most cases
  the tests for WIN32, UNIX and APPLE will be for the target system, so an
  additional set of variables for the host system is required ->
  CMAKE_HOST_WIN32, CMAKE_HOST_UNIX, CMAKE_HOST_APPLE.
  WIN32, UNIX and APPLE are now set in the platform files in
  Modules/Platforms/.
  To keep cmake scripts (-P) and custom language and compiler modules
  working, these variables are still also set here in this place, but they
  will be reset in CMakeSystemSpecificInformation.cmake before the platform
  files are executed. */
#if defined(_WIN32)
    this->SetDefinition("WIN32", "1");
    this->SetDefinition("CMAKE_HOST_WIN32", "1");
    this->SetDefinition("CMAKE_HOST_SYSTEM_NAME", "Windows");
#else
    this->SetDefinition("UNIX", "1");
    this->SetDefinition("CMAKE_HOST_UNIX", "1");

    struct utsname uts_name;
    if(uname(&uts_name) >= 0)
    {
        this->SetDefinition("CMAKE_HOST_SYSTEM_NAME", uts_name.sysname);
    }
#endif
#if defined(__CYGWIN__)
    std::string legacy;
    if(cmSystemTools::GetEnv("CMAKE_LEGACY_CYGWIN_WIN32", legacy) &&
       cmSystemTools::IsOn(legacy.c_str()))
    {
        this->SetDefinition("WIN32", "1");
        this->SetDefinition("CMAKE_HOST_WIN32", "1");
    }
#endif
#if defined(__APPLE__)
    this->SetDefinition("APPLE", "1");
    this->SetDefinition("CMAKE_HOST_APPLE", "1");
#endif
#if defined(__sun__)
    this->SetDefinition("CMAKE_HOST_SOLARIS", "1");
#endif

    this->SetDefinition("CMAKE_MAJOR_VERSION",
                        std::to_string(cmVersion::GetMajorVersion()));
    this->SetDefinition("CMAKE_MINOR_VERSION",
                        std::to_string(cmVersion::GetMinorVersion()));
    this->SetDefinition("CMAKE_PATCH_VERSION",
                        std::to_string(cmVersion::GetPatchVersion()));
    this->SetDefinition("CMAKE_TWEAK_VERSION",
                        std::to_string(cmVersion::GetTweakVersion()));
    this->SetDefinition("CMAKE_VERSION", cmVersion::GetCMakeVersion());

    this->SetDefinition("CMAKE_FILES_DIRECTORY",
                        cmake::GetCMakeFilesDirectory());

    // Setup the default include file regular expression (match everything).
    this->Position->BuildSystemDirectory->Properties.SetProperty(
        "INCLUDE_REGULAR_EXPRESSION", "^.*$");
}

void
cmStateSnapshot::SetDirectoryDefinitions()
{
    this->SetDefinition("CMAKE_SOURCE_DIR", this->State->GetSourceDirectory());
    this->SetDefinition("CMAKE_CURRENT_SOURCE_DIR",
                        this->State->GetSourceDirectory());
    this->SetDefinition("CMAKE_BINARY_DIR", this->State->GetBinaryDirectory());
    this->SetDefinition("CMAKE_CURRENT_BINARY_DIR",
                        this->State->GetBinaryDirectory());
}

void
cmStateSnapshot::InitializeFromParent()
{
    cmStateDetail::PositionType parent = this->Position->DirectoryParent;
    assert(this->Position->Vars.IsValid());
    assert(parent->Vars.IsValid());

    *this->Position->Vars =
        cmDefinitions::MakeClosure(parent->Vars, parent->Root);

    InitializeContentFromParent(
        parent->BuildSystemDirectory->IncludeDirectories,
        this->Position->BuildSystemDirectory->IncludeDirectories,
        parent->BuildSystemDirectory->IncludeDirectoryBacktraces,
        this->Position->BuildSystemDirectory->IncludeDirectoryBacktraces,
        this->Position->IncludeDirectoryPosition);

    InitializeContentFromParent(
        parent->BuildSystemDirectory->CompileDefinitions,
        this->Position->BuildSystemDirectory->CompileDefinitions,
        parent->BuildSystemDirectory->CompileDefinitionsBacktraces,
        this->Position->BuildSystemDirectory->CompileDefinitionsBacktraces,
        this->Position->CompileDefinitionsPosition);

    InitializeContentFromParent(
        parent->BuildSystemDirectory->CompileOptions,
        this->Position->BuildSystemDirectory->CompileOptions,
        parent->BuildSystemDirectory->CompileOptionsBacktraces,
        this->Position->BuildSystemDirectory->CompileOptionsBacktraces,
        this->Position->CompileOptionsPosition);

    InitializeContentFromParent(
        parent->BuildSystemDirectory->LinkOptions,
        this->Position->BuildSystemDirectory->LinkOptions,
        parent->BuildSystemDirectory->LinkOptionsBacktraces,
        this->Position->BuildSystemDirectory->LinkOptionsBacktraces,
        this->Position->LinkOptionsPosition);

    InitializeContentFromParent(
        parent->BuildSystemDirectory->LinkDirectories,
        this->Position->BuildSystemDirectory->LinkDirectories,
        parent->BuildSystemDirectory->LinkDirectoriesBacktraces,
        this->Position->BuildSystemDirectory->LinkDirectoriesBacktraces,
        this->Position->LinkDirectoriesPosition);

    const char* include_regex =
        parent->BuildSystemDirectory->Properties.GetPropertyValue(
            "INCLUDE_REGULAR_EXPRESSION");
    this->Position->BuildSystemDirectory->Properties.SetProperty(
        "INCLUDE_REGULAR_EXPRESSION", include_regex);
}

cmState*
cmStateSnapshot::GetState() const
{
    return this->State;
}

cmStateDirectory
cmStateSnapshot::GetDirectory() const
{
    return cmStateDirectory(this->Position->BuildSystemDirectory, *this);
}

void
cmStateSnapshot::SetProjectName(const std::string& name)
{
    this->Position->BuildSystemDirectory->ProjectName = name;
}

std::string
cmStateSnapshot::GetProjectName() const
{
    return this->Position->BuildSystemDirectory->ProjectName;
}

void
cmStateSnapshot::InitializeFromParent_ForSubdirsCommand()
{
    std::string currentSrcDir =
        *this->GetDefinition("CMAKE_CURRENT_SOURCE_DIR");
    std::string currentBinDir =
        *this->GetDefinition("CMAKE_CURRENT_BINARY_DIR");
    this->InitializeFromParent();
    this->SetDefinition("CMAKE_SOURCE_DIR", this->State->GetSourceDirectory());
    this->SetDefinition("CMAKE_BINARY_DIR", this->State->GetBinaryDirectory());

    this->SetDefinition("CMAKE_CURRENT_SOURCE_DIR", currentSrcDir);
    this->SetDefinition("CMAKE_CURRENT_BINARY_DIR", currentBinDir);
}

bool
cmStateSnapshot::StrictWeakOrder::operator()(const cmStateSnapshot& lhs,
                                             const cmStateSnapshot& rhs) const
{
    return lhs.Position.StrictWeakOrdered(rhs.Position);
}

bool
operator==(const cmStateSnapshot& lhs, const cmStateSnapshot& rhs)
{
    return lhs.Position == rhs.Position;
}

bool
operator!=(const cmStateSnapshot& lhs, const cmStateSnapshot& rhs)
{
    return lhs.Position != rhs.Position;
}
