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

#include "cmConditionEvaluator.h"
#include "cmExecutionStatus.h"
#include "cmExpandedCommandArgument.h"
#include "cmMakefile.h"
#include "cmSystemTools.h"
#include "cmake.h"

#include <memory>  // IWYU pragma: keep

cmWhileFunctionBlocker::cmWhileFunctionBlocker(cmMakefile* mf)
: Makefile(mf)
, Depth(0)
{
    this->Makefile->PushLoopBlock();
}

cmWhileFunctionBlocker::~cmWhileFunctionBlocker()
{
    this->Makefile->PopLoopBlock();
}

bool
cmWhileFunctionBlocker::IsFunctionBlocked(const cmListFileFunction& lff,
                                          cmMakefile&               mf,
                                          cmExecutionStatus&        inStatus)
{
    // at end of for each execute recorded commands
    if(lff.Name.Lower == "while")
    {
        // record the number of while commands past this one
        this->Depth++;
    } else if(lff.Name.Lower == "endwhile")
    {
        // if this is the endwhile for this while loop then execute
        if(!this->Depth)
        {
            // Remove the function blocker for this scope or bail.
            std::unique_ptr<cmFunctionBlocker> fb(
                mf.RemoveFunctionBlocker(this, lff));
            if(!fb.get())
            {
                return false;
            }

            std::string errorString;

            std::vector<cmExpandedCommandArgument> expandedArguments;
            mf.ExpandArguments(this->Args, expandedArguments);
            cmake::MessageType messageType;

            cmListFileContext execContext = this->GetStartingContext();

            cmCommandContext commandContext;
            commandContext.Line = execContext.Line;
            commandContext.Name = execContext.Name;

            cmConditionEvaluator conditionEvaluator(
                mf, this->GetStartingContext(),
                mf.GetBacktrace(commandContext));

            bool isTrue = conditionEvaluator.IsTrue(expandedArguments,
                                                    errorString, messageType);

            while(isTrue)
            {
                if(!errorString.empty())
                {
                    std::string err = "had incorrect arguments: ";
                    for(cmListFileArgument const& arg : this->Args)
                    {
                        err += (arg.Delim ? "\"" : "");
                        err += arg.Value;
                        err += (arg.Delim ? "\"" : "");
                        err += " ";
                    }
                    err += "(";
                    err += errorString;
                    err += ").";
                    mf.IssueMessage(messageType, err);
                    if(messageType == cmake::FATAL_ERROR)
                    {
                        cmSystemTools::SetFatalErrorOccured();
                        return true;
                    }
                }

                // Invoke all the functions that were collected in the block.
                for(cmListFileFunction const& fn : this->Functions)
                {
                    cmExecutionStatus status;
                    mf.ExecuteCommand(fn, status);
                    if(status.GetReturnInvoked())
                    {
                        inStatus.SetReturnInvoked();
                        return true;
                    }
                    if(status.GetBreakInvoked())
                    {
                        return true;
                    }
                    if(status.GetContinueInvoked())
                    {
                        break;
                    }
                    if(cmSystemTools::GetFatalErrorOccured())
                    {
                        return true;
                    }
                }
                expandedArguments.clear();
                mf.ExpandArguments(this->Args, expandedArguments);
                isTrue = conditionEvaluator.IsTrue(expandedArguments,
                                                   errorString, messageType);
            }
            return true;
        }
        // decrement for each nested while that ends
        this->Depth--;
    }

    // record the command
    this->Functions.push_back(lff);

    // always return true
    return true;
}

bool
cmWhileFunctionBlocker::ShouldRemove(const cmListFileFunction& lff, cmMakefile&)
{
    if(lff.Name.Lower == "endwhile")
    {
        // if the endwhile has arguments, then make sure
        // they match the arguments of the matching while
        if(lff.Arguments.empty() || lff.Arguments == this->Args)
        {
            return true;
        }
    }
    return false;
}

bool
cmWhileCommand::InvokeInitialPass(const std::vector<cmListFileArgument>& args,
                                  cmExecutionStatus&)
{
    if(args.empty())
    {
        this->SetError("called with incorrect number of arguments");
        return false;
    }

    // create a function blocker
    cmWhileFunctionBlocker* f = new cmWhileFunctionBlocker(this->Makefile);
    f->Args                   = args;
    this->Makefile->AddFunctionBlocker(f);

    return true;
}
