#include "./jsonio.h"

#include "./jsonio_parse.h"

#include <cxxabi.h>
#include <typeindex>


/* ----------------------------------------------------------------------
   Low-level json stuff
   Spec at http://www.json.org/
*/

RdJsonContext::RdJsonContext(char const *_s, shared_ptr<ChunkFile> const &_blobs, bool _noTypeCheck)
    : fullStr(_s), s(_s), blobs(_blobs), noTypeCheck(_noTypeCheck)
{
}

RdJsonContext::~RdJsonContext() {}

bool RdJsonContext::fail(std::type_info const &t, string const &reason)
{
  failType = &t;
  failReason = reason;
  failPos = s;
  return false;
}

bool RdJsonContext::fail(std::type_info const &t, char const *reason)
{
  failType = &t;
  failReason = reason;
  failPos = s;
  return false;
}

bool RdJsonContext::failSizeMismatch(std::type_info const &t, size_t actual, size_t expected)
{
  return fail(t, "Size mismatch: actual="s + to_string(actual) + " expected="s + to_string(expected));
}

static void simplifyTypeName(string &name, string const &longVersion, string const &shortVersion)
{
  while (true) {
    auto pos = name.find(longVersion);
    if (pos == string::npos) return;
    name.replace(pos, longVersion.size(), shortVersion);
  }
}

static string niceTypeName(std::type_info const &t)
{
  auto ti = std::type_index(t);
  if (ti == std::type_index(typeid(string)))
    return "string"; // instead of 12basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcE or something
  int status = 0;
  char const *retCstr = abi::__cxa_demangle(t.name(), 0, 0, &status);
  if (!retCstr) {
    return string(ti.name());
  }
  string ret(retCstr);
  free((void *)retCstr);

  simplifyTypeName(ret, "std::__1::", "");
  simplifyTypeName(ret, "std::", "");
  simplifyTypeName(ret, "basic_string<char, char_traits<char>, allocator<char> >", "string");
  simplifyTypeName(ret, "vector<char, allocator<char> >", "vector<char>");

  return ret;
}

string RdJsonContext::fmtFail()
{
  if (!failPos || !failType || failReason.empty()) {
    return "no failure noted"s;
  }

  auto ret = "rdJson<"s + niceTypeName(*failType) + "> fail: "s + failReason;

  string ss(fullStr);
  size_t off = (failPos - fullStr);
  ret += " at pos "s + to_string(off) + "/"s + to_string(ss.size());
  if (ss.size() < 500 && off < ss.size() + 2) {
    ret += " in\n"s + ss + "\n"s + string(off, ' ') + "^";
  }
  if (0) eprintf("%s\n", ret.c_str());
  return ret;
}

void RdJsonContext::skipSpace()
{
  while (1) {
    char c = *s;
    // Because isspace does funky locale-dependent stuff that I don't want
    if (c == ' ' || c == '\t' || c == '\n' || c == '\r') {
      s++;
    }
    else {
      break;
    }
  }
}

bool RdJsonContext::skipValue()
{
  skipSpace();
  if (*s == 0x22) {
    s++;
    while (1) {
      u_char c = *s++;
      if (c == 0x5c) {
        c = *s++;
        if (c == 'u') {
          c = *s++;
          if (!isHexDigit(c)) return fail(typeid(string), "expected unicode escape hex");
          c = *s++;
          if (!isHexDigit(c)) return fail(typeid(string), "expected unicode escape hex");
          c = *s++;
          if (!isHexDigit(c)) return fail(typeid(string), "expected unicode escape hex");
          c = *s++;
          if (!isHexDigit(c)) return fail(typeid(string), "expected unicode escape hex");
        }
        else {
        }
      }
      else if (c == 0x22) {
        break;
      }
      else if (c == 0) { // end of string
        s--;
        return fail(typeid(string), "end of string");
      }
      else if (c < 0x20) { // control character, error
        s--;
        return fail(typeid(string), "surprising control character");
      }
    }
  }
  else if (*s == '[') {
    s++;
    skipSpace();
    while (1) {
      if (*s == ',') {
        s++;
      }
      else if (*s == ']') {
        s++;
        break;
      }
      else {
        if (!skipValue()) return false;
      }
    }
  }
  else if (*s == '{') {
    s++;
    skipSpace();
    while (1) {
      if (*s == ',') {
        s++;
      }
      else if (*s == ':') {
        s++;
      }
      else if (*s == '}') {
        s++;
        break;
      }
      else {
        if (!skipValue()) return false;
      }
    }
  }
  else if (isalnum(*s) || *s == '.' || *s == '-') {
    s++;
    while (isalnum(*s) || *s == '.' || *s == '-') s++;
  }
  else {
    return false;
  }

  return true;
}

bool RdJsonContext::skipMember()
{
  skipSpace();
  if (*s == '\"') {
    string tmp;
    rdJson(*this, tmp);
    skipSpace();
    if (*s == ':') {
      s++;
      skipSpace();
      if (!skipValue()) return false;
      return true;
    }
  }
  return false;
}

bool RdJsonContext::match(char const *pattern)
{
  skipSpace();
  char const *p = s;
  while (*pattern) {
    if (*pattern == ' ') {
      while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') {
        p++;
      }
      pattern++;
    }
    else if (*p == *pattern) {
      p++;
      pattern++;
    }
    else {
      return false;
    }
  }
  s = p;
  return true;
}

bool RdJsonContext::matchKey(char const *pattern)
{
  skipSpace();
  char const *p = s;
  if (*p != '"') {
    return false;
  }
  p++;
  while (*pattern) {
    if (*p == *pattern) {
      p++;
      pattern++;
    }
    else {
      return false;
    }
  }
  if (*p != '"') {
    return false;
  }
  p++;
  while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') {
    p++;
  }
  if (*p != ':') {
    return false;
  }
  p++;
  while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') {
    p++;
  }
  s = p;
  return true;
}

bool RdJsonContext::matchAnyKey(string &key)
{
  skipSpace();
  char const *p = s;
  if (*p != '"') {
    return false;
  }
  p++;
  key.clear();
  while (*p != '"') {
    key += *p++;
  }

  if (*p != '"') {
    return false;
  }
  p++;
  while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') {
    p++;
  }
  if (*p != ':') {
    return false;
  }
  p++;
  while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') {
    p++;
  }
  s = p;
  return true;
}


WrJsonContext::WrJsonContext() {}

WrJsonContext::~WrJsonContext() {}

void WrJsonContext::emit(char const *str)
{
  while (*str) {
    *s++ = *str++;
  }
}
