// File: crn_command_line_params.cpp
// See Copyright Notice and license at the end of inc/crnlib.h
#include "crn_core.h"
#include "crn_command_line_params.h"
#include "crn_console.h"
#include "crn_cfile_stream.h"

#ifdef WIN32
   #define CRNLIB_CMD_LINE_ALLOW_SLASH_PARAMS 1
#endif

#if CRNLIB_USE_WIN32_API
#include "crn_winhdr.h"
#endif
namespace crnlib
{
   void get_command_line_as_single_string(dynamic_string& cmd_line, int argc, char *argv[])
   {
      argc, argv;
#if CRNLIB_USE_WIN32_API
      cmd_line.set(GetCommandLineA());
#else
      cmd_line.clear();
      for (int i = 0; i < argc; i++)
      {
         dynamic_string tmp(argv[i]);
         if ((tmp.front() != '"') && (tmp.front() != '-') && (tmp.front() != '@'))
            tmp = "\"" + tmp + "\"";
         if (cmd_line.get_len())
            cmd_line += " ";
         cmd_line += tmp;
      }
#endif
   }

   command_line_params::command_line_params()
   {
   }

   void command_line_params::clear()
   {
      m_params.clear();

      m_param_map.clear();
   }

   bool command_line_params::split_params(const char* p, dynamic_string_array& params)
   {
      bool within_param = false;
      bool within_quote = false;

      uint ofs = 0;
      dynamic_string str;

      while (p[ofs])
      {
         const char c = p[ofs];

         if (within_param)
         {
            if (within_quote)
            {
               if (c == '"')
                  within_quote = false;

               str.append_char(c);
            }
            else if ((c == ' ') || (c == '\t'))
            {
               if (!str.is_empty())
               {
                  params.push_back(str);
                  str.clear();
               }
               within_param = false;
            }
            else
            {
               if (c == '"')
                  within_quote = true;

               str.append_char(c);
            }
         }
         else if ((c != ' ') && (c != '\t'))
         {
            within_param = true;

            if (c == '"')
               within_quote = true;

            str.append_char(c);
         }

         ofs++;
      }

      if (within_quote)
      {
         console::error("Unmatched quote in command line \"%s\"", p);
         return false;
      }

      if (!str.is_empty())
         params.push_back(str);

      return true;
   }

   bool command_line_params::load_string_file(const char* pFilename, dynamic_string_array& strings)
   {
      cfile_stream in_stream;
      if (!in_stream.open(pFilename, cDataStreamReadable | cDataStreamSeekable))
      {
         console::error("Unable to open file \"%s\" for reading!", pFilename);
         return false;
      }

      dynamic_string ansi_str;

      for ( ; ; )
      {
         if (!in_stream.read_line(ansi_str))
            break;

         ansi_str.trim();
         if (ansi_str.is_empty())
            continue;

         strings.push_back(dynamic_string(ansi_str.get_ptr()));
      }

      return true;
   }

   bool command_line_params::parse(const dynamic_string_array& params, uint n, const param_desc* pParam_desc)
   {
      CRNLIB_ASSERT(n && pParam_desc);

      m_params = params;

      uint arg_index = 0;
      while (arg_index < params.size())
      {
         const uint cur_arg_index = arg_index;
         const dynamic_string& src_param = params[arg_index++];

         if (src_param.is_empty())
            continue;
#if CRNLIB_CMD_LINE_ALLOW_SLASH_PARAMS
         if ((src_param[0] == '/') || (src_param[0] == '-'))
#else
         if (src_param[0] == '-')
#endif
         {
            if (src_param.get_len() < 2)
            {
               console::error("Invalid command line parameter: \"%s\"", src_param.get_ptr());
               return false;
            }

            dynamic_string key_str(src_param);

            key_str.right(1);

            int modifier = 0;
            char c = key_str[key_str.get_len() - 1];
            if (c == '+')
               modifier = 1;
            else if (c == '-')
               modifier = -1;

            if (modifier)
               key_str.left(key_str.get_len() - 1);

            uint param_index;
            for (param_index = 0; param_index < n; param_index++)
               if (key_str == pParam_desc[param_index].m_pName)
                  break;

            if (param_index == n)
            {
               console::error("Unrecognized command line parameter: \"%s\"", src_param.get_ptr());
               return false;
            }

            const param_desc& desc = pParam_desc[param_index];

            const uint cMaxValues = 16;
            dynamic_string val_str[cMaxValues];
            uint num_val_strs = 0;
            if (desc.m_num_values)
            {
               CRNLIB_ASSERT(desc.m_num_values <= cMaxValues);

               if ((arg_index + desc.m_num_values) > params.size())
               {
                  console::error("Expected %u value(s) after command line parameter: \"%s\"", desc.m_num_values, src_param.get_ptr());
                  return false;
               }

               for (uint v = 0; v < desc.m_num_values; v++)
                  val_str[num_val_strs++] = params[arg_index++];
            }

            dynamic_string_array strings;

            if ((desc.m_support_listing_file) && (val_str[0].get_len() >= 2) && (val_str[0][0] == '@'))
            {
               dynamic_string filename(val_str[0]);
               filename.right(1);
               filename.unquote();

               if (!load_string_file(filename.get_ptr(), strings))
               {
                  console::error("Failed loading listing file \"%s\"!", filename.get_ptr());
                  return false;
               }
            }
            else
            {
               for (uint v = 0; v < num_val_strs; v++)
               {
                  val_str[v].unquote();
                  strings.push_back(val_str[v]);
               }
            }

            param_value pv;
            pv.m_values.swap(strings);
            pv.m_index = cur_arg_index;
            pv.m_modifier = (int8)modifier;
            m_param_map.insert(std::make_pair(key_str, pv));
         }
         else
         {
            param_value pv;
            pv.m_values.push_back(src_param);
            pv.m_values.back().unquote();
            pv.m_index = cur_arg_index;
            m_param_map.insert(std::make_pair(g_empty_dynamic_string, pv));
         }
      }

      return true;
   }

   bool command_line_params::parse(const char* pCmd_line, uint n, const param_desc* pParam_desc, bool skip_first_param)
   {
      CRNLIB_ASSERT(n && pParam_desc);

      dynamic_string_array p;
      if (!split_params(pCmd_line, p))
         return 0;

      if (p.empty())
         return 0;

      if (skip_first_param)
         p.erase(0U);

      return parse(p, n, pParam_desc);
   }

   bool command_line_params::is_param(uint index) const
   {
      CRNLIB_ASSERT(index < m_params.size());
      if (index >= m_params.size())
         return false;

      const dynamic_string& w = m_params[index];
      if (w.is_empty())
         return false;

#if CRNLIB_CMD_LINE_ALLOW_SLASH_PARAMS
      return (w.get_len() >= 2) && ((w[0] == '-') || (w[0] == '/'));
#else
      return (w.get_len() >= 2) && (w[0] == '-');
#endif
   }

   uint command_line_params::find(uint num_keys, const char** ppKeys, crnlib::vector<param_map_const_iterator>* pIterators, crnlib::vector<uint>* pUnmatched_indices) const
   {
      CRNLIB_ASSERT(ppKeys);

      if (pUnmatched_indices)
      {
         pUnmatched_indices->resize(m_params.size());
         for (uint i = 0; i < m_params.size(); i++)
            (*pUnmatched_indices)[i] = i;
      }

      uint n = 0;
      for (uint i = 0; i < num_keys; i++)
      {
         const char* pKey = ppKeys[i];

         param_map_const_iterator begin, end;
         find(pKey, begin, end);

         while (begin != end)
         {
            if (pIterators)
               pIterators->push_back(begin);

            if (pUnmatched_indices)
            {
               int k = pUnmatched_indices->find(begin->second.m_index);
               if (k >= 0)
                  pUnmatched_indices->erase_unordered(k);
            }

            n++;
            begin++;
         }
      }

      return n;
   }

   void command_line_params::find(const char* pKey, param_map_const_iterator& begin, param_map_const_iterator& end) const
   {
      dynamic_string key(pKey);
      begin = m_param_map.lower_bound(key);
      end = m_param_map.upper_bound(key);
   }

   uint command_line_params::get_count(const char* pKey) const
   {
      param_map_const_iterator begin, end;
      find(pKey, begin, end);

      uint n = 0;

      while (begin != end)
      {
         n++;
         begin++;
      }

      return n;
   }

   command_line_params::param_map_const_iterator command_line_params::get_param(const char* pKey, uint index) const
   {
      param_map_const_iterator begin, end;
      find(pKey, begin, end);

      if (begin == end)
         return m_param_map.end();

      uint n = 0;

      while ((begin != end) && (n != index))
      {
         n++;
         begin++;
      }

      if (begin == end)
         return m_param_map.end();

      return begin;
   }

   bool command_line_params::has_value(const char* pKey, uint index) const
   {
      return get_num_values(pKey, index) != 0;
   }

   uint command_line_params::get_num_values(const char* pKey, uint index) const
   {
      param_map_const_iterator it = get_param(pKey, index);

      if (it == end())
         return 0;

      return it->second.m_values.size();
   }

   bool command_line_params::get_value_as_bool(const char* pKey, uint index, bool def) const
   {
      param_map_const_iterator it = get_param(pKey, index);
      if (it == end())
         return def;

      if (it->second.m_modifier)
         return it->second.m_modifier > 0;
      else
         return true;
   }

   int command_line_params::get_value_as_int(const char* pKey, uint index, int def, int l, int h, uint value_index) const
   {
      param_map_const_iterator it = get_param(pKey, index);
      if ((it == end()) || (value_index >= it->second.m_values.size()))
         return def;

      int val;
      const char* p = it->second.m_values[value_index].get_ptr();
      if (!string_to_int(p, val))
      {
         crnlib::console::warning("Invalid value specified for parameter \"%s\", using default value of %i", pKey, def);
         return def;
      }

      if (val < l)
      {
         crnlib::console::warning("Value %i for parameter \"%s\" is out of range, clamping to %i", val, pKey, l);
         val = l;
      }
      else if (val > h)
      {
         crnlib::console::warning("Value %i for parameter \"%s\" is out of range, clamping to %i", val, pKey, h);
         val = h;
      }

      return val;
   }

   float command_line_params::get_value_as_float(const char* pKey, uint index, float def, float l, float h, uint value_index) const
   {
      param_map_const_iterator it = get_param(pKey, index);
      if ((it == end()) || (value_index >= it->second.m_values.size()))
         return def;

      float val;
      const char* p = it->second.m_values[value_index].get_ptr();
      if (!string_to_float(p, val))
      {
         crnlib::console::warning("Invalid value specified for float parameter \"%s\", using default value of %f", pKey, def);
         return def;
      }

      if (val < l)
      {
         crnlib::console::warning("Value %f for parameter \"%s\" is out of range, clamping to %f", val, pKey, l);
         val = l;
      }
      else if (val > h)
      {
         crnlib::console::warning("Value %f for parameter \"%s\" is out of range, clamping to %f", val, pKey, h);
         val = h;
      }

      return val;
   }

   bool command_line_params::get_value_as_string(const char* pKey, uint index, dynamic_string& value, uint value_index) const
   {
      param_map_const_iterator it = get_param(pKey, index);
      if ((it == end()) || (value_index >= it->second.m_values.size()))
      {
         value.empty();
         return false;
      }

      value = it->second.m_values[value_index];
      return true;
   }

   const dynamic_string& command_line_params::get_value_as_string_or_empty(const char* pKey, uint index, uint value_index) const
   {
      param_map_const_iterator it = get_param(pKey, index);
      if ((it == end()) || (value_index >= it->second.m_values.size()))
      return g_empty_dynamic_string;

      return it->second.m_values[value_index];
   }

} // namespace crnlib