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

#include <algorithm>
#include <set>

#include "cmGeneratorExpression.h"
#include "cmMakefile.h"
#include "cmSystemTools.h"

class cmExecutionStatus;

// cmIncludeDirectoryCommand
bool
cmIncludeDirectoryCommand::InitialPass(std::vector<std::string> const& args,
                                       cmExecutionStatus&)
{
    if(args.empty())
    {
        return true;
    }

    std::vector<std::string>::const_iterator i = args.begin();

    bool before = this->Makefile->IsOn("CMAKE_INCLUDE_DIRECTORIES_BEFORE");
    bool system = false;

    if((*i) == "BEFORE")
    {
        before = true;
        ++i;
    } else if((*i) == "AFTER")
    {
        before = false;
        ++i;
    }

    std::vector<std::string> beforeIncludes;
    std::vector<std::string> afterIncludes;
    std::set<std::string>    systemIncludes;

    for(; i != args.end(); ++i)
    {
        if(*i == "SYSTEM")
        {
            system = true;
            continue;
        }
        if(i->empty())
        {
            this->SetError("given empty-string as include directory.");
            return false;
        }

        std::vector<std::string> includes;

        this->GetIncludes(*i, includes);

        if(before)
        {
            beforeIncludes.insert(beforeIncludes.end(), includes.begin(),
                                  includes.end());
        } else
        {
            afterIncludes.insert(afterIncludes.end(), includes.begin(),
                                 includes.end());
        }
        if(system)
        {
            systemIncludes.insert(includes.begin(), includes.end());
        }
    }
    std::reverse(beforeIncludes.begin(), beforeIncludes.end());

    this->Makefile->AddIncludeDirectories(afterIncludes);
    this->Makefile->AddIncludeDirectories(beforeIncludes, before);
    this->Makefile->AddSystemIncludeDirectories(systemIncludes);

    return true;
}

// do a lot of cleanup on the arguments because this is one place where folks
// sometimes take the output of a program and pass it directly into this
// command not thinking that a single argument could be filled with spaces
// and newlines etc like below:
//
// "   /foo/bar
//    /boo/hoo /dingle/berry "
//
// ideally that should be three separate arguments but when sucking the
// output from a program and passing it into a command the cleanup doesn't
// always happen
//
void
cmIncludeDirectoryCommand::GetIncludes(const std::string&        arg,
                                       std::vector<std::string>& incs)
{
    // break apart any line feed arguments
    std::string::size_type pos     = 0;
    std::string::size_type lastPos = 0;
    while((pos = arg.find('\n', lastPos)) != std::string::npos)
    {
        if(pos)
        {
            std::string inc = arg.substr(lastPos, pos);
            this->NormalizeInclude(inc);
            if(!inc.empty())
            {
                incs.push_back(std::move(inc));
            }
        }
        lastPos = pos + 1;
    }
    std::string inc = arg.substr(lastPos);
    this->NormalizeInclude(inc);
    if(!inc.empty())
    {
        incs.push_back(std::move(inc));
    }
}

void
cmIncludeDirectoryCommand::NormalizeInclude(std::string& inc)
{
    std::string::size_type b = inc.find_first_not_of(" \r");
    std::string::size_type e = inc.find_last_not_of(" \r");
    if((b != std::string::npos) && (e != std::string::npos))
    {
        inc.assign(inc, b, 1 + e - b);  // copy the remaining substring
    } else
    {
        inc.clear();
        return;
    }

    if(!cmSystemTools::IsOff(inc))
    {
        cmSystemTools::ConvertToUnixSlashes(inc);

        if(!cmSystemTools::FileIsFullPath(inc))
        {
            if(!cmGeneratorExpression::StartsWithGeneratorExpression(inc))
            {
                std::string tmp = this->Makefile->GetCurrentSourceDirectory();
                tmp += "/";
                tmp += inc;
                inc = tmp;
            }
        }
    }
}
