/* Blinker
   Copyright 2011-2019 Stefan Schuermans <stefan@blinkenarea.org>
   Copyleft GNU public license - http://www.gnu.org/copyleft/gpl.html
   a blinkenarea.org project */

#include <math.h>
#include <string>

#include "StringParser.h"

namespace Blinker {

/**
 * @brief constructor
 * @param[in] str string to parse
 */
StringParser::StringParser(const std::string &str):
  m_str(str),
  m_it(m_str.begin())
{
}

/**
 * @brief parse fixed character
 * @param[in] chr character to expect
 * @return if expected character was found and processed
 */
bool StringParser::fixChr(char chr)
{
  if (m_it == m_str.end() || *m_it != chr)
    return false;
  ++m_it;
  return true;
}

/**
 * @brief parse one character out of a set
 * @param[in] set set of characters allowed
 * @param[out] chr character parsed
 * @return if a character from the set was found and processed
 */
bool StringParser::oneChrOf(const std::string &set, char &chr)
{
  if (m_it == m_str.end() || set.find(*m_it) == std::string::npos)
    return false;
  chr = *m_it;
  ++m_it;
  return true;
}

/**
 * @brief parse boolean value
 * @param[out] boolVal boolean value parsed from string
 * @return if parsing was successful
 */
bool StringParser::boolVal(bool &boolVal)
{
  if (m_it == m_str.end())
    return false;
  if (*m_it == '0') {
    boolVal = false;
    return true;
  }
  if (*m_it == '1') {
    boolVal = true;
    return true;
  }
  if (*m_it == 'f') {
    boolVal = false;
    const char *ptr = "false";
    while (m_it != m_str.end() && *ptr && *m_it == *ptr)
      ++m_it, ++ptr;
    return !*ptr;
  }
  if (*m_it == 't') {
    boolVal = true;
    const char *ptr = "true";
    while (m_it != m_str.end() && *ptr && *m_it == *ptr)
      ++m_it, ++ptr;
    return !*ptr;
  }
  return false;
}

/**
 * @brief parse unsigned number
 * @param[out] uint number parsed from string
 * @return if parsing was successful
 */
bool StringParser::uintNo(unsigned int &uint)
{
  bool ret = false;
  uint = 0;
  while (m_it != m_str.end() && *m_it >= '0' && *m_it <= '9') {
    uint *= 10;
    uint += *m_it - '0';
    ++m_it;
    ret = true;
  }
  return ret;
}

/**
 * @brief parse unsigned number and check minimum
 * @param[in] min minimum value to expect
 * @param[out] uint number parsed from string
 * @return if parsing was successful
 */
bool StringParser::uintMin(unsigned int min, unsigned int &uint)
{
  return uintNo(uint) && uint >= min;
}

/**
 * @brief parse hexadecimal number
 * @param[in] digits number of digits to parse
 * @param[out] uint number parsed from string
 * @return if parsing was successful
 */
bool StringParser::uintHex(unsigned int digits, unsigned int &uint)
{
  uint = 0;
  for (; digits > 0; --digits) {
    if (m_it == m_str.end()) {
      return false;
    }
    char chr = *m_it;
    ++m_it;
    uint <<= 4;
    if (chr >= '0' && chr <= '9') {
      uint |= chr - '0';
    } else if (chr >= 'A' && chr <= 'F') {
      uint |= chr - 'A' + 10;
    } else if (chr >= 'a' && chr <= 'f') {
      uint |= chr - 'a' + 10;
    } else {
      return false;
    }
  }
  return true;
}

/**
 * @brief parse signed number
 * @param[out] sint number parsed from string
 * @return if parsing was successful
 */
bool StringParser::sintNo(int &sint)
{
  int sign = 1;
  unsigned int uint;
  bool ret;
  sint = 0;
  if (m_it != m_str.end()) {
    switch (*m_it) {
      case '+':
        sign = 1;
        ++m_it;
        break;
      case '-':
        sign = -1;
        ++m_it;
        break;
    }
  }
  ret = uintNo(uint);
  sint = sign * uint;
  return ret;
}

/**
 * @brief parse floating point value
 * @param[out] fl floating point value parsed from string
 * @return if parsing was successful
 */
bool StringParser::floatVal(float &fl)
{
  /* parse float manually to accept floats with dot as decimal separator for
   * all locales */
  int sign = 1, exp_sign = 1;
  float val = 0.0f, frac_val = 0.1f, exp_val = 0.0f;
  bool ret = false;
  // sign
  if (m_it != m_str.end()) {
    switch (*m_it) {
      case '+':
        sign = 1.0f;
        ++m_it;
        break;
      case '-':
        sign = -1.0f;
        ++m_it;
        break;
    }
  }
  // digits before decimal pdot
  while (m_it != m_str.end() && *m_it >= '0' && *m_it <= '9') {
    val *= 10.0f;
    val += *m_it - '0';
    ++m_it;
    ret = true;
  }
  // decimal dot
  if (m_it != m_str.end() && *m_it == '.') {
    ++m_it;
    ret = true;
    // digits after decimal dot
    while (m_it != m_str.end() && *m_it >= '0' && *m_it <= '9') {
      val += (*m_it - '0') * frac_val;
      ++m_it;
      frac_val *= 0.1f;
    }
  }
  // exponent
  if (m_it != m_str.end() && (*m_it == 'e' || *m_it == 'E')) {
    ++m_it;
    // exponent sign
    if (m_it != m_str.end()) {
      switch (*m_it) {
        case '+':
          exp_sign = 1.0f;
          ++m_it;
          break;
        case '-':
          exp_sign = -1.0f;
          ++m_it;
          break;
      }
    }
    // exponent digits
    while (m_it != m_str.end() && *m_it >= '0' && *m_it <= '9') {
      exp_val *= 10.0f;
      exp_val += *m_it - '0';
      ++m_it;
    }
  }
  fl = sign * val * pow(10.0, exp_sign * exp_val);
  return ret;
}

/**
 * @brief parse until a delimiter is found
 * @param[in] delim set of delimiter characters
 * @param[in] empty if empty substring is okay
 * @param[out] str substring parsed
 * @return if a substring was found and processed
 */
bool StringParser::untilDelim(const std::string &delim, bool empty,
                              std::string &str)
{
  str.clear();
  while (m_it != m_str.end() && delim.find(*m_it) == std::string::npos) {
    str += *m_it;
    ++m_it;
  }
  return empty || !str.empty();
}

/**
 * @brief check if parsing is done
 * @return if parsing is done (i.e. has arrived at the end of the string)
 */
bool StringParser::isDone()
{
  return m_it == m_str.end();
}

} // namespace Blinker

