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

#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"

template <typename FunctionSignature>
struct cmCommandLineArgument
{
  enum class Values
  {
    Zero,
    One,
    Two,
    ZeroOrOne,
    OneOrMore
  };

  std::string InvalidSyntaxMessage;
  std::string InvalidValueMessage;
  std::string Name;
  Values Type;
  std::function<FunctionSignature> StoreCall;

  template <typename FunctionType>
  cmCommandLineArgument(std::string n, Values t, FunctionType&& func)
    : InvalidSyntaxMessage(cmStrCat("Invalid syntax used with ", n))
    , InvalidValueMessage(cmStrCat("Invalid value used with ", n))
    , Name(std::move(n))
    , Type(t)
    , StoreCall(std::forward<FunctionType>(func))
  {
  }

  template <typename FunctionType>
  cmCommandLineArgument(std::string n, std::string failedMsg, Values t,
                        FunctionType&& func)
    : InvalidSyntaxMessage(cmStrCat("Invalid syntax used with ", n))
    , InvalidValueMessage(std::move(failedMsg))
    , Name(std::move(n))
    , Type(t)
    , StoreCall(std::forward<FunctionType>(func))
  {
  }

  bool matches(std::string const& input) const
  {
    return (this->Type == Values::Zero) ? (input == this->Name)
                                        : cmHasPrefix(input, this->Name);
  }

  template <typename T, typename... CallState>
  bool parse(std::string const& input, T& index,
             std::vector<std::string> const& allArgs,
             CallState&&... state) const
  {
    enum class ParseMode
    {
      Valid,
      Invalid,
      SyntaxError,
      ValueError
    };
    ParseMode parseState = ParseMode::Valid;

    if (this->Type == Values::Zero) {
      if (input.size() == this->Name.size()) {
        parseState =
          this->StoreCall(std::string{}, std::forward<CallState>(state)...)
          ? ParseMode::Valid
          : ParseMode::Invalid;
      } else {
        parseState = ParseMode::SyntaxError;
      }

    } else if (this->Type == Values::One || this->Type == Values::ZeroOrOne) {
      if (input.size() == this->Name.size()) {
        auto nextValueIndex = index + 1;
        if (nextValueIndex >= allArgs.size() ||
            allArgs[nextValueIndex][0] == '-') {
          if (this->Type == Values::ZeroOrOne) {
            parseState =
              this->StoreCall(std::string{}, std::forward<CallState>(state)...)
              ? ParseMode::Valid
              : ParseMode::Invalid;
          } else {
            parseState = ParseMode::ValueError;
          }
        } else {
          parseState = this->StoreCall(allArgs[nextValueIndex],
                                       std::forward<CallState>(state)...)
            ? ParseMode::Valid
            : ParseMode::Invalid;
          index = nextValueIndex;
        }
      } else {
        // parse the string to get the value
        auto possible_value = cm::string_view(input).substr(this->Name.size());
        if (possible_value.empty()) {
          parseState = ParseMode::SyntaxError;
          parseState = ParseMode::ValueError;
        } else if (possible_value[0] == '=') {
          possible_value.remove_prefix(1);
          if (possible_value.empty()) {
            parseState = ParseMode::ValueError;
          } else {
            parseState = this->StoreCall(std::string(possible_value),
                                         std::forward<CallState>(state)...)
              ? ParseMode::Valid
              : ParseMode::Invalid;
          }
        }
        if (parseState == ParseMode::Valid) {
          if (possible_value[0] == ' ') {
            possible_value.remove_prefix(1);
          }

          parseState = this->StoreCall(std::string(possible_value),
                                       std::forward<CallState>(state)...)
            ? ParseMode::Valid
            : ParseMode::Invalid;
        }
      }
    } else if (this->Type == Values::Two) {
      if (input.size() == this->Name.size()) {
        if (index + 2 >= allArgs.size() || allArgs[index + 1][0] == '-' ||
            allArgs[index + 2][0] == '-') {
          parseState = ParseMode::ValueError;
        } else {
          index += 2;
          parseState =
            this->StoreCall(cmStrCat(allArgs[index - 1], ";", allArgs[index]),
                            std::forward<CallState>(state)...)
            ? ParseMode::Valid
            : ParseMode::Invalid;
        }
      }
    } else if (this->Type == Values::OneOrMore) {
      if (input.size() == this->Name.size()) {
        auto nextValueIndex = index + 1;
        if (nextValueIndex >= allArgs.size() ||
            allArgs[nextValueIndex][0] == '-') {
          parseState = ParseMode::ValueError;
        } else {
          std::string buffer = allArgs[nextValueIndex++];
          while (nextValueIndex < allArgs.size() &&
                 allArgs[nextValueIndex][0] != '-') {
            buffer = cmStrCat(buffer, ";", allArgs[nextValueIndex++]);
          }
          parseState =
            this->StoreCall(buffer, std::forward<CallState>(state)...)
            ? ParseMode::Valid
            : ParseMode::Invalid;
          index = (nextValueIndex - 1);
        }
      }
    }

    if (parseState == ParseMode::SyntaxError) {
      cmSystemTools::Error(this->InvalidSyntaxMessage);
    } else if (parseState == ParseMode::ValueError) {
      cmSystemTools::Error(this->InvalidValueMessage);
    }
    return (parseState == ParseMode::Valid);
  }
};
