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

#include "cmGeneratorExpressionEvaluator.h"

#include <assert.h>
#include <stddef.h>
#include <utility>

cmGeneratorExpressionParser::cmGeneratorExpressionParser(
    const std::vector<cmGeneratorExpressionToken>& tokens)
: Tokens(tokens)
, NestingLevel(0)
{}

void
cmGeneratorExpressionParser::Parse(
    std::vector<cmGeneratorExpressionEvaluator*>& result)
{
    it = this->Tokens.begin();

    while(this->it != this->Tokens.end())
    {
        this->ParseContent(result);
    }
}

static void
extendText(std::vector<cmGeneratorExpressionEvaluator*>&           result,
           std::vector<cmGeneratorExpressionToken>::const_iterator it)
{
    if(!result.empty() &&
       (*(result.end() - 1))->GetType() == cmGeneratorExpressionEvaluator::Text)
    {
        TextContent* textContent =
            static_cast<TextContent*>(*(result.end() - 1));
        textContent->Extend(it->Length);
    } else
    {
        TextContent* textContent = new TextContent(it->Content, it->Length);
        result.push_back(textContent);
    }
}

static void
extendResult(std::vector<cmGeneratorExpressionEvaluator*>&       result,
             const std::vector<cmGeneratorExpressionEvaluator*>& contents)
{
    if(!result.empty() &&
       (*(result.end() - 1))->GetType() ==
           cmGeneratorExpressionEvaluator::Text &&
       (*contents.begin())->GetType() == cmGeneratorExpressionEvaluator::Text)
    {
        TextContent* textContent =
            static_cast<TextContent*>(*(result.end() - 1));
        textContent->Extend(
            static_cast<TextContent*>(*contents.begin())->GetLength());
        delete *contents.begin();
        result.insert(result.end(), contents.begin() + 1, contents.end());
    } else
    {
        result.insert(result.end(), contents.begin(), contents.end());
    }
}

void
cmGeneratorExpressionParser::ParseGeneratorExpression(
    std::vector<cmGeneratorExpressionEvaluator*>& result)
{
    assert(this->it != this->Tokens.end());
    unsigned int nestedLevel = this->NestingLevel;
    ++this->NestingLevel;

    std::vector<cmGeneratorExpressionToken>::const_iterator startToken =
        this->it - 1;

    std::vector<cmGeneratorExpressionEvaluator*> identifier;
    while(this->it->TokenType != cmGeneratorExpressionToken::EndExpression &&
          this->it->TokenType != cmGeneratorExpressionToken::ColonSeparator)
    {
        if(this->it->TokenType == cmGeneratorExpressionToken::CommaSeparator)
        {
            extendText(identifier, this->it);
            ++this->it;
        } else
        {
            this->ParseContent(identifier);
        }
        if(this->it == this->Tokens.end())
        {
            break;
        }
    }
    if(identifier.empty())
    {
        // ERROR
    }

    if(this->it != this->Tokens.end() &&
       this->it->TokenType == cmGeneratorExpressionToken::EndExpression)
    {
        GeneratorExpressionContent* content = new GeneratorExpressionContent(
            startToken->Content,
            this->it->Content - startToken->Content + this->it->Length);
        assert(this->it != this->Tokens.end());
        ++this->it;
        --this->NestingLevel;
        content->SetIdentifier(std::move(identifier));
        result.push_back(content);
        return;
    }

    std::vector<std::vector<cmGeneratorExpressionEvaluator*>> parameters;
    std::vector<std::vector<cmGeneratorExpressionToken>::const_iterator>
                                                            commaTokens;
    std::vector<cmGeneratorExpressionToken>::const_iterator colonToken;

    bool emptyParamTermination = false;

    if(this->it != this->Tokens.end() &&
       this->it->TokenType == cmGeneratorExpressionToken::ColonSeparator)
    {
        colonToken = this->it;
        parameters.resize(parameters.size() + 1);
        assert(this->it != this->Tokens.end());
        ++this->it;
        if(this->it == this->Tokens.end())
        {
            emptyParamTermination = true;
        }

        while(this->it != this->Tokens.end() &&
              this->it->TokenType == cmGeneratorExpressionToken::CommaSeparator)
        {
            commaTokens.push_back(this->it);
            parameters.resize(parameters.size() + 1);
            assert(this->it != this->Tokens.end());
            ++this->it;
            if(this->it == this->Tokens.end())
            {
                emptyParamTermination = true;
            }
        }
        while(this->it != this->Tokens.end() &&
              this->it->TokenType == cmGeneratorExpressionToken::ColonSeparator)
        {
            extendText(*(parameters.end() - 1), this->it);
            assert(this->it != this->Tokens.end());
            ++this->it;
        }
        while(this->it != this->Tokens.end() &&
              this->it->TokenType != cmGeneratorExpressionToken::EndExpression)
        {
            this->ParseContent(*(parameters.end() - 1));
            if(this->it == this->Tokens.end())
            {
                break;
            }
            while(this->it != this->Tokens.end() &&
                  this->it->TokenType ==
                      cmGeneratorExpressionToken::CommaSeparator)
            {
                commaTokens.push_back(this->it);
                parameters.resize(parameters.size() + 1);
                assert(this->it != this->Tokens.end());
                ++this->it;
                if(this->it == this->Tokens.end())
                {
                    emptyParamTermination = true;
                }
            }
            while(this->it != this->Tokens.end() &&
                  this->it->TokenType ==
                      cmGeneratorExpressionToken::ColonSeparator)
            {
                extendText(*(parameters.end() - 1), this->it);
                assert(this->it != this->Tokens.end());
                ++this->it;
            }
        }
        if(this->it != this->Tokens.end() &&
           this->it->TokenType == cmGeneratorExpressionToken::EndExpression)
        {
            --this->NestingLevel;
            assert(this->it != this->Tokens.end());
            ++this->it;
        }
    }

    if(nestedLevel != this->NestingLevel)
    {
        // There was a '$<' in the text, but no corresponding '>'. Rebuild to
        // treat the '$<' as having been plain text, along with the
        // corresponding : and , tokens that might have been found.
        extendText(result, startToken);
        extendResult(result, identifier);
        if(!parameters.empty())
        {
            extendText(result, colonToken);

            typedef std::vector<cmGeneratorExpressionEvaluator*>
                                                            EvaluatorVector;
            typedef std::vector<cmGeneratorExpressionToken> TokenVector;
            std::vector<EvaluatorVector>::const_iterator    pit =
                parameters.begin();
            const std::vector<EvaluatorVector>::const_iterator pend =
                parameters.end();
            std::vector<TokenVector::const_iterator>::const_iterator commaIt =
                commaTokens.begin();
            assert(parameters.size() > commaTokens.size());
            for(; pit != pend; ++pit, ++commaIt)
            {
                if(!pit->empty() && !emptyParamTermination)
                {
                    extendResult(result, *pit);
                }
                if(commaIt != commaTokens.end())
                {
                    extendText(result, *commaIt);
                } else
                {
                    break;
                }
            }
        }
        return;
    }

    size_t contentLength = ((this->it - 1)->Content - startToken->Content) +
                           (this->it - 1)->Length;
    GeneratorExpressionContent* content =
        new GeneratorExpressionContent(startToken->Content, contentLength);
    content->SetIdentifier(std::move(identifier));
    content->SetParameters(std::move(parameters));
    result.push_back(content);
}

void
cmGeneratorExpressionParser::ParseContent(
    std::vector<cmGeneratorExpressionEvaluator*>& result)
{
    assert(this->it != this->Tokens.end());
    switch(this->it->TokenType)
    {
        case cmGeneratorExpressionToken::Text:
        {
            if(this->NestingLevel == 0)
            {
                if(!result.empty() && (*(result.end() - 1))->GetType() ==
                                          cmGeneratorExpressionEvaluator::Text)
                {
                    // A comma in 'plain text' could have split text that should
                    // otherwise be continuous. Extend the last text content
                    // instead of creating a new one.
                    TextContent* textContent =
                        static_cast<TextContent*>(*(result.end() - 1));
                    textContent->Extend(this->it->Length);
                    assert(this->it != this->Tokens.end());
                    ++this->it;
                    return;
                }
            }
            cmGeneratorExpressionEvaluator* n =
                new TextContent(this->it->Content, this->it->Length);
            result.push_back(n);
            assert(this->it != this->Tokens.end());
            ++this->it;
            return;
        }
        case cmGeneratorExpressionToken::BeginExpression:
            assert(this->it != this->Tokens.end());
            ++this->it;
            this->ParseGeneratorExpression(result);
            return;
        case cmGeneratorExpressionToken::EndExpression:
        case cmGeneratorExpressionToken::ColonSeparator:
        case cmGeneratorExpressionToken::CommaSeparator:
            if(this->NestingLevel == 0)
            {
                extendText(result, this->it);
            } else
            {
                assert(false && "Got unexpected syntax token.");
            }
            assert(this->it != this->Tokens.end());
            ++this->it;
            return;
    }
    assert(false && "Unhandled token in generator expression.");
}
