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

#include "cmsys/Glob.hxx"

#include "cmMakefile.h"
#include "cmStateTypes.h"
#include "cmSystemTools.h"

class cmExecutionStatus;

cmFindPathCommand::cmFindPathCommand()
{
    this->EnvironmentPath   = "INCLUDE";
    this->IncludeFileInPath = false;
}

// cmFindPathCommand
bool
cmFindPathCommand::InitialPass(std::vector<std::string> const& argsIn,
                               cmExecutionStatus&)
{
    this->VariableDocumentation = "Path to a file.";
    this->CMakePathName         = "INCLUDE";
    if(!this->ParseArguments(argsIn))
    {
        return false;
    }
    if(this->AlreadyInCache)
    {
        // If the user specifies the entry on the command line without a
        // type we should add the type and docstring but keep the original
        // value.
        if(this->AlreadyInCacheWithoutMetaInfo)
        {
            this->Makefile->AddCacheDefinition(
                this->VariableName, "", this->VariableDocumentation.c_str(),
                (this->IncludeFileInPath ? cmStateEnums::FILEPATH
                                         : cmStateEnums::PATH));
        }
        return true;
    }

    std::string result = this->FindHeader();
    if(!result.empty())
    {
        this->Makefile->AddCacheDefinition(this->VariableName, result.c_str(),
                                           this->VariableDocumentation.c_str(),
                                           (this->IncludeFileInPath)
                                               ? cmStateEnums::FILEPATH
                                               : cmStateEnums::PATH);
        return true;
    }
    this->Makefile->AddCacheDefinition(
        this->VariableName, (this->VariableName + "-NOTFOUND").c_str(),
        this->VariableDocumentation.c_str(),
        (this->IncludeFileInPath) ? cmStateEnums::FILEPATH
                                  : cmStateEnums::PATH);
    return true;
}

std::string
cmFindPathCommand::FindHeader()
{
    std::string header;
    if(this->SearchFrameworkFirst || this->SearchFrameworkOnly)
    {
        header = this->FindFrameworkHeader();
    }
    if(header.empty() && !this->SearchFrameworkOnly)
    {
        header = this->FindNormalHeader();
    }
    if(header.empty() && this->SearchFrameworkLast)
    {
        header = this->FindFrameworkHeader();
    }
    return header;
}

std::string
cmFindPathCommand::FindHeaderInFramework(std::string const& file,
                                         std::string const& dir)
{
    std::string            fileName = file;
    std::string            frameWorkName;
    std::string::size_type pos = fileName.find('/');
    // if there is a / in the name try to find the header as a framework
    // For example bar/foo.h would look for:
    // bar.framework/Headers/foo.h
    if(pos != std::string::npos)
    {
        // remove the name from the slash;
        fileName      = fileName.substr(pos + 1);
        frameWorkName = file;
        frameWorkName =
            frameWorkName.substr(0, frameWorkName.size() - fileName.size() - 1);
        // if the framework has a path in it then just use the filename
        if(frameWorkName.find('/') != std::string::npos)
        {
            fileName = file;
            frameWorkName.clear();
        }
        if(!frameWorkName.empty())
        {
            std::string fpath = dir;
            fpath += frameWorkName;
            fpath += ".framework";
            std::string intPath = fpath;
            intPath += "/Headers/";
            intPath += fileName;
            if(cmSystemTools::FileExists(intPath))
            {
                if(this->IncludeFileInPath)
                {
                    return intPath;
                }
                return fpath;
            }
        }
    }
    // if it is not found yet or not a framework header, then do a glob search
    // for all frameworks in the directory: dir/*.framework/Headers/<file>
    std::string glob = dir;
    glob += "*.framework/Headers/";
    glob += file;
    cmsys::Glob globIt;
    globIt.FindFiles(glob);
    std::vector<std::string> files = globIt.GetFiles();
    if(!files.empty())
    {
        std::string fheader = cmSystemTools::CollapseFullPath(files[0]);
        if(this->IncludeFileInPath)
        {
            return fheader;
        }
        fheader.resize(fheader.size() - file.size());
        return fheader;
    }
    return "";
}

std::string
cmFindPathCommand::FindNormalHeader()
{
    std::string tryPath;
    for(std::string const& n : this->Names)
    {
        for(std::string const& sp : this->SearchPaths)
        {
            tryPath = sp;
            tryPath += n;
            if(cmSystemTools::FileExists(tryPath))
            {
                if(this->IncludeFileInPath)
                {
                    return tryPath;
                }
                return sp;
            }
        }
    }
    return "";
}

std::string
cmFindPathCommand::FindFrameworkHeader()
{
    for(std::string const& n : this->Names)
    {
        for(std::string const& sp : this->SearchPaths)
        {
            std::string fwPath = this->FindHeaderInFramework(n, sp);
            if(!fwPath.empty())
            {
                return fwPath;
            }
        }
    }
    return "";
}
