// 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
