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

#include "cmsys/RegularExpression.hxx"
#include <algorithm>
#include <assert.h>
#include <functional>
#include <iterator>
#include <set>
#include <sstream>
#include <stdexcept>
#include <stdio.h>
#include <stdlib.h>  // required for atoi

#include "cmAlgorithms.h"
#include "cmGeneratorExpression.h"
#include "cmMakefile.h"
#include "cmPolicies.h"
#include "cmStringReplaceHelper.h"
#include "cmSystemTools.h"
#include "cmake.h"

class cmExecutionStatus;

bool
cmListCommand::InitialPass(std::vector<std::string> const& args,
                           cmExecutionStatus&)
{
    if(args.size() < 2)
    {
        this->SetError("must be called with at least two arguments.");
        return false;
    }

    const std::string& subCommand = args[0];
    if(subCommand == "LENGTH")
    {
        return this->HandleLengthCommand(args);
    }
    if(subCommand == "GET")
    {
        return this->HandleGetCommand(args);
    }
    if(subCommand == "APPEND")
    {
        return this->HandleAppendCommand(args);
    }
    if(subCommand == "FIND")
    {
        return this->HandleFindCommand(args);
    }
    if(subCommand == "INSERT")
    {
        return this->HandleInsertCommand(args);
    }
    if(subCommand == "JOIN")
    {
        return this->HandleJoinCommand(args);
    }
    if(subCommand == "REMOVE_AT")
    {
        return this->HandleRemoveAtCommand(args);
    }
    if(subCommand == "REMOVE_ITEM")
    {
        return this->HandleRemoveItemCommand(args);
    }
    if(subCommand == "REMOVE_DUPLICATES")
    {
        return this->HandleRemoveDuplicatesCommand(args);
    }
    if(subCommand == "TRANSFORM")
    {
        return this->HandleTransformCommand(args);
    }
    if(subCommand == "SORT")
    {
        return this->HandleSortCommand(args);
    }
    if(subCommand == "SUBLIST")
    {
        return this->HandleSublistCommand(args);
    }
    if(subCommand == "REVERSE")
    {
        return this->HandleReverseCommand(args);
    }
    if(subCommand == "FILTER")
    {
        return this->HandleFilterCommand(args);
    }

    std::string e = "does not recognize sub-command " + subCommand;
    this->SetError(e);
    return false;
}

bool
cmListCommand::GetListString(std::string& listString, const std::string& var)
{
    // get the old value
    const char* cacheValue = this->Makefile->GetDefinition(var);
    if(!cacheValue)
    {
        return false;
    }
    listString = cacheValue;
    return true;
}

bool
cmListCommand::GetList(std::vector<std::string>& list, const std::string& var)
{
    std::string listString;
    if(!this->GetListString(listString, var))
    {
        return false;
    }
    // if the size of the list
    if(listString.empty())
    {
        return true;
    }
    // expand the variable into a list
    cmSystemTools::ExpandListArgument(listString, list, true);
    // if no empty elements then just return
    if(std::find(list.begin(), list.end(), std::string()) == list.end())
    {
        return true;
    }
    // if we have empty elements we need to check policy CMP0007
    switch(this->Makefile->GetPolicyStatus(cmPolicies::CMP0007))
    {
        case cmPolicies::WARN:
        {
            // Default is to warn and use old behavior
            // OLD behavior is to allow compatibility, so recall
            // ExpandListArgument without the true which will remove
            // empty values
            list.clear();
            cmSystemTools::ExpandListArgument(listString, list);
            std::string warn =
                cmPolicies::GetPolicyWarning(cmPolicies::CMP0007);
            warn += " List has value = [";
            warn += listString;
            warn += "].";
            this->Makefile->IssueMessage(cmake::AUTHOR_WARNING, warn);
            return true;
        }
        case cmPolicies::OLD:
            // OLD behavior is to allow compatibility, so recall
            // ExpandListArgument without the true which will remove
            // empty values
            list.clear();
            cmSystemTools::ExpandListArgument(listString, list);
            return true;
        case cmPolicies::NEW:
            return true;
        case cmPolicies::REQUIRED_IF_USED:
        case cmPolicies::REQUIRED_ALWAYS:
            this->Makefile->IssueMessage(
                cmake::FATAL_ERROR,
                cmPolicies::GetRequiredPolicyError(cmPolicies::CMP0007));
            return false;
    }
    return true;
}

bool
cmListCommand::HandleLengthCommand(std::vector<std::string> const& args)
{
    if(args.size() != 3)
    {
        this->SetError("sub-command LENGTH requires two arguments.");
        return false;
    }

    const std::string&       listName     = args[1];
    const std::string&       variableName = args[args.size() - 1];
    std::vector<std::string> varArgsExpanded;
    // do not check the return value here
    // if the list var is not found varArgsExpanded will have size 0
    // and we will return 0
    this->GetList(varArgsExpanded, listName);
    size_t length = varArgsExpanded.size();
    char   buffer[1024];
    sprintf(buffer, "%d", static_cast<int>(length));

    this->Makefile->AddDefinition(variableName, buffer);
    return true;
}

bool
cmListCommand::HandleGetCommand(std::vector<std::string> const& args)
{
    if(args.size() < 4)
    {
        this->SetError("sub-command GET requires at least three arguments.");
        return false;
    }

    const std::string& listName     = args[1];
    const std::string& variableName = args[args.size() - 1];
    // expand the variable
    std::vector<std::string> varArgsExpanded;
    if(!this->GetList(varArgsExpanded, listName))
    {
        this->Makefile->AddDefinition(variableName, "NOTFOUND");
        return true;
    }
    // FIXME: Add policy to make non-existing lists an error like empty lists.
    if(varArgsExpanded.empty())
    {
        this->SetError("GET given empty list");
        return false;
    }

    std::string value;
    size_t      cc;
    const char* sep   = "";
    size_t      nitem = varArgsExpanded.size();
    for(cc = 2; cc < args.size() - 1; cc++)
    {
        int item = atoi(args[cc].c_str());
        value += sep;
        sep = ";";
        if(item < 0)
        {
            item = static_cast<int>(nitem) + item;
        }
        if(item < 0 || nitem <= static_cast<size_t>(item))
        {
            std::ostringstream str;
            str << "index: " << item << " out of range (-" << nitem << ", "
                << nitem - 1 << ")";
            this->SetError(str.str());
            return false;
        }
        value += varArgsExpanded[item];
    }

    this->Makefile->AddDefinition(variableName, value.c_str());
    return true;
}

bool
cmListCommand::HandleAppendCommand(std::vector<std::string> const& args)
{
    assert(args.size() >= 2);

    // Skip if nothing to append.
    if(args.size() < 3)
    {
        return true;
    }

    const std::string& listName = args[1];
    // expand the variable
    std::string listString;
    this->GetListString(listString, listName);

    if(!listString.empty() && !args.empty())
    {
        listString += ";";
    }
    listString += cmJoin(cmMakeRange(args).advance(2), ";");

    this->Makefile->AddDefinition(listName, listString.c_str());
    return true;
}

bool
cmListCommand::HandleFindCommand(std::vector<std::string> const& args)
{
    if(args.size() != 4)
    {
        this->SetError("sub-command FIND requires three arguments.");
        return false;
    }

    const std::string& listName     = args[1];
    const std::string& variableName = args[args.size() - 1];
    // expand the variable
    std::vector<std::string> varArgsExpanded;
    if(!this->GetList(varArgsExpanded, listName))
    {
        this->Makefile->AddDefinition(variableName, "-1");
        return true;
    }

    std::vector<std::string>::iterator it =
        std::find(varArgsExpanded.begin(), varArgsExpanded.end(), args[2]);
    if(it != varArgsExpanded.end())
    {
        std::ostringstream indexStream;
        indexStream << std::distance(varArgsExpanded.begin(), it);
        this->Makefile->AddDefinition(variableName, indexStream.str().c_str());
        return true;
    }

    this->Makefile->AddDefinition(variableName, "-1");
    return true;
}

bool
cmListCommand::HandleInsertCommand(std::vector<std::string> const& args)
{
    if(args.size() < 4)
    {
        this->SetError("sub-command INSERT requires at least three arguments.");
        return false;
    }

    const std::string& listName = args[1];

    // expand the variable
    int                      item = atoi(args[2].c_str());
    std::vector<std::string> varArgsExpanded;
    if((!this->GetList(varArgsExpanded, listName) || varArgsExpanded.empty()) &&
       item != 0)
    {
        std::ostringstream str;
        str << "index: " << item << " out of range (0, 0)";
        this->SetError(str.str());
        return false;
    }

    if(!varArgsExpanded.empty())
    {
        size_t nitem = varArgsExpanded.size();
        if(item < 0)
        {
            item = static_cast<int>(nitem) + item;
        }
        if(item < 0 || nitem < static_cast<size_t>(item))
        {
            std::ostringstream str;
            str << "index: " << item << " out of range (-"
                << varArgsExpanded.size() << ", " << varArgsExpanded.size()
                << ")";
            this->SetError(str.str());
            return false;
        }
    }

    varArgsExpanded.insert(varArgsExpanded.begin() + item, args.begin() + 3,
                           args.end());

    std::string value = cmJoin(varArgsExpanded, ";");
    this->Makefile->AddDefinition(listName, value.c_str());
    return true;
}

bool
cmListCommand::HandleJoinCommand(std::vector<std::string> const& args)
{
    if(args.size() != 4)
    {
        std::ostringstream error;
        error << "sub-command JOIN requires three arguments ("
              << args.size() - 1 << " found).";
        this->SetError(error.str());
        return false;
    }

    const std::string& listName     = args[1];
    const std::string& glue         = args[2];
    const std::string& variableName = args[3];

    // expand the variable
    std::vector<std::string> varArgsExpanded;
    if(!this->GetList(varArgsExpanded, listName))
    {
        this->Makefile->AddDefinition(variableName, "");
        return true;
    }

    std::string value = cmJoin(
        cmMakeRange(varArgsExpanded.begin(), varArgsExpanded.end()), glue);

    this->Makefile->AddDefinition(variableName, value.c_str());
    return true;
}

bool
cmListCommand::HandleRemoveItemCommand(std::vector<std::string> const& args)
{
    if(args.size() < 3)
    {
        this->SetError(
            "sub-command REMOVE_ITEM requires two or more arguments.");
        return false;
    }

    const std::string& listName = args[1];
    // expand the variable
    std::vector<std::string> varArgsExpanded;
    if(!this->GetList(varArgsExpanded, listName))
    {
        this->SetError("sub-command REMOVE_ITEM requires list to be present.");
        return false;
    }

    std::vector<std::string> remove(args.begin() + 2, args.end());
    std::sort(remove.begin(), remove.end());
    std::vector<std::string>::const_iterator remEnd =
        std::unique(remove.begin(), remove.end());
    std::vector<std::string>::const_iterator remBegin = remove.begin();

    std::vector<std::string>::const_iterator argsEnd =
        cmRemoveMatching(varArgsExpanded, cmMakeRange(remBegin, remEnd));
    std::vector<std::string>::const_iterator argsBegin =
        varArgsExpanded.begin();
    std::string value = cmJoin(cmMakeRange(argsBegin, argsEnd), ";");
    this->Makefile->AddDefinition(listName, value.c_str());
    return true;
}

bool
cmListCommand::HandleReverseCommand(std::vector<std::string> const& args)
{
    assert(args.size() >= 2);
    if(args.size() > 2)
    {
        this->SetError("sub-command REVERSE only takes one argument.");
        return false;
    }

    const std::string& listName = args[1];
    // expand the variable
    std::vector<std::string> varArgsExpanded;
    if(!this->GetList(varArgsExpanded, listName))
    {
        this->SetError("sub-command REVERSE requires list to be present.");
        return false;
    }

    std::string value = cmJoin(cmReverseRange(varArgsExpanded), ";");

    this->Makefile->AddDefinition(listName, value.c_str());
    return true;
}

bool
cmListCommand::HandleRemoveDuplicatesCommand(
    std::vector<std::string> const& args)
{
    assert(args.size() >= 2);
    if(args.size() > 2)
    {
        this->SetError(
            "sub-command REMOVE_DUPLICATES only takes one argument.");
        return false;
    }

    const std::string& listName = args[1];
    // expand the variable
    std::vector<std::string> varArgsExpanded;
    if(!this->GetList(varArgsExpanded, listName))
    {
        this->SetError(
            "sub-command REMOVE_DUPLICATES requires list to be present.");
        return false;
    }

    std::vector<std::string>::const_iterator argsEnd =
        cmRemoveDuplicates(varArgsExpanded);
    std::vector<std::string>::const_iterator argsBegin =
        varArgsExpanded.begin();
    std::string value = cmJoin(cmMakeRange(argsBegin, argsEnd), ";");

    this->Makefile->AddDefinition(listName, value.c_str());
    return true;
}

// Helpers for list(TRANSFORM <list> ...)
namespace
{
using transform_type = std::function<std::string(const std::string&)>;

class transform_error : public std::runtime_error
{
public:
    transform_error(const std::string& error)
    : std::runtime_error(error)
    {}
};

class TransformSelector
{
public:
    virtual ~TransformSelector() {}

    std::string Tag;

    virtual bool Validate(std::size_t count = 0) = 0;

    virtual bool InSelection(const std::string&) = 0;

    virtual void Transform(std::vector<std::string>& list,
                           const transform_type&     transform)
    {
        std::transform(list.begin(), list.end(), list.begin(), transform);
    }

protected:
    TransformSelector(std::string&& tag)
    : Tag(std::move(tag))
    {}
};
class TransformNoSelector : public TransformSelector
{
public:
    TransformNoSelector()
    : TransformSelector("NO SELECTOR")
    {}

    bool Validate(std::size_t) override { return true; }

    bool InSelection(const std::string&) override { return true; }
};
class TransformSelectorRegex : public TransformSelector
{
public:
    TransformSelectorRegex(const std::string& regex)
    : TransformSelector("REGEX")
    , Regex(regex)
    {}

    bool Validate(std::size_t) override { return this->Regex.is_valid(); }

    bool InSelection(const std::string& value) override
    {
        return this->Regex.find(value);
    }

    cmsys::RegularExpression Regex;
};
class TransformSelectorIndexes : public TransformSelector
{
public:
    std::vector<int> Indexes;

    bool InSelection(const std::string&) override { return true; }

    void Transform(std::vector<std::string>& list,
                   const transform_type&     transform) override
    {
        this->Validate(list.size());

        for(auto index : this->Indexes)
        {
            list[index] = transform(list[index]);
        }
    }

protected:
    TransformSelectorIndexes(std::string&& tag)
    : TransformSelector(std::move(tag))
    {}
    TransformSelectorIndexes(std::string&& tag, std::vector<int>&& indexes)
    : TransformSelector(std::move(tag))
    , Indexes(indexes)
    {}

    int NormalizeIndex(int index, std::size_t count)
    {
        if(index < 0)
        {
            index = static_cast<int>(count) + index;
        }
        if(index < 0 || count <= static_cast<std::size_t>(index))
        {
            std::ostringstream str;
            str << "sub-command TRANSFORM, selector " << this->Tag
                << ", index: " << index << " out of range (-" << count << ", "
                << count - 1 << ").";
            throw transform_error(str.str());
        }
        return index;
    }
};
class TransformSelectorAt : public TransformSelectorIndexes
{
public:
    TransformSelectorAt(std::vector<int>&& indexes)
    : TransformSelectorIndexes("AT", std::move(indexes))
    {}

    bool Validate(std::size_t count) override
    {
        decltype(Indexes) indexes;

        for(auto index : Indexes)
        {
            indexes.push_back(this->NormalizeIndex(index, count));
        }
        this->Indexes = std::move(indexes);

        return true;
    }
};
class TransformSelectorFor : public TransformSelectorIndexes
{
public:
    TransformSelectorFor(int start, int stop, int step)
    : TransformSelectorIndexes("FOR")
    , Start(start)
    , Stop(stop)
    , Step(step)
    {}

    bool Validate(std::size_t count) override
    {
        this->Start = this->NormalizeIndex(this->Start, count);
        this->Stop  = this->NormalizeIndex(this->Stop, count);

        // compute indexes
        auto size = (this->Stop - this->Start + 1) / this->Step;
        if((this->Stop - this->Start + 1) % this->Step != 0)
        {
            size += 1;
        }

        this->Indexes.resize(size);
        auto start = this->Start, step = this->Step;
        std::generate(this->Indexes.begin(), this->Indexes.end(),
                      [&start, step]() -> int {
                          auto r = start;
                          start += step;
                          return r;
                      });

        return true;
    }

private:
    int Start, Stop, Step;
};

class TransformAction
{
public:
    virtual ~TransformAction() {}

    virtual std::string Transform(const std::string& input) = 0;
};
class TransformReplace : public TransformAction
{
public:
    TransformReplace(const std::vector<std::string>& arguments,
                     cmMakefile*                     makefile)
    : ReplaceHelper(arguments[0], arguments[1], makefile)
    {
        makefile->ClearMatches();

        if(!this->ReplaceHelper.IsRegularExpressionValid())
        {
            std::ostringstream error;
            error << "sub-command TRANSFORM, action REPLACE: Failed to compile "
                     "regex \""
                  << arguments[0] << "\".";
            throw transform_error(error.str());
        }
        if(!this->ReplaceHelper.IsReplaceExpressionValid())
        {
            std::ostringstream error;
            error << "sub-command TRANSFORM, action REPLACE: "
                  << this->ReplaceHelper.GetError() << ".";
            throw transform_error(error.str());
        }
    }

    std::string Transform(const std::string& input) override
    {
        // Scan through the input for all matches.
        std::string output;

        if(!this->ReplaceHelper.Replace(input, output))
        {
            std::ostringstream error;
            error << "sub-command TRANSFORM, action REPLACE: "
                  << this->ReplaceHelper.GetError() << ".";
            throw transform_error(error.str());
        }

        return output;
    }

private:
    cmStringReplaceHelper ReplaceHelper;
};
}

bool
cmListCommand::HandleTransformCommand(std::vector<std::string> const& args)
{
    if(args.size() < 3)
    {
        this->SetError(
            "sub-command TRANSFORM requires an action to be specified.");
        return false;
    }

    // Structure collecting all elements of the command
    struct Command
    {
        Command(const std::string& listName)
        : ListName(listName)
        , OutputName(listName)
        {}

        std::string                        Name;
        std::string                        ListName;
        std::vector<std::string>           Arguments;
        std::unique_ptr<TransformAction>   Action;
        std::unique_ptr<TransformSelector> Selector;
        std::string                        OutputName;
    } command(args[1]);

    // Descriptor of action
    // Arity: number of arguments required for the action
    // Transform: lambda function implementing the action
    struct ActionDescriptor
    {
        ActionDescriptor(const std::string& name)
        : Name(name)
        {}
        ActionDescriptor(const std::string& name, int arity,
                         const transform_type& transform)
        : Name(name)
        , Arity(arity)
        , Transform(transform)
        {}

        operator const std::string&() const { return Name; }

        std::string    Name;
        int            Arity = 0;
        transform_type Transform;
    };

    // Build a set of supported actions.
    std::set<ActionDescriptor,
             std::function<bool(const std::string&, const std::string&)>>
        descriptors(
            [](const std::string& x, const std::string& y) { return x < y; });
    descriptors = {
        { "APPEND", 1,
          [&command](const std::string& s) -> std::string {
              if(command.Selector->InSelection(s))
              {
                  return s + command.Arguments[0];
              }

              return s;
          } },
        { "PREPEND", 1,
          [&command](const std::string& s) -> std::string {
              if(command.Selector->InSelection(s))
              {
                  return command.Arguments[0] + s;
              }

              return s;
          } },
        { "TOUPPER", 0,
          [&command](const std::string& s) -> std::string {
              if(command.Selector->InSelection(s))
              {
                  return cmSystemTools::UpperCase(s);
              }

              return s;
          } },
        { "TOLOWER", 0,
          [&command](const std::string& s) -> std::string {
              if(command.Selector->InSelection(s))
              {
                  return cmSystemTools::LowerCase(s);
              }

              return s;
          } },
        { "STRIP", 0,
          [&command](const std::string& s) -> std::string {
              if(command.Selector->InSelection(s))
              {
                  return cmSystemTools::TrimWhitespace(s);
              }

              return s;
          } },
        { "GENEX_STRIP", 0,
          [&command](const std::string& s) -> std::string {
              if(command.Selector->InSelection(s))
              {
                  return cmGeneratorExpression::Preprocess(
                      s, cmGeneratorExpression::StripAllGeneratorExpressions);
              }

              return s;
          } },
        { "REPLACE", 2,
          [&command](const std::string& s) -> std::string {
              if(command.Selector->InSelection(s))
              {
                  return command.Action->Transform(s);
              }

              return s;
          } }
    };

    using size_type = std::vector<std::string>::size_type;
    size_type index = 2;

    // Parse all possible function parameters
    auto descriptor = descriptors.find(args[index]);

    if(descriptor == descriptors.end())
    {
        std::ostringstream error;
        error << " sub-command TRANSFORM, " << args[index]
              << " invalid action.";
        this->SetError(error.str());
        return false;
    }

    // Action arguments
    index += 1;
    if(args.size() < index + descriptor->Arity)
    {
        std::ostringstream error;
        error << "sub-command TRANSFORM, action " << descriptor->Name
              << " expects " << descriptor->Arity << " argument(s).";
        this->SetError(error.str());
        return false;
    }

    command.Name = descriptor->Name;
    index += descriptor->Arity;
    if(descriptor->Arity > 0)
    {
        command.Arguments =
            std::vector<std::string>(args.begin() + 3, args.begin() + index);
    }

    if(command.Name == "REPLACE")
    {
        try
        {
            command.Action = cm::make_unique<TransformReplace>(
                command.Arguments, this->Makefile);
        } catch(const transform_error& e)
        {
            this->SetError(e.what());
            return false;
        }
    }

    const std::string REGEX{ "REGEX" }, AT{ "AT" }, FOR{ "FOR" },
        OUTPUT_VARIABLE{ "OUTPUT_VARIABLE" };

    // handle optional arguments
    while(args.size() > index)
    {
        if((args[index] == REGEX || args[index] == AT || args[index] == FOR) &&
           command.Selector)
        {
            std::ostringstream error;
            error << "sub-command TRANSFORM, selector already specified ("
                  << command.Selector->Tag << ").";
            this->SetError(error.str());
            return false;
        }

        // REGEX selector
        if(args[index] == REGEX)
        {
            if(args.size() == ++index)
            {
                this->SetError("sub-command TRANSFORM, selector REGEX expects "
                               "'regular expression' argument.");
                return false;
            }

            command.Selector =
                cm::make_unique<TransformSelectorRegex>(args[index]);
            if(!command.Selector->Validate())
            {
                std::ostringstream error;
                error << "sub-command TRANSFORM, selector REGEX failed to "
                         "compile "
                         "regex \"";
                error << args[index] << "\".";
                this->SetError(error.str());
                return false;
            }

            index += 1;
            continue;
        }

        // AT selector
        if(args[index] == AT)
        {
            // get all specified indexes
            std::vector<int> indexes;
            while(args.size() > ++index)
            {
                std::size_t pos;
                int         value;

                try
                {
                    value = std::stoi(args[index], &pos);
                    if(pos != args[index].length())
                    {
                        // this is not a number, stop processing
                        break;
                    }
                    indexes.push_back(value);
                } catch(const std::invalid_argument&)
                {
                    // this is not a number, stop processing
                    break;
                }
            }

            if(indexes.empty())
            {
                this->SetError(
                    "sub-command TRANSFORM, selector AT expects at least one "
                    "numeric value.");
                return false;
            }

            command.Selector =
                cm::make_unique<TransformSelectorAt>(std::move(indexes));

            continue;
        }

        // FOR selector
        if(args[index] == FOR)
        {
            if(args.size() <= ++index + 1)
            {
                this->SetError(
                    "sub-command TRANSFORM, selector FOR expects, at least,"
                    " two arguments.");
                return false;
            }

            int  start = 0, stop = 0, step = 1;
            bool valid = true;
            try
            {
                std::size_t pos;

                start = std::stoi(args[index], &pos);
                if(pos != args[index].length())
                {
                    // this is not a number
                    valid = false;
                } else
                {
                    stop = std::stoi(args[++index], &pos);
                    if(pos != args[index].length())
                    {
                        // this is not a number
                        valid = false;
                    }
                }
            } catch(const std::invalid_argument&)
            {
                // this is not numbers
                valid = false;
            }
            if(!valid)
            {
                this->SetError("sub-command TRANSFORM, selector FOR expects, "
                               "at least, two numeric values.");
                return false;
            }
            // try to read a third numeric value for step
            if(args.size() > ++index)
            {
                try
                {
                    std::size_t pos;

                    step = std::stoi(args[index], &pos);
                    if(pos != args[index].length())
                    {
                        // this is not a number
                        step = 1;
                    } else
                    {
                        index += 1;
                    }
                } catch(const std::invalid_argument&)
                {
                    // this is not number, ignore exception
                }
            }

            if(step < 0)
            {
                this->SetError("sub-command TRANSFORM, selector FOR expects "
                               "non negative numeric value for <step>.");
            }

            command.Selector =
                cm::make_unique<TransformSelectorFor>(start, stop, step);

            continue;
        }

        // output variable
        if(args[index] == OUTPUT_VARIABLE)
        {
            if(args.size() == ++index)
            {
                this->SetError("sub-command TRANSFORM, OUTPUT_VARIABLE "
                               "expects variable name argument.");
                return false;
            }

            command.OutputName = args[index++];
            continue;
        }

        std::ostringstream error;
        error << "sub-command TRANSFORM, '"
              << cmJoin(cmMakeRange(args).advance(index), " ")
              << "': unexpected argument(s).";
        this->SetError(error.str());
        return false;
    }

    // expand the list variable
    std::vector<std::string> varArgsExpanded;
    if(!this->GetList(varArgsExpanded, command.ListName))
    {
        this->Makefile->AddDefinition(command.OutputName, "");
        return true;
    }

    if(!command.Selector)
    {
        // no selector specified, apply transformation to all elements
        command.Selector = cm::make_unique<TransformNoSelector>();
    }

    try
    {
        command.Selector->Transform(varArgsExpanded, descriptor->Transform);
    } catch(const transform_error& e)
    {
        this->SetError(e.what());
        return false;
    }

    this->Makefile->AddDefinition(command.OutputName,
                                  cmJoin(varArgsExpanded, ";").c_str());

    return true;
}

class cmStringSorter
{
public:
    enum class Order
    {
        UNINITIALIZED,
        ASCENDING,
        DESCENDING,
    };

    enum class Compare
    {
        UNINITIALIZED,
        STRING,
        FILE_BASENAME,
    };
    enum class CaseSensitivity
    {
        UNINITIALIZED,
        SENSITIVE,
        INSENSITIVE,
    };

protected:
    typedef std::string (*StringFilter)(const std::string& in);
    StringFilter GetCompareFilter(Compare compare)
    {
        return (compare == Compare::FILE_BASENAME)
                   ? cmSystemTools::GetFilenameName
                   : nullptr;
    }

    StringFilter GetCaseFilter(CaseSensitivity sensitivity)
    {
        return (sensitivity == CaseSensitivity::INSENSITIVE)
                   ? cmSystemTools::LowerCase
                   : nullptr;
    }

public:
    cmStringSorter(Compare compare, CaseSensitivity caseSensitivity,
                   Order desc = Order::ASCENDING)
    : filters{ GetCompareFilter(compare), GetCaseFilter(caseSensitivity) }
    , descending(desc == Order::DESCENDING)
    {}

    std::string ApplyFilter(const std::string& argument)
    {
        std::string result = argument;
        for(auto filter : filters)
        {
            if(filter != nullptr)
            {
                result = filter(result);
            }
        }
        return result;
    }

    bool operator()(const std::string& a, const std::string& b)
    {
        std::string af = ApplyFilter(a);
        std::string bf = ApplyFilter(b);
        bool        result;
        if(descending)
        {
            result = bf < af;
        } else
        {
            result = af < bf;
        }
        return result;
    }

protected:
    StringFilter filters[2] = { nullptr, nullptr };
    bool         descending;
};

bool
cmListCommand::HandleSortCommand(std::vector<std::string> const& args)
{
    assert(args.size() >= 2);
    if(args.size() > 8)
    {
        this->SetError("sub-command SORT only takes up to six arguments.");
        return false;
    }

    auto sortCompare         = cmStringSorter::Compare::UNINITIALIZED;
    auto sortCaseSensitivity = cmStringSorter::CaseSensitivity::UNINITIALIZED;
    auto sortOrder           = cmStringSorter::Order::UNINITIALIZED;

    size_t            argumentIndex = 2;
    const std::string messageHint   = "sub-command SORT ";

    while(argumentIndex < args.size())
    {
        const std::string option = args[argumentIndex++];
        if(option == "COMPARE")
        {
            if(sortCompare != cmStringSorter::Compare::UNINITIALIZED)
            {
                std::string error = messageHint + "option \"" + option +
                                    "\" has been specified multiple times.";
                this->SetError(error);
                return false;
            }
            if(argumentIndex < args.size())
            {
                const std::string argument = args[argumentIndex++];
                if(argument == "STRING")
                {
                    sortCompare = cmStringSorter::Compare::STRING;
                } else if(argument == "FILE_BASENAME")
                {
                    sortCompare = cmStringSorter::Compare::FILE_BASENAME;
                } else
                {
                    std::string error = messageHint + "value \"" + argument +
                                        "\" for option \"" + option +
                                        "\" is invalid.";
                    this->SetError(error);
                    return false;
                }
            } else
            {
                std::string error = messageHint +
                                    "missing argument for option \"" + option +
                                    "\".";
                this->SetError(error);
                return false;
            }
        } else if(option == "CASE")
        {
            if(sortCaseSensitivity !=
               cmStringSorter::CaseSensitivity::UNINITIALIZED)
            {
                std::string error = messageHint + "option \"" + option +
                                    "\" has been specified multiple times.";
                this->SetError(error);
                return false;
            }
            if(argumentIndex < args.size())
            {
                const std::string argument = args[argumentIndex++];
                if(argument == "SENSITIVE")
                {
                    sortCaseSensitivity =
                        cmStringSorter::CaseSensitivity::SENSITIVE;
                } else if(argument == "INSENSITIVE")
                {
                    sortCaseSensitivity =
                        cmStringSorter::CaseSensitivity::INSENSITIVE;
                } else
                {
                    std::string error = messageHint + "value \"" + argument +
                                        "\" for option \"" + option +
                                        "\" is invalid.";
                    this->SetError(error);
                    return false;
                }
            } else
            {
                std::string error = messageHint +
                                    "missing argument for option \"" + option +
                                    "\".";
                this->SetError(error);
                return false;
            }
        } else if(option == "ORDER")
        {
            if(sortOrder != cmStringSorter::Order::UNINITIALIZED)
            {
                std::string error = messageHint + "option \"" + option +
                                    "\" has been specified multiple times.";
                this->SetError(error);
                return false;
            }
            if(argumentIndex < args.size())
            {
                const std::string argument = args[argumentIndex++];
                if(argument == "ASCENDING")
                {
                    sortOrder = cmStringSorter::Order::ASCENDING;
                } else if(argument == "DESCENDING")
                {
                    sortOrder = cmStringSorter::Order::DESCENDING;
                } else
                {
                    std::string error = messageHint + "value \"" + argument +
                                        "\" for option \"" + option +
                                        "\" is invalid.";
                    this->SetError(error);
                    return false;
                }
            } else
            {
                std::string error = messageHint +
                                    "missing argument for option \"" + option +
                                    "\".";
                this->SetError(error);
                return false;
            }
        } else
        {
            std::string error =
                messageHint + "option \"" + option + "\" is unknown.";
            this->SetError(error);
            return false;
        }
    }
    // set Default Values if Option is not given
    if(sortCompare == cmStringSorter::Compare::UNINITIALIZED)
    {
        sortCompare = cmStringSorter::Compare::STRING;
    }
    if(sortCaseSensitivity == cmStringSorter::CaseSensitivity::UNINITIALIZED)
    {
        sortCaseSensitivity = cmStringSorter::CaseSensitivity::SENSITIVE;
    }
    if(sortOrder == cmStringSorter::Order::UNINITIALIZED)
    {
        sortOrder = cmStringSorter::Order::ASCENDING;
    }

    const std::string& listName = args[1];
    // expand the variable
    std::vector<std::string> varArgsExpanded;
    if(!this->GetList(varArgsExpanded, listName))
    {
        this->SetError("sub-command SORT requires list to be present.");
        return false;
    }

    if((sortCompare == cmStringSorter::Compare::STRING) &&
       (sortCaseSensitivity == cmStringSorter::CaseSensitivity::SENSITIVE) &&
       (sortOrder == cmStringSorter::Order::ASCENDING))
    {
        std::sort(varArgsExpanded.begin(), varArgsExpanded.end());
    } else
    {
        cmStringSorter sorter(sortCompare, sortCaseSensitivity, sortOrder);
        std::sort(varArgsExpanded.begin(), varArgsExpanded.end(), sorter);
    }

    std::string value = cmJoin(varArgsExpanded, ";");
    this->Makefile->AddDefinition(listName, value.c_str());
    return true;
}

bool
cmListCommand::HandleSublistCommand(std::vector<std::string> const& args)
{
    if(args.size() != 5)
    {
        std::ostringstream error;
        error << "sub-command SUBLIST requires four arguments ("
              << args.size() - 1 << " found).";
        this->SetError(error.str());
        return false;
    }

    const std::string& listName     = args[1];
    const std::string& variableName = args[args.size() - 1];

    // expand the variable
    std::vector<std::string> varArgsExpanded;
    if(!this->GetList(varArgsExpanded, listName) || varArgsExpanded.empty())
    {
        this->Makefile->AddDefinition(variableName, "");
        return true;
    }

    const int start  = atoi(args[2].c_str());
    const int length = atoi(args[3].c_str());

    using size_type = decltype(varArgsExpanded)::size_type;

    if(start < 0 || size_type(start) >= varArgsExpanded.size())
    {
        std::ostringstream error;
        error << "begin index: " << start << " is out of range 0 - "
              << varArgsExpanded.size() - 1;
        this->SetError(error.str());
        return false;
    }
    if(length < -1)
    {
        std::ostringstream error;
        error << "length: " << length << " should be -1 or greater";
        this->SetError(error.str());
        return false;
    }

    const size_type end =
        (length == -1 || size_type(start + length) > varArgsExpanded.size())
            ? varArgsExpanded.size()
            : size_type(start + length);
    std::vector<std::string> sublist(varArgsExpanded.begin() + start,
                                     varArgsExpanded.begin() + end);
    this->Makefile->AddDefinition(variableName, cmJoin(sublist, ";").c_str());
    return true;
}

bool
cmListCommand::HandleRemoveAtCommand(std::vector<std::string> const& args)
{
    if(args.size() < 3)
    {
        this->SetError("sub-command REMOVE_AT requires at least "
                       "two arguments.");
        return false;
    }

    const std::string& listName = args[1];
    // expand the variable
    std::vector<std::string> varArgsExpanded;
    if(!this->GetList(varArgsExpanded, listName))
    {
        this->SetError("sub-command REMOVE_AT requires list to be present.");
        return false;
    }
    // FIXME: Add policy to make non-existing lists an error like empty lists.
    if(varArgsExpanded.empty())
    {
        this->SetError("REMOVE_AT given empty list");
        return false;
    }

    size_t              cc;
    std::vector<size_t> removed;
    size_t              nitem = varArgsExpanded.size();
    for(cc = 2; cc < args.size(); ++cc)
    {
        int item = atoi(args[cc].c_str());
        if(item < 0)
        {
            item = static_cast<int>(nitem) + item;
        }
        if(item < 0 || nitem <= static_cast<size_t>(item))
        {
            std::ostringstream str;
            str << "index: " << item << " out of range (-" << nitem << ", "
                << nitem - 1 << ")";
            this->SetError(str.str());
            return false;
        }
        removed.push_back(static_cast<size_t>(item));
    }

    std::sort(removed.begin(), removed.end());
    std::vector<size_t>::const_iterator remEnd =
        std::unique(removed.begin(), removed.end());
    std::vector<size_t>::const_iterator remBegin = removed.begin();

    std::vector<std::string>::const_iterator argsEnd =
        cmRemoveIndices(varArgsExpanded, cmMakeRange(remBegin, remEnd));
    std::vector<std::string>::const_iterator argsBegin =
        varArgsExpanded.begin();
    std::string value = cmJoin(cmMakeRange(argsBegin, argsEnd), ";");

    this->Makefile->AddDefinition(listName, value.c_str());
    return true;
}

bool
cmListCommand::HandleFilterCommand(std::vector<std::string> const& args)
{
    if(args.size() < 2)
    {
        this->SetError("sub-command FILTER requires a list to be specified.");
        return false;
    }

    if(args.size() < 3)
    {
        this->SetError(
            "sub-command FILTER requires an operator to be specified.");
        return false;
    }

    if(args.size() < 4)
    {
        this->SetError("sub-command FILTER requires a mode to be specified.");
        return false;
    }

    const std::string& listName = args[1];
    // expand the variable
    std::vector<std::string> varArgsExpanded;
    if(!this->GetList(varArgsExpanded, listName))
    {
        this->SetError("sub-command FILTER requires list to be present.");
        return false;
    }

    const std::string& op = args[2];
    bool               includeMatches;
    if(op == "INCLUDE")
    {
        includeMatches = true;
    } else if(op == "EXCLUDE")
    {
        includeMatches = false;
    } else
    {
        this->SetError("sub-command FILTER does not recognize operator " + op);
        return false;
    }

    const std::string& mode = args[3];
    if(mode == "REGEX")
    {
        if(args.size() != 5)
        {
            this->SetError("sub-command FILTER, mode REGEX "
                           "requires five arguments.");
            return false;
        }
        return this->FilterRegex(args, includeMatches, listName,
                                 varArgsExpanded);
    }

    this->SetError("sub-command FILTER does not recognize mode " + mode);
    return false;
}

class MatchesRegex
{
public:
    MatchesRegex(cmsys::RegularExpression& in_regex, bool in_includeMatches)
    : regex(in_regex)
    , includeMatches(in_includeMatches)
    {}

    bool operator()(const std::string& target)
    {
        return regex.find(target) ^ includeMatches;
    }

private:
    cmsys::RegularExpression& regex;
    const bool                includeMatches;
};

bool
cmListCommand::FilterRegex(std::vector<std::string> const& args,
                           bool includeMatches, std::string const& listName,
                           std::vector<std::string>& varArgsExpanded)
{
    const std::string&       pattern = args[4];
    cmsys::RegularExpression regex(pattern);
    if(!regex.is_valid())
    {
        std::string error = "sub-command FILTER, mode REGEX ";
        error += "failed to compile regex \"";
        error += pattern;
        error += "\".";
        this->SetError(error);
        return false;
    }

    std::vector<std::string>::iterator argsBegin = varArgsExpanded.begin();
    std::vector<std::string>::iterator argsEnd   = varArgsExpanded.end();
    std::vector<std::string>::iterator newArgsEnd =
        std::remove_if(argsBegin, argsEnd, MatchesRegex(regex, includeMatches));

    std::string value = cmJoin(cmMakeRange(argsBegin, newArgsEnd), ";");
    this->Makefile->AddDefinition(listName, value.c_str());
    return true;
}
