/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
   file Copyright.txt or https://cmake.org/licensing#kwsys for details.  */
#include "kwsysPrivate.h"
#include KWSYS_HEADER(CommandLineArguments.hxx)

#include KWSYS_HEADER(Configure.hxx)
#include KWSYS_HEADER(String.hxx)

// Work-around CMake dependency scanning limitation.  This must
// duplicate the above list of headers.
#if 0
#  include "CommandLineArguments.hxx.in"
#  include "Configure.hxx.in"
#  include "String.hxx.in"
#endif

#include <iostream>
#include <map>
#include <set>
#include <sstream>
#include <vector>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifdef _MSC_VER
#  pragma warning(disable : 4786)
#endif

#if defined(__sgi) && !defined(__GNUC__)
#  pragma set woff 1375 /* base class destructor not virtual */
#endif

#if 0
#  define CommandLineArguments_DEBUG(x)                                       \
    std::cout << __LINE__ << " CLA: " << x << std::endl
#else
#  define CommandLineArguments_DEBUG(x)
#endif

namespace KWSYS_NAMESPACE {

struct CommandLineArgumentsCallbackStructure
{
  const char* Argument;
  int ArgumentType;
  CommandLineArguments::CallbackType Callback;
  void* CallData;
  void* Variable;
  int VariableType;
  const char* Help;
};

class CommandLineArgumentsVectorOfStrings : public std::vector<kwsys::String>
{
};
class CommandLineArgumentsSetOfStrings : public std::set<kwsys::String>
{
};
class CommandLineArgumentsMapOfStrucs
  : public std::map<kwsys::String, CommandLineArgumentsCallbackStructure>
{
};

class CommandLineArgumentsInternal
{
public:
  CommandLineArgumentsInternal() = default;

  using VectorOfStrings = CommandLineArgumentsVectorOfStrings;
  using CallbacksMap = CommandLineArgumentsMapOfStrucs;
  using String = kwsys::String;
  using SetOfStrings = CommandLineArgumentsSetOfStrings;

  VectorOfStrings Argv;
  String Argv0;
  CallbacksMap Callbacks;

  CommandLineArguments::ErrorCallbackType UnknownArgumentCallback{ nullptr };
  void* ClientData{ nullptr };

  VectorOfStrings::size_type LastArgument{ 0 };

  VectorOfStrings UnusedArguments;
};

CommandLineArguments::CommandLineArguments()
{
  this->Internals = new CommandLineArguments::Internal;
  this->Help = "";
  this->LineLength = 80;
  this->StoreUnusedArgumentsFlag = false;
}

CommandLineArguments::~CommandLineArguments()
{
  delete this->Internals;
}

void CommandLineArguments::Initialize(int argc, const char* const argv[])
{
  int cc;

  this->Initialize();
  this->Internals->Argv0 = argv[0];
  for (cc = 1; cc < argc; cc++) {
    this->ProcessArgument(argv[cc]);
  }
}

void CommandLineArguments::Initialize(int argc, char* argv[])
{
  this->Initialize(argc, static_cast<const char* const*>(argv));
}

void CommandLineArguments::Initialize()
{
  this->Internals->Argv.clear();
  this->Internals->LastArgument = 0;
}

void CommandLineArguments::ProcessArgument(const char* arg)
{
  this->Internals->Argv.push_back(arg);
}

bool CommandLineArguments::GetMatchedArguments(
  std::vector<std::string>* matches, const std::string& arg)
{
  matches->clear();
  CommandLineArguments::Internal::CallbacksMap::iterator it;

  // Does the argument match to any we know about?
  for (it = this->Internals->Callbacks.begin();
       it != this->Internals->Callbacks.end(); it++) {
    const CommandLineArguments::Internal::String& parg = it->first;
    CommandLineArgumentsCallbackStructure* cs = &it->second;
    if (cs->ArgumentType == CommandLineArguments::NO_ARGUMENT ||
        cs->ArgumentType == CommandLineArguments::SPACE_ARGUMENT) {
      if (arg == parg) {
        matches->push_back(parg);
      }
    } else if (arg.find(parg) == 0) {
      matches->push_back(parg);
    }
  }
  return !matches->empty();
}

int CommandLineArguments::Parse()
{
  std::vector<std::string>::size_type cc;
  std::vector<std::string> matches;
  if (this->StoreUnusedArgumentsFlag) {
    this->Internals->UnusedArguments.clear();
  }
  for (cc = 0; cc < this->Internals->Argv.size(); cc++) {
    const std::string& arg = this->Internals->Argv[cc];
    CommandLineArguments_DEBUG("Process argument: " << arg);
    this->Internals->LastArgument = cc;
    if (this->GetMatchedArguments(&matches, arg)) {
      // Ok, we found one or more arguments that match what user specified.
      // Let's find the longest one.
      CommandLineArguments::Internal::VectorOfStrings::size_type kk;
      CommandLineArguments::Internal::VectorOfStrings::size_type maxidx = 0;
      CommandLineArguments::Internal::String::size_type maxlen = 0;
      for (kk = 0; kk < matches.size(); kk++) {
        if (matches[kk].size() > maxlen) {
          maxlen = matches[kk].size();
          maxidx = kk;
        }
      }
      // So, the longest one is probably the right one. Now see if it has any
      // additional value
      CommandLineArgumentsCallbackStructure* cs =
        &this->Internals->Callbacks[matches[maxidx]];
      const std::string& sarg = matches[maxidx];
      if (cs->Argument != sarg) {
        abort();
      }
      switch (cs->ArgumentType) {
        case NO_ARGUMENT:
          // No value
          if (!this->PopulateVariable(cs, nullptr)) {
            return 0;
          }
          break;
        case SPACE_ARGUMENT:
          if (cc == this->Internals->Argv.size() - 1) {
            this->Internals->LastArgument--;
            return 0;
          }
          CommandLineArguments_DEBUG("This is a space argument: "
                                     << arg << " value: "
                                     << this->Internals->Argv[cc + 1]);
          // Value is the next argument
          if (!this->PopulateVariable(cs,
                                      this->Internals->Argv[cc + 1].c_str())) {
            return 0;
          }
          cc++;
          break;
        case EQUAL_ARGUMENT:
          if (arg.size() == sarg.size() || arg.at(sarg.size()) != '=') {
            this->Internals->LastArgument--;
            return 0;
          }
          // Value is everythng followed the '=' sign
          if (!this->PopulateVariable(cs, arg.c_str() + sarg.size() + 1)) {
            return 0;
          }
          break;
        case CONCAT_ARGUMENT:
          // Value is whatever follows the argument
          if (!this->PopulateVariable(cs, arg.c_str() + sarg.size())) {
            return 0;
          }
          break;
        case MULTI_ARGUMENT:
          // Suck in all the rest of the arguments
          CommandLineArguments_DEBUG("This is a multi argument: " << arg);
          for (cc++; cc < this->Internals->Argv.size(); ++cc) {
            const std::string& marg = this->Internals->Argv[cc];
            CommandLineArguments_DEBUG(
              " check multi argument value: " << marg);
            if (this->GetMatchedArguments(&matches, marg)) {
              CommandLineArguments_DEBUG("End of multi argument "
                                         << arg << " with value: " << marg);
              break;
            }
            CommandLineArguments_DEBUG(
              " populate multi argument value: " << marg);
            if (!this->PopulateVariable(cs, marg.c_str())) {
              return 0;
            }
          }
          if (cc != this->Internals->Argv.size()) {
            CommandLineArguments_DEBUG("Again End of multi argument " << arg);
            cc--;
            continue;
          }
          break;
        default:
          std::cerr << "Got unknown argument type: \"" << cs->ArgumentType
                    << "\"" << std::endl;
          this->Internals->LastArgument--;
          return 0;
      }
    } else {
      // Handle unknown arguments
      if (this->Internals->UnknownArgumentCallback) {
        if (!this->Internals->UnknownArgumentCallback(
              arg.c_str(), this->Internals->ClientData)) {
          this->Internals->LastArgument--;
          return 0;
        }
        return 1;
      } else if (this->StoreUnusedArgumentsFlag) {
        CommandLineArguments_DEBUG("Store unused argument " << arg);
        this->Internals->UnusedArguments.push_back(arg);
      } else {
        std::cerr << "Got unknown argument: \"" << arg << "\"" << std::endl;
        this->Internals->LastArgument--;
        return 0;
      }
    }
  }
  return 1;
}

void CommandLineArguments::GetRemainingArguments(int* argc, char*** argv)
{
  CommandLineArguments::Internal::VectorOfStrings::size_type size =
    this->Internals->Argv.size() - this->Internals->LastArgument + 1;
  CommandLineArguments::Internal::VectorOfStrings::size_type cc;

  // Copy Argv0 as the first argument
  char** args = new char*[size];
  args[0] = new char[this->Internals->Argv0.size() + 1];
  strcpy(args[0], this->Internals->Argv0.c_str());
  int cnt = 1;

  // Copy everything after the LastArgument, since that was not parsed.
  for (cc = this->Internals->LastArgument + 1;
       cc < this->Internals->Argv.size(); cc++) {
    args[cnt] = new char[this->Internals->Argv[cc].size() + 1];
    strcpy(args[cnt], this->Internals->Argv[cc].c_str());
    cnt++;
  }
  *argc = cnt;
  *argv = args;
}

void CommandLineArguments::GetUnusedArguments(int* argc, char*** argv)
{
  CommandLineArguments::Internal::VectorOfStrings::size_type size =
    this->Internals->UnusedArguments.size() + 1;
  CommandLineArguments::Internal::VectorOfStrings::size_type cc;

  // Copy Argv0 as the first argument
  char** args = new char*[size];
  args[0] = new char[this->Internals->Argv0.size() + 1];
  strcpy(args[0], this->Internals->Argv0.c_str());
  int cnt = 1;

  // Copy everything after the LastArgument, since that was not parsed.
  for (cc = 0; cc < this->Internals->UnusedArguments.size(); cc++) {
    kwsys::String& str = this->Internals->UnusedArguments[cc];
    args[cnt] = new char[str.size() + 1];
    strcpy(args[cnt], str.c_str());
    cnt++;
  }
  *argc = cnt;
  *argv = args;
}

void CommandLineArguments::DeleteRemainingArguments(int argc, char*** argv)
{
  int cc;
  for (cc = 0; cc < argc; ++cc) {
    delete[](*argv)[cc];
  }
  delete[] * argv;
}

void CommandLineArguments::AddCallback(const char* argument,
                                       ArgumentTypeEnum type,
                                       CallbackType callback, void* call_data,
                                       const char* help)
{
  CommandLineArgumentsCallbackStructure s;
  s.Argument = argument;
  s.ArgumentType = type;
  s.Callback = callback;
  s.CallData = call_data;
  s.VariableType = CommandLineArguments::NO_VARIABLE_TYPE;
  s.Variable = nullptr;
  s.Help = help;

  this->Internals->Callbacks[argument] = s;
  this->GenerateHelp();
}

void CommandLineArguments::AddArgument(const char* argument,
                                       ArgumentTypeEnum type,
                                       VariableTypeEnum vtype, void* variable,
                                       const char* help)
{
  CommandLineArgumentsCallbackStructure s;
  s.Argument = argument;
  s.ArgumentType = type;
  s.Callback = nullptr;
  s.CallData = nullptr;
  s.VariableType = vtype;
  s.Variable = variable;
  s.Help = help;

  this->Internals->Callbacks[argument] = s;
  this->GenerateHelp();
}

#define CommandLineArgumentsAddArgumentMacro(type, ctype)                     \
  void CommandLineArguments::AddArgument(const char* argument,                \
                                         ArgumentTypeEnum type,               \
                                         ctype* variable, const char* help)   \
  {                                                                           \
    this->AddArgument(argument, type, CommandLineArguments::type##_TYPE,      \
                      variable, help);                                        \
  }

/* clang-format off */
CommandLineArgumentsAddArgumentMacro(BOOL, bool)
CommandLineArgumentsAddArgumentMacro(INT, int)
CommandLineArgumentsAddArgumentMacro(DOUBLE, double)
CommandLineArgumentsAddArgumentMacro(STRING, char*)
CommandLineArgumentsAddArgumentMacro(STL_STRING, std::string)

CommandLineArgumentsAddArgumentMacro(VECTOR_BOOL, std::vector<bool>)
CommandLineArgumentsAddArgumentMacro(VECTOR_INT, std::vector<int>)
CommandLineArgumentsAddArgumentMacro(VECTOR_DOUBLE, std::vector<double>)
CommandLineArgumentsAddArgumentMacro(VECTOR_STRING, std::vector<char*>)
CommandLineArgumentsAddArgumentMacro(VECTOR_STL_STRING,
                                     std::vector<std::string>)
#ifdef HELP_CLANG_FORMAT
;
#endif
/* clang-format on */

#define CommandLineArgumentsAddBooleanArgumentMacro(type, ctype)              \
  void CommandLineArguments::AddBooleanArgument(                              \
    const char* argument, ctype* variable, const char* help)                  \
  {                                                                           \
    this->AddArgument(argument, CommandLineArguments::NO_ARGUMENT,            \
                      CommandLineArguments::type##_TYPE, variable, help);     \
  }

/* clang-format off */
CommandLineArgumentsAddBooleanArgumentMacro(BOOL, bool)
CommandLineArgumentsAddBooleanArgumentMacro(INT, int)
CommandLineArgumentsAddBooleanArgumentMacro(DOUBLE, double)
CommandLineArgumentsAddBooleanArgumentMacro(STRING, char*)
CommandLineArgumentsAddBooleanArgumentMacro(STL_STRING, std::string)
#ifdef HELP_CLANG_FORMAT
;
#endif
/* clang-format on */

void CommandLineArguments::SetClientData(void* client_data)
{
  this->Internals->ClientData = client_data;
}

void CommandLineArguments::SetUnknownArgumentCallback(
  CommandLineArguments::ErrorCallbackType callback)
{
  this->Internals->UnknownArgumentCallback = callback;
}

const char* CommandLineArguments::GetHelp(const char* arg)
{
  auto it = this->Internals->Callbacks.find(arg);
  if (it == this->Internals->Callbacks.end()) {
    return nullptr;
  }

  // Since several arguments may point to the same argument, find the one this
  // one point to if this one is pointing to another argument.
  CommandLineArgumentsCallbackStructure* cs = &(it->second);
  for (;;) {
    auto hit = this->Internals->Callbacks.find(cs->Help);
    if (hit == this->Internals->Callbacks.end()) {
      break;
    }
    cs = &(hit->second);
  }
  return cs->Help;
}

void CommandLineArguments::SetLineLength(unsigned int ll)
{
  if (ll < 9 || ll > 1000) {
    return;
  }
  this->LineLength = ll;
  this->GenerateHelp();
}

const char* CommandLineArguments::GetArgv0()
{
  return this->Internals->Argv0.c_str();
}

unsigned int CommandLineArguments::GetLastArgument()
{
  return static_cast<unsigned int>(this->Internals->LastArgument + 1);
}

void CommandLineArguments::GenerateHelp()
{
  std::ostringstream str;

  // Collapse all arguments into the map of vectors of all arguments that do
  // the same thing.
  CommandLineArguments::Internal::CallbacksMap::iterator it;
  using MapArgs = std::map<CommandLineArguments::Internal::String,
                           CommandLineArguments::Internal::SetOfStrings>;
  MapArgs mp;
  MapArgs::iterator mpit, smpit;
  for (it = this->Internals->Callbacks.begin();
       it != this->Internals->Callbacks.end(); it++) {
    CommandLineArgumentsCallbackStructure* cs = &(it->second);
    mpit = mp.find(cs->Help);
    if (mpit != mp.end()) {
      mpit->second.insert(it->first);
      mp[it->first].insert(it->first);
    } else {
      mp[it->first].insert(it->first);
    }
  }
  for (it = this->Internals->Callbacks.begin();
       it != this->Internals->Callbacks.end(); it++) {
    CommandLineArgumentsCallbackStructure* cs = &(it->second);
    mpit = mp.find(cs->Help);
    if (mpit != mp.end()) {
      mpit->second.insert(it->first);
      smpit = mp.find(it->first);
      CommandLineArguments::Internal::SetOfStrings::iterator sit;
      for (sit = smpit->second.begin(); sit != smpit->second.end(); sit++) {
        mpit->second.insert(*sit);
      }
      mp.erase(smpit);
    } else {
      mp[it->first].insert(it->first);
    }
  }

  // Find the length of the longest string
  CommandLineArguments::Internal::String::size_type maxlen = 0;
  for (mpit = mp.begin(); mpit != mp.end(); mpit++) {
    CommandLineArguments::Internal::SetOfStrings::iterator sit;
    for (sit = mpit->second.begin(); sit != mpit->second.end(); sit++) {
      CommandLineArguments::Internal::String::size_type clen = sit->size();
      switch (this->Internals->Callbacks[*sit].ArgumentType) {
        case CommandLineArguments::NO_ARGUMENT:
          clen += 0;
          break;
        case CommandLineArguments::CONCAT_ARGUMENT:
          clen += 3;
          break;
        case CommandLineArguments::SPACE_ARGUMENT:
          clen += 4;
          break;
        case CommandLineArguments::EQUAL_ARGUMENT:
          clen += 4;
          break;
      }
      if (clen > maxlen) {
        maxlen = clen;
      }
    }
  }

  CommandLineArguments::Internal::String::size_type maxstrlen = maxlen;
  maxlen += 4; // For the space before and after the option

  // Print help for each option
  for (mpit = mp.begin(); mpit != mp.end(); mpit++) {
    CommandLineArguments::Internal::SetOfStrings::iterator sit;
    for (sit = mpit->second.begin(); sit != mpit->second.end(); sit++) {
      str << std::endl;
      std::string argument = *sit;
      switch (this->Internals->Callbacks[*sit].ArgumentType) {
        case CommandLineArguments::NO_ARGUMENT:
          break;
        case CommandLineArguments::CONCAT_ARGUMENT:
          argument += "opt";
          break;
        case CommandLineArguments::SPACE_ARGUMENT:
          argument += " opt";
          break;
        case CommandLineArguments::EQUAL_ARGUMENT:
          argument += "=opt";
          break;
        case CommandLineArguments::MULTI_ARGUMENT:
          argument += " opt opt ...";
          break;
      }
      str << "  " << argument.substr(0, maxstrlen) << "  ";
    }
    const char* ptr = this->Internals->Callbacks[mpit->first].Help;
    size_t len = strlen(ptr);
    int cnt = 0;
    while (len > 0) {
      // If argument with help is longer than line length, split it on previous
      // space (or tab) and continue on the next line
      CommandLineArguments::Internal::String::size_type cc;
      for (cc = 0; ptr[cc]; cc++) {
        if (*ptr == ' ' || *ptr == '\t') {
          ptr++;
          len--;
        }
      }
      if (cnt > 0) {
        for (cc = 0; cc < maxlen; cc++) {
          str << " ";
        }
      }
      CommandLineArguments::Internal::String::size_type skip = len;
      if (skip > this->LineLength - maxlen) {
        skip = this->LineLength - maxlen;
        for (cc = skip - 1; cc > 0; cc--) {
          if (ptr[cc] == ' ' || ptr[cc] == '\t') {
            break;
          }
        }
        if (cc != 0) {
          skip = cc;
        }
      }
      str.write(ptr, static_cast<std::streamsize>(skip));
      str << std::endl;
      ptr += skip;
      len -= skip;
      cnt++;
    }
  }
  /*
  // This can help debugging help string
  str << endl;
  unsigned int cc;
  for ( cc = 0; cc < this->LineLength; cc ++ )
    {
    str << cc % 10;
    }
  str << endl;
  */
  this->Help = str.str();
}

void CommandLineArguments::PopulateVariable(bool* variable,
                                            const std::string& value)
{
  if (value == "1" || value == "ON" || value == "on" || value == "On" ||
      value == "TRUE" || value == "true" || value == "True" ||
      value == "yes" || value == "Yes" || value == "YES") {
    *variable = true;
  } else {
    *variable = false;
  }
}

void CommandLineArguments::PopulateVariable(int* variable,
                                            const std::string& value)
{
  char* res = nullptr;
  *variable = static_cast<int>(strtol(value.c_str(), &res, 10));
  // if ( res && *res )
  //  {
  //  Can handle non-int
  //  }
}

void CommandLineArguments::PopulateVariable(double* variable,
                                            const std::string& value)
{
  char* res = nullptr;
  *variable = strtod(value.c_str(), &res);
  // if ( res && *res )
  //  {
  //  Can handle non-double
  //  }
}

void CommandLineArguments::PopulateVariable(char** variable,
                                            const std::string& value)
{
  delete[] * variable;
  *variable = new char[value.size() + 1];
  strcpy(*variable, value.c_str());
}

void CommandLineArguments::PopulateVariable(std::string* variable,
                                            const std::string& value)
{
  *variable = value;
}

void CommandLineArguments::PopulateVariable(std::vector<bool>* variable,
                                            const std::string& value)
{
  bool val = false;
  if (value == "1" || value == "ON" || value == "on" || value == "On" ||
      value == "TRUE" || value == "true" || value == "True" ||
      value == "yes" || value == "Yes" || value == "YES") {
    val = true;
  }
  variable->push_back(val);
}

void CommandLineArguments::PopulateVariable(std::vector<int>* variable,
                                            const std::string& value)
{
  char* res = nullptr;
  variable->push_back(static_cast<int>(strtol(value.c_str(), &res, 10)));
  // if ( res && *res )
  //  {
  //  Can handle non-int
  //  }
}

void CommandLineArguments::PopulateVariable(std::vector<double>* variable,
                                            const std::string& value)
{
  char* res = nullptr;
  variable->push_back(strtod(value.c_str(), &res));
  // if ( res && *res )
  //  {
  //  Can handle non-int
  //  }
}

void CommandLineArguments::PopulateVariable(std::vector<char*>* variable,
                                            const std::string& value)
{
  char* var = new char[value.size() + 1];
  strcpy(var, value.c_str());
  variable->push_back(var);
}

void CommandLineArguments::PopulateVariable(std::vector<std::string>* variable,
                                            const std::string& value)
{
  variable->push_back(value);
}

bool CommandLineArguments::PopulateVariable(
  CommandLineArgumentsCallbackStructure* cs, const char* value)
{
  // Call the callback
  if (cs->Callback) {
    if (!cs->Callback(cs->Argument, value, cs->CallData)) {
      this->Internals->LastArgument--;
      return false;
    }
  }
  CommandLineArguments_DEBUG("Set argument: " << cs->Argument << " to "
                                              << value);
  if (cs->Variable) {
    std::string var = "1";
    if (value) {
      var = value;
    }
    switch (cs->VariableType) {
      case CommandLineArguments::INT_TYPE:
        this->PopulateVariable(static_cast<int*>(cs->Variable), var);
        break;
      case CommandLineArguments::DOUBLE_TYPE:
        this->PopulateVariable(static_cast<double*>(cs->Variable), var);
        break;
      case CommandLineArguments::STRING_TYPE:
        this->PopulateVariable(static_cast<char**>(cs->Variable), var);
        break;
      case CommandLineArguments::STL_STRING_TYPE:
        this->PopulateVariable(static_cast<std::string*>(cs->Variable), var);
        break;
      case CommandLineArguments::BOOL_TYPE:
        this->PopulateVariable(static_cast<bool*>(cs->Variable), var);
        break;
      case CommandLineArguments::VECTOR_BOOL_TYPE:
        this->PopulateVariable(static_cast<std::vector<bool>*>(cs->Variable),
                               var);
        break;
      case CommandLineArguments::VECTOR_INT_TYPE:
        this->PopulateVariable(static_cast<std::vector<int>*>(cs->Variable),
                               var);
        break;
      case CommandLineArguments::VECTOR_DOUBLE_TYPE:
        this->PopulateVariable(static_cast<std::vector<double>*>(cs->Variable),
                               var);
        break;
      case CommandLineArguments::VECTOR_STRING_TYPE:
        this->PopulateVariable(static_cast<std::vector<char*>*>(cs->Variable),
                               var);
        break;
      case CommandLineArguments::VECTOR_STL_STRING_TYPE:
        this->PopulateVariable(
          static_cast<std::vector<std::string>*>(cs->Variable), var);
        break;
      default:
        std::cerr << "Got unknown variable type: \"" << cs->VariableType
                  << "\"" << std::endl;
        this->Internals->LastArgument--;
        return false;
    }
  }
  return true;
}

} // namespace KWSYS_NAMESPACE
