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

#include <sstream>

#include "cmGlobalGenerator.h"
#include "cmInstalledFile.h"
#include "cmListFileCache.h"
#include "cmMakefile.h"
#include "cmPolicies.h"
#include "cmProperty.h"
#include "cmPropertyDefinition.h"
#include "cmSourceFile.h"
#include "cmState.h"
#include "cmSystemTools.h"
#include "cmTarget.h"
#include "cmTargetPropertyComputer.h"
#include "cmTest.h"
#include "cmake.h"

class cmExecutionStatus;
class cmMessenger;

cmGetPropertyCommand::cmGetPropertyCommand() { this->InfoType = OutValue; }

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

    // The cmake variable in which to store the result.
    this->Variable = args[0];

    // Get the scope from which to get the property.
    cmProperty::ScopeType scope;
    if(args[1] == "GLOBAL")
    {
        scope = cmProperty::GLOBAL;
    } else if(args[1] == "DIRECTORY")
    {
        scope = cmProperty::DIRECTORY;
    } else if(args[1] == "TARGET")
    {
        scope = cmProperty::TARGET;
    } else if(args[1] == "SOURCE")
    {
        scope = cmProperty::SOURCE_FILE;
    } else if(args[1] == "TEST")
    {
        scope = cmProperty::TEST;
    } else if(args[1] == "VARIABLE")
    {
        scope = cmProperty::VARIABLE;
    } else if(args[1] == "CACHE")
    {
        scope = cmProperty::CACHE;
    } else if(args[1] == "INSTALL")
    {
        scope = cmProperty::INSTALL;
    } else
    {
        std::ostringstream e;
        e << "given invalid scope " << args[1] << ".  "
          << "Valid scopes are "
          << "GLOBAL, DIRECTORY, TARGET, SOURCE, TEST, VARIABLE, CACHE, "
             "INSTALL.";
        this->SetError(e.str());
        return false;
    }

    // Parse remaining arguments.
    enum Doing
    {
        DoingNone,
        DoingName,
        DoingProperty,
        DoingType
    };
    Doing doing = DoingName;
    for(unsigned int i = 2; i < args.size(); ++i)
    {
        if(args[i] == "PROPERTY")
        {
            doing = DoingProperty;
        } else if(args[i] == "BRIEF_DOCS")
        {
            doing          = DoingNone;
            this->InfoType = OutBriefDoc;
        } else if(args[i] == "FULL_DOCS")
        {
            doing          = DoingNone;
            this->InfoType = OutFullDoc;
        } else if(args[i] == "SET")
        {
            doing          = DoingNone;
            this->InfoType = OutSet;
        } else if(args[i] == "DEFINED")
        {
            doing          = DoingNone;
            this->InfoType = OutDefined;
        } else if(doing == DoingName)
        {
            doing      = DoingNone;
            this->Name = args[i];
        } else if(doing == DoingProperty)
        {
            doing              = DoingNone;
            this->PropertyName = args[i];
        } else
        {
            std::ostringstream e;
            e << "given invalid argument \"" << args[i] << "\".";
            this->SetError(e.str());
            return false;
        }
    }

    // Make sure a property name was found.
    if(this->PropertyName.empty())
    {
        this->SetError("not given a PROPERTY <name> argument.");
        return false;
    }

    // Compute requested output.
    if(this->InfoType == OutBriefDoc)
    {
        // Lookup brief documentation.
        std::string output;
        if(cmPropertyDefinition const* def =
               this->Makefile->GetState()->GetPropertyDefinition(
                   this->PropertyName, scope))
        {
            output = def->GetShortDescription();
        } else
        {
            output = "NOTFOUND";
        }
        this->Makefile->AddDefinition(this->Variable, output.c_str());
    } else if(this->InfoType == OutFullDoc)
    {
        // Lookup full documentation.
        std::string output;
        if(cmPropertyDefinition const* def =
               this->Makefile->GetState()->GetPropertyDefinition(
                   this->PropertyName, scope))
        {
            output = def->GetFullDescription();
        } else
        {
            output = "NOTFOUND";
        }
        this->Makefile->AddDefinition(this->Variable, output.c_str());
    } else if(this->InfoType == OutDefined)
    {
        // Lookup if the property is defined
        if(this->Makefile->GetState()->GetPropertyDefinition(this->PropertyName,
                                                             scope))
        {
            this->Makefile->AddDefinition(this->Variable, "1");
        } else
        {
            this->Makefile->AddDefinition(this->Variable, "0");
        }
    } else
    {
        // Dispatch property getting.
        switch(scope)
        {
            case cmProperty::GLOBAL:
                return this->HandleGlobalMode();
            case cmProperty::DIRECTORY:
                return this->HandleDirectoryMode();
            case cmProperty::TARGET:
                return this->HandleTargetMode();
            case cmProperty::SOURCE_FILE:
                return this->HandleSourceMode();
            case cmProperty::TEST:
                return this->HandleTestMode();
            case cmProperty::VARIABLE:
                return this->HandleVariableMode();
            case cmProperty::CACHE:
                return this->HandleCacheMode();
            case cmProperty::INSTALL:
                return this->HandleInstallMode();

            case cmProperty::CACHED_VARIABLE:
                break;  // should never happen
        }
    }

    return true;
}

bool
cmGetPropertyCommand::StoreResult(const char* value)
{
    if(this->InfoType == OutSet)
    {
        this->Makefile->AddDefinition(this->Variable, value ? "1" : "0");
    } else  // if(this->InfoType == OutValue)
    {
        if(value)
        {
            this->Makefile->AddDefinition(this->Variable, value);
        } else
        {
            this->Makefile->RemoveDefinition(this->Variable);
        }
    }
    return true;
}

bool
cmGetPropertyCommand::HandleGlobalMode()
{
    if(!this->Name.empty())
    {
        this->SetError("given name for GLOBAL scope.");
        return false;
    }

    // Get the property.
    cmake* cm = this->Makefile->GetCMakeInstance();
    return this->StoreResult(
        cm->GetState()->GetGlobalProperty(this->PropertyName));
}

bool
cmGetPropertyCommand::HandleDirectoryMode()
{
    // Default to the current directory.
    cmMakefile* mf = this->Makefile;

    // Lookup the directory if given.
    if(!this->Name.empty())
    {
        // Construct the directory name.  Interpret relative paths with
        // respect to the current directory.
        std::string dir = this->Name;
        if(!cmSystemTools::FileIsFullPath(dir))
        {
            dir = this->Makefile->GetCurrentSourceDirectory();
            dir += "/";
            dir += this->Name;
        }

        // The local generators are associated with collapsed paths.
        dir = cmSystemTools::CollapseFullPath(dir);

        // Lookup the generator.
        mf = this->Makefile->GetGlobalGenerator()->FindMakefile(dir);
        if(!mf)
        {
            // Could not find the directory.
            this->SetError(
                "DIRECTORY scope provided but requested directory was not "
                "found. "
                "This could be because the directory argument was invalid or, "
                "it is valid but has not been processed yet.");
            return false;
        }
    }

    if(this->PropertyName == "DEFINITIONS")
    {
        switch(mf->GetPolicyStatus(cmPolicies::CMP0059))
        {
            case cmPolicies::WARN:
                mf->IssueMessage(
                    cmake::AUTHOR_WARNING,
                    cmPolicies::GetPolicyWarning(cmPolicies::CMP0059));
                CM_FALLTHROUGH;
            case cmPolicies::OLD:
                return this->StoreResult(mf->GetDefineFlagsCMP0059());
            case cmPolicies::NEW:
            case cmPolicies::REQUIRED_ALWAYS:
            case cmPolicies::REQUIRED_IF_USED:
                break;
        }
    }

    // Get the property.
    return this->StoreResult(mf->GetProperty(this->PropertyName));
}

bool
cmGetPropertyCommand::HandleTargetMode()
{
    if(this->Name.empty())
    {
        this->SetError("not given name for TARGET scope.");
        return false;
    }

    if(cmTarget* target = this->Makefile->FindTargetToUse(this->Name))
    {
        if(this->PropertyName == "ALIASED_TARGET")
        {
            if(this->Makefile->IsAlias(this->Name))
            {
                return this->StoreResult(target->GetName().c_str());
            }
            return this->StoreResult(nullptr);
        }
        const char*         prop_cstr = nullptr;
        cmListFileBacktrace bt        = this->Makefile->GetBacktrace();
        cmMessenger*        messenger = this->Makefile->GetMessenger();
        if(cmTargetPropertyComputer::PassesWhitelist(
               target->GetType(), this->PropertyName, messenger, bt))
        {
            prop_cstr =
                target->GetComputedProperty(this->PropertyName, messenger, bt);
            if(!prop_cstr)
            {
                prop_cstr = target->GetProperty(this->PropertyName);
            }
        }
        return this->StoreResult(prop_cstr);
    }
    std::ostringstream e;
    e << "could not find TARGET " << this->Name
      << ".  Perhaps it has not yet been created.";
    this->SetError(e.str());
    return false;
}

bool
cmGetPropertyCommand::HandleSourceMode()
{
    if(this->Name.empty())
    {
        this->SetError("not given name for SOURCE scope.");
        return false;
    }

    // Get the source file.
    if(cmSourceFile* sf = this->Makefile->GetOrCreateSource(this->Name))
    {
        return this->StoreResult(sf->GetPropertyForUser(this->PropertyName));
    }
    std::ostringstream e;
    e << "given SOURCE name that could not be found or created: " << this->Name;
    this->SetError(e.str());
    return false;
}

bool
cmGetPropertyCommand::HandleTestMode()
{
    if(this->Name.empty())
    {
        this->SetError("not given name for TEST scope.");
        return false;
    }

    // Loop over all tests looking for matching names.
    if(cmTest* test = this->Makefile->GetTest(this->Name))
    {
        return this->StoreResult(test->GetProperty(this->PropertyName));
    }

    // If not found it is an error.
    std::ostringstream e;
    e << "given TEST name that does not exist: " << this->Name;
    this->SetError(e.str());
    return false;
}

bool
cmGetPropertyCommand::HandleVariableMode()
{
    if(!this->Name.empty())
    {
        this->SetError("given name for VARIABLE scope.");
        return false;
    }

    return this->StoreResult(this->Makefile->GetDefinition(this->PropertyName));
}

bool
cmGetPropertyCommand::HandleCacheMode()
{
    if(this->Name.empty())
    {
        this->SetError("not given name for CACHE scope.");
        return false;
    }

    const char* value = nullptr;
    if(this->Makefile->GetState()->GetCacheEntryValue(this->Name))
    {
        value = this->Makefile->GetState()->GetCacheEntryProperty(
            this->Name, this->PropertyName);
    }
    this->StoreResult(value);
    return true;
}

bool
cmGetPropertyCommand::HandleInstallMode()
{
    if(this->Name.empty())
    {
        this->SetError("not given name for INSTALL scope.");
        return false;
    }

    // Get the installed file.
    cmake* cm = this->Makefile->GetCMakeInstance();

    if(cmInstalledFile* file =
           cm->GetOrCreateInstalledFile(this->Makefile, this->Name))
    {
        std::string value;
        bool        isSet = file->GetProperty(this->PropertyName, value);

        return this->StoreResult(isSet ? value.c_str() : nullptr);
    }
    std::ostringstream e;
    e << "given INSTALL name that could not be found or created: "
      << this->Name;
    this->SetError(e.str());
    return false;
}
