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

namespace crnlib {
char* crn_strdup(const char* pStr) {
  if (!pStr)
    pStr = "";

  size_t l = strlen(pStr) + 1;
  char* p = (char*)crnlib_malloc(l);
  if (p)
    memcpy(p, pStr, l);

  return p;
}

char* strcpy_safe(char* pDst, size_t dst_len, const char* pSrc) {
  CRNLIB_ASSERT(pDst && pSrc && dst_len);
  if (!dst_len)
    return pDst;

  char* q = pDst;
  char c;

  do {
    if (dst_len == 1) {
      *q++ = '\0';
      break;
    }

    c = *pSrc++;
    *q++ = c;

    dst_len--;

  } while (c);

  CRNLIB_ASSERT((q - pDst) <= (int)dst_len);

  return pDst;
}

bool int_to_string(int value, char* pDst, uint len) {
  CRNLIB_ASSERT(pDst);

  const uint cBufSize = 16;
  char buf[cBufSize];

  uint j = static_cast<uint>((value < 0) ? -value : value);

  char* p = buf + cBufSize - 1;

  *p-- = '\0';

  do {
    *p-- = static_cast<uint8>('0' + (j % 10));
    j /= 10;
  } while (j);

  if (value < 0)
    *p-- = '-';

  const size_t total_bytes = (buf + cBufSize - 1) - p;
  if (total_bytes > len)
    return false;

  for (size_t i = 0; i < total_bytes; i++)
    pDst[i] = p[1 + i];

  return true;
}

bool uint_to_string(uint value, char* pDst, uint len) {
  CRNLIB_ASSERT(pDst);

  const uint cBufSize = 16;
  char buf[cBufSize];

  char* p = buf + cBufSize - 1;

  *p-- = '\0';

  do {
    *p-- = static_cast<uint8>('0' + (value % 10));
    value /= 10;
  } while (value);

  const size_t total_bytes = (buf + cBufSize - 1) - p;
  if (total_bytes > len)
    return false;

  for (size_t i = 0; i < total_bytes; i++)
    pDst[i] = p[1 + i];

  return true;
}

bool string_to_int(const char*& pBuf, int& value) {
  value = 0;

  CRNLIB_ASSERT(pBuf);
  const char* p = pBuf;

  while (*p && isspace(*p))
    p++;

  uint result = 0;
  bool negative = false;

  if (!isdigit(*p)) {
    if (p[0] == '-') {
      negative = true;
      p++;
    } else
      return false;
  }

  while (*p && isdigit(*p)) {
    if (result & 0xE0000000U)
      return false;

    const uint result8 = result << 3U;
    const uint result2 = result << 1U;

    if (result2 > (0xFFFFFFFFU - result8))
      return false;

    result = result8 + result2;

    uint c = p[0] - '0';
    if (c > (0xFFFFFFFFU - result))
      return false;

    result += c;

    p++;
  }

  if (negative) {
    if (result > 0x80000000U) {
      value = 0;
      return false;
    }
    value = -static_cast<int>(result);
  } else {
    if (result > 0x7FFFFFFFU) {
      value = 0;
      return false;
    }
    value = static_cast<int>(result);
  }

  pBuf = p;

  return true;
}

bool string_to_int64(const char*& pBuf, int64& value) {
  value = 0;

  CRNLIB_ASSERT(pBuf);
  const char* p = pBuf;

  while (*p && isspace(*p))
    p++;

  uint64 result = 0;
  bool negative = false;

  if (!isdigit(*p)) {
    if (p[0] == '-') {
      negative = true;
      p++;
    } else
      return false;
  }

  while (*p && isdigit(*p)) {
    if (result & 0xE000000000000000ULL)
      return false;

    const uint64 result8 = result << 3U;
    const uint64 result2 = result << 1U;

    if (result2 > (0xFFFFFFFFFFFFFFFFULL - result8))
      return false;

    result = result8 + result2;

    uint c = p[0] - '0';
    if (c > (0xFFFFFFFFFFFFFFFFULL - result))
      return false;

    result += c;

    p++;
  }

  if (negative) {
    if (result > 0x8000000000000000ULL) {
      value = 0;
      return false;
    }
    value = -static_cast<int64>(result);
  } else {
    if (result > 0x7FFFFFFFFFFFFFFFULL) {
      value = 0;
      return false;
    }
    value = static_cast<int64>(result);
  }

  pBuf = p;

  return true;
}

bool string_to_uint(const char*& pBuf, uint& value) {
  value = 0;

  CRNLIB_ASSERT(pBuf);
  const char* p = pBuf;

  while (*p && isspace(*p))
    p++;

  uint result = 0;

  if (!isdigit(*p))
    return false;

  while (*p && isdigit(*p)) {
    if (result & 0xE0000000U)
      return false;

    const uint result8 = result << 3U;
    const uint result2 = result << 1U;

    if (result2 > (0xFFFFFFFFU - result8))
      return false;

    result = result8 + result2;

    uint c = p[0] - '0';
    if (c > (0xFFFFFFFFU - result))
      return false;

    result += c;

    p++;
  }

  value = result;

  pBuf = p;

  return true;
}

bool string_to_uint64(const char*& pBuf, uint64& value) {
  value = 0;

  CRNLIB_ASSERT(pBuf);
  const char* p = pBuf;

  while (*p && isspace(*p))
    p++;

  uint64 result = 0;

  if (!isdigit(*p))
    return false;

  while (*p && isdigit(*p)) {
    if (result & 0xE000000000000000ULL)
      return false;

    const uint64 result8 = result << 3U;
    const uint64 result2 = result << 1U;

    if (result2 > (0xFFFFFFFFFFFFFFFFULL - result8))
      return false;

    result = result8 + result2;

    uint c = p[0] - '0';
    if (c > (0xFFFFFFFFFFFFFFFFULL - result))
      return false;

    result += c;

    p++;
  }

  value = result;

  pBuf = p;

  return true;
}

bool string_to_bool(const char* p, bool& value) {
  CRNLIB_ASSERT(p);

  value = false;

  if (crnlib_stricmp(p, "false") == 0)
    return true;

  if (crnlib_stricmp(p, "true") == 0) {
    value = true;
    return true;
  }

  const char* q = p;
  uint v;
  if (string_to_uint(q, v)) {
    if (!v)
      return true;
    else if (v == 1) {
      value = true;
      return true;
    }
  }

  return false;
}

bool string_to_float(const char*& p, float& value, uint round_digit) {
  double d;
  if (!string_to_double(p, d, round_digit)) {
    value = 0;
    return false;
  }
  value = static_cast<float>(d);
  return true;
}

bool string_to_double(const char*& p, double& value, uint round_digit) {
  return string_to_double(p, p + 128, value, round_digit);
}

// I wrote this approx. 20 years ago in C/assembly using a limited FP emulator package, so it's a bit crude.
bool string_to_double(const char*& p, const char* pEnd, double& value, uint round_digit) {
  CRNLIB_ASSERT(p);

  value = 0;

  enum { AF_BLANK = 1,
         AF_SIGN = 2,
         AF_DPOINT = 3,
         AF_BADCHAR = 4,
         AF_OVRFLOW = 5,
         AF_EXPONENT = 6,
         AF_NODIGITS = 7 };
  int status = 0;

  const char* buf = p;

  int got_sign_flag = 0, got_dp_flag = 0, got_num_flag = 0;
  int got_e_flag = 0, got_e_sign_flag = 0, e_sign = 0;
  uint whole_count = 0, frac_count = 0;

  double whole = 0, frac = 0, scale = 1, exponent = 1;

  if (p >= pEnd) {
    status = AF_NODIGITS;
    goto af_exit;
  }

  while (*buf) {
    if (!isspace(*buf))
      break;
    if (++buf >= pEnd) {
      status = AF_NODIGITS;
      goto af_exit;
    }
  }

  p = buf;

  while (*buf) {
    p = buf;
    if (buf >= pEnd)
      break;

    int i = *buf++;

    switch (i) {
      case 'e':
      case 'E': {
        got_e_flag = 1;
        goto exit_while;
      }
      case '+': {
        if ((got_num_flag) || (got_sign_flag)) {
          status = AF_SIGN;
          goto af_exit;
        }

        got_sign_flag = 1;

        break;
      }
      case '-': {
        if ((got_num_flag) || (got_sign_flag)) {
          status = AF_SIGN;
          goto af_exit;
        }

        got_sign_flag = -1;

        break;
      }
      case '.': {
        if (got_dp_flag) {
          status = AF_DPOINT;
          goto af_exit;
        }

        got_dp_flag = 1;

        break;
      }
      default: {
        if ((i < '0') || (i > '9'))
          goto exit_while;
        else {
          i -= '0';

          got_num_flag = 1;

          if (got_dp_flag) {
            if (frac_count < round_digit) {
              frac = frac * 10.0f + i;

              scale = scale * 10.0f;
            } else if (frac_count == round_digit) {
              if (i >= 5) /* check for round */
                frac = frac + 1.0f;
            }

            frac_count++;
          } else {
            whole = whole * 10.0f + i;

            whole_count++;

            if (whole > 1e+100) {
              status = AF_OVRFLOW;
              goto af_exit;
            }
          }
        }

        break;
      }
    }
  }

exit_while:

  if (got_e_flag) {
    if ((got_num_flag == 0) && (got_dp_flag)) {
      status = AF_EXPONENT;
      goto af_exit;
    }

    int e = 0;
    e_sign = 1;
    got_num_flag = 0;
    got_e_sign_flag = 0;

    while (*buf) {
      p = buf;
      if (buf >= pEnd)
        break;

      int i = *buf++;

      if (i == '+') {
        if ((got_num_flag) || (got_e_sign_flag)) {
          status = AF_EXPONENT;
          goto af_exit;
        }

        e_sign = 1;
        got_e_sign_flag = 1;
      } else if (i == '-') {
        if ((got_num_flag) || (got_e_sign_flag)) {
          status = AF_EXPONENT;
          goto af_exit;
        }

        e_sign = -1;
        got_e_sign_flag = 1;
      } else if ((i >= '0') && (i <= '9')) {
        got_num_flag = 1;

        if ((e = (e * 10) + (i - 48)) > 100) {
          status = AF_EXPONENT;
          goto af_exit;
        }
      } else
        break;
    }

    for (int i = 1; i <= e; i++) /* compute 10^e */
      exponent = exponent * 10.0f;
  }

  if (((whole_count + frac_count) == 0) && (got_e_flag == 0)) {
    status = AF_NODIGITS;
    goto af_exit;
  }

  if (frac)
    whole = whole + (frac / scale);

  if (got_e_flag) {
    if (e_sign > 0)
      whole = whole * exponent;
    else
      whole = whole / exponent;
  }

  if (got_sign_flag < 0)
    whole = -whole;

  value = whole;

af_exit:
  return (status == 0);
}

}  // namespace crnlib
