/*
  Copyright (C) Skeletex Research, s.r.o. - All Rights Reserved
  Unauthorized copying of this file, via any medium is strictly prohibited
  Proprietary and confidential
*/
#ifndef UTILS_ARGUMENT_MAP_H
#define UTILS_ARGUMENT_MAP_H
#include <Utils/ExtSTD.h>

namespace utils
{
  /*!
    \brief Class to parse and conveniently hold console flags and parameters.

    Console arguments are parsed to the flags and flag parameters. 
    
    Given a following console input
    `MyApp.exe -v 123 -X --in-files file1.txt file2.txt`
    this class will interpret arguments as following:
    
    * "v", "X", and "in-files" are flags,
    * "123" is a parameter of flag "v",
    * "file1.txt" and "file2.txt" are first and second parameters of flag "in-files" respectively.
  */
  class ConsoleArgs
  {
  public:
    //! The maximal number of dashes in the argument prexif, to consider it a flag. 
    static size_t MAX_FLAG_PREFIX_DASHES;
    /*! 
      \brief Construct an instance of this class from console arguments. The first argument is the executable name and always ignored.
      \throws std::invalid_argument when parameter arguments are specified before any flag arguments.
    */
    explicit ConsoleArgs(int argc, const char* argv[])
    {
      std::string last_flag_name;
      for (int i = 1; i < argc; ++i)
      {
        const std::string arg = argv[i];
        const std::string arg_trimmed_dashes = std_ext::TrimBegin(arg, std_ext::WHITESPACES + "-");
        const auto dashes_count = arg.length() - arg_trimmed_dashes.length();
        const bool is_flag = dashes_count > 0 && dashes_count <= MAX_FLAG_PREFIX_DASHES;
        if (is_flag)
        {
          last_flag_name = std_ext::TrimBegin(arg, std_ext::WHITESPACES + "-");
          map[last_flag_name] = {};
        }
        else
        {
          if (last_flag_name.empty())
          {
            throw std::invalid_argument("Parameter argument predceeds any flag arguments.");
          }
          map.at(last_flag_name).emplace_back(arg);
        }
      }
    }

    //! Returns the number of flag arguments.
    size_t CountFlags() const
    {
      return map.size();
    }

    //! Check if there exists a flag with the specified name. The prefix dashes are ignored.
    bool HasFlag(std::string flag_name) const
    {
      return std_ext::Contains(std_ext::TrimBegin(flag_name, "-"), map);
    }

    //! Check if a flag has a parameter with a specified name.
    bool HasParam(const std::string& flag_name, const std::string& param_name) const
    {
      return std_ext::Contains(param_name, GetParams(flag_name));
    }

    /*!
      \brief Returns parameters of a specified flag.
      \throws std::invalid_argument when there are no parameters defined for the flag.
    */
    const std::vector<std::string>& GetParams(const std::string& flag_name) const
    {
      if (!HasFlag(flag_name))
      {
        throw std::invalid_argument("Flag argument \"" + flag_name + "\" has no parameters.");
      }
      return map.at(flag_name);
    }

    /*! 
      \brief Executes a specified function for each flag in the arguments list.
      \param loop_body  The function executed for each flag. 
                        The first argument is flag name; the second is a list of flag parameters.

      The following example will print all flags:
      \code{.cpp}
        args.ForEachFlag([](const std::string &flag, const std::vector<std::string> &params){
          std::cout << flag << std::endl;
        });
      \endcode
    */
    void ForEachFlag(const std::function<void(const std::string &, const std::vector<std::string> &)>& LoopBody) const
    {
      for (const auto& element : map)
      {
        LoopBody(element.first, element.second);
      }
    }

  private:
    std::map<std::string, std::vector<std::string>> map;
  };


  size_t ConsoleArgs::MAX_FLAG_PREFIX_DASHES = 2;
  
}

#endif // UTILS_ARGUMENT_MAP_H