#pragma once
#include "./std_headers.h"
#include <any>

#include "./jsonio.h"
#include "./jsonio_parse.h"

/*
  Write C++ types to a string (char *) as JSON.
  For efficiency, this is a two-pass process:
    - Call wrJsonSize to get the buffer size needed (a slight over-estimate).
    - Allocate a buffer
    - Call wrJson.
  See asJson (defined below) for the right way to do it.

  To allow serializing your own types, add definitions of wrJsonSize, wrJson, and rdJson.

  Read C++ types from a string (char *) as JSON
  The string should be null-terminated.
  See fromJson (defined below) for the right way to do it.

*/

void wrJsonSize(WrJsonContext &ctx, bool const &value);
void wrJson(WrJsonContext &ctx, bool const &value);
bool rdJson(RdJsonContext &ctx, bool &value);

void wrJsonSize(WrJsonContext &ctx, S32 const &value);
void wrJson(WrJsonContext &ctx, S32 const &value);
bool rdJson(RdJsonContext &ctx, S32 &value);

void wrJsonSize(WrJsonContext &ctx, U32 const &value);
void wrJson(WrJsonContext &ctx, U32 const &value);
bool rdJson(RdJsonContext &ctx, U32 &value);

void wrJsonSize(WrJsonContext &ctx, S64 const &value);
void wrJson(WrJsonContext &ctx, S64 const &value);
bool rdJson(RdJsonContext &ctx, S64 &value);

void wrJsonSize(WrJsonContext &ctx, U64 const &value);
void wrJson(WrJsonContext &ctx, U64 const &value);
bool rdJson(RdJsonContext &ctx, U64 &value);

void wrJsonSize(WrJsonContext &ctx, float const &value);
void wrJson(WrJsonContext &ctx, float const &value);
bool rdJson(RdJsonContext &ctx, float &value);

void wrJsonSize(WrJsonContext &ctx, double const &value);
void wrJson(WrJsonContext &ctx, double const &value);
bool rdJson(RdJsonContext &ctx, double &value);

void wrJsonSize(WrJsonContext &ctx, std::complex<double> const &value);
void wrJson(WrJsonContext &ctx, std::complex<double> const &value);
bool rdJson(RdJsonContext &ctx, std::complex<double> &value);

void wrJsonSize(WrJsonContext &ctx, string const &value);
void wrJson(WrJsonContext &ctx, string const &value);
bool rdJson(RdJsonContext &ctx, string &value);

void wrJsonSize(WrJsonContext &ctx, jsonstr const &value);
void wrJson(WrJsonContext &ctx, jsonstr const &value);
bool rdJson(RdJsonContext &ctx, jsonstr &value);

void wrJsonSize(WrJsonContext &ctx, any const &value);
void wrJson(WrJsonContext &ctx, any const &value);
bool rdJson(RdJsonContext &ctx, any &value);

/*
  Json - shared_ptr<T>
*/

template <typename T>
void wrJsonSize(WrJsonContext &ctx, shared_ptr<T> const &p)
{
  if (p) {
    wrJsonSize(ctx, *p);
  }
  else {
    ctx.size += 4; // null;
  }
}

template <typename T>
void wrJson(WrJsonContext &ctx, shared_ptr<T> const &p)
{
  if (p) {
    wrJson(ctx, *p);
  }
  else {
    ctx.emit("null");
  }
}

template <typename T>
bool rdJson(RdJsonContext &ctx, shared_ptr<T> &p)
{
  ctx.skipSpace();
  if (ctx.s[0] == 'n' && ctx.s[1] == 'u' && ctx.s[2] == 'l' && ctx.s[3] == 'l') {
    ctx.s += 4;
    p = nullptr;
    return true;
  }
  if (!p) {
    p = make_shared<T>();
  }
  return rdJson(ctx, *p);
}

/*
  Json - vector<T>
*/
template <typename T>
void wrJson(WrJsonContext &ctx, vector<T> const &arr)
{
  *ctx.s++ = '[';
  bool sep = false;
  for (auto it = arr.begin(); it != arr.end(); it++) {
    if (sep) *ctx.s++ = ',';
    sep = true;
    wrJson(ctx, *it);
  }
  *ctx.s++ = ']';
}

template <typename T>
void wrJsonSize(WrJsonContext &ctx, vector<T> const &arr)
{
  ctx.size += 2 + arr.size();
  for (auto it = arr.begin(); it != arr.end(); it++) {
    wrJsonSize(ctx, *it);
  }
}

template <typename T>
bool rdJson(RdJsonContext &ctx, vector<T> &arr)
{
  ctx.skipSpace();
  if (*ctx.s != '[') return ctx.fail(typeid(arr), "expected [");
  ctx.s++;
  arr.clear();
  while (1) {
    ctx.skipSpace();
    if (*ctx.s == ']') break;
    T tmp;
    if (!rdJson(ctx, tmp)) return ctx.fail(typeid(arr), "rdJson(tmp)");
    arr.push_back(tmp);
    ctx.skipSpace();
    if (*ctx.s == ',') {
      ctx.s++;
    }
    else if (*ctx.s == ']') {
      break;
    }
    else {
      return ctx.fail(typeid(arr), "expected , or ]");
    }
  }
  ctx.s++;
  return true;
}

#ifdef USE_EIGEN3
/*
  Json - Eigen::Matrix
  Instantiated for relevant values of T in jsonio_types.cc
*/
template <typename _Scalar, int _Rows, int _Cols>
void wrJsonSize(WrJsonContext &ctx, Eigen::Matrix<_Scalar, _Rows, _Cols> const &arr);
template <typename _Scalar, int _Rows, int _Cols>
void wrJson(WrJsonContext &ctx, Eigen::Matrix<_Scalar, _Rows, _Cols> const &arr);
template <typename _Scalar, int _Rows, int _Cols>
bool rdJson(RdJsonContext &ctx, Eigen::Matrix<_Scalar, _Rows, _Cols> &arr);
#endif

/*
  JsonVec - vector<shared_ptr<T>>
*/
template <typename T>
void wrJson(WrJsonContext &ctx, vector<shared_ptr<T>> const &arr)
{
  *ctx.s++ = '[';
  bool sep = false;
  for (auto it = arr.begin(); it != arr.end(); it++) {
    if (sep) *ctx.s++ = ',';
    sep = true;
    if (!*it) {
      ctx.emit("null");
    }
    else {
      wrJson(ctx, **it);
    }
  }
  *ctx.s++ = ']';
}

template <typename T>
void wrJsonSize(WrJsonContext &ctx, vector<shared_ptr<T>> const &arr)
{
  ctx.size += 2 + arr.size();
  for (auto it = arr.begin(); it != arr.end(); it++) {
    if (!*it) {
      ctx.size += 4;
    }
    else {
      wrJsonSize(ctx, **it);
    }
  }
}

template <typename T>
bool rdJson(RdJsonContext &ctx, vector<shared_ptr<T>> &arr)
{
  ctx.skipSpace();
  if (*ctx.s != '[') return ctx.fail(typeid(arr), "expected [");
  ctx.s++;
  arr.clear();
  while (1) {
    ctx.skipSpace();
    if (*ctx.s == ']') break;
    if (ctx.s[0] == 'n' && ctx.s[1] == 'u' && ctx.s[2] == 'l' && ctx.s[3] == 'l') {
      ctx.s += 4;
      arr.emplace_back(nullptr);
    }
    else {
      auto tmp = make_shared<T>();
      if (!rdJson(ctx, *tmp)) return ctx.fail(typeid(arr), "rdJson(tmp)");
      arr.push_back(tmp);
    }
    ctx.skipSpace();
    if (*ctx.s == ',') {
      ctx.s++;
    }
    else if (*ctx.s == ']') {
      break;
    }
    else {
      return ctx.fail(typeid(arr), "expected , or ]");
    }
  }
  ctx.s++;
  return true;
}

/*
  Json - map<KT, VT>
*/
template <typename KT, typename VT>
void wrJsonSize(WrJsonContext &ctx, map<KT, VT> const &arr)
{
  ctx.size += 2;
  for (auto it = arr.begin(); it != arr.end(); it++) {
    wrJsonSize(ctx, it->first);
    wrJsonSize(ctx, it->second);
    ctx.size += 2;
  }
}

template <typename KT, typename VT>
void wrJson(WrJsonContext &ctx, map<KT, VT> const &arr)
{
  *ctx.s++ = '{';
  bool sep = false;
  for (auto it = arr.begin(); it != arr.end(); it++) {
    if (sep) *ctx.s++ = ',';
    sep = true;
    wrJson(ctx, it->first);
    *ctx.s++ = ':';
    wrJson(ctx, it->second);
  }
  *ctx.s++ = '}';
}

template <typename KT, typename VT>
bool rdJson(RdJsonContext &ctx, map<KT, VT> &arr)
{
  ctx.skipSpace();
  if (*ctx.s != '{') return ctx.fail(typeid(arr), "expected {");
  ctx.s++;
  arr.clear();
  while (1) {
    ctx.skipSpace();
    if (*ctx.s == '}') break;
    KT ktmp;
    if (!rdJson(ctx, ktmp)) return ctx.fail(typeid(arr), "rdJson(ktmp)");
    ctx.skipSpace();
    if (*ctx.s != ':') return ctx.fail(typeid(arr), "expected :");
    ctx.s++;
    ctx.skipSpace();
    VT vtmp;
    if (!rdJson(ctx, vtmp)) return ctx.fail(typeid(arr), "rdJson(vtmp)");
    arr[ktmp] = vtmp;

    ctx.skipSpace();
    if (*ctx.s == ',') {
      ctx.s++;
    }
    else if (*ctx.s == '}') {
      break;
    }
    else {
      return ctx.fail(typeid(arr), "Expected , or }");
    }
  }
  ctx.s++;
  return true;
}

/*
  Json - map<KT, shared_ptr<VT>>
*/
template <typename KT, typename VT>
void wrJsonSize(WrJsonContext &ctx, map<KT, shared_ptr<VT>> const &arr)
{
  ctx.size += 2;
  for (auto it = arr.begin(); it != arr.end(); it++) {
    if (!it->second) continue;
    wrJsonSize(ctx, it->first);
    wrJsonSize(ctx, *it->second);
    ctx.size += 2;
  }
}

template <typename KT, typename VT>
void wrJson(WrJsonContext &ctx, map<KT, shared_ptr<VT>> const &arr)
{
  *ctx.s++ = '{';
  bool sep = false;
  for (auto it = arr.begin(); it != arr.end(); it++) {
    if (!it->second) continue;
    if (sep) *ctx.s++ = ',';
    sep = true;
    wrJson(ctx, it->first);
    *ctx.s++ = ':';
    wrJson(ctx, *it->second);
  }
  *ctx.s++ = '}';
}

template <typename KT, typename VT>
bool rdJson(RdJsonContext &ctx, map<KT, shared_ptr<VT>> &arr)
{
  ctx.skipSpace();
  if (*ctx.s != '{') return ctx.fail(typeid(arr), "Expected {");
  ctx.s++;
  arr.clear();
  while (1) {
    ctx.skipSpace();
    if (*ctx.s == '}') break;
    KT ktmp;
    if (!rdJson(ctx, ktmp)) return ctx.fail(typeid(arr), "rdJson(ktmp)");
    ctx.skipSpace();
    if (*ctx.s != ':') return ctx.fail(typeid(arr), "Expected :");
    ctx.s++;
    ctx.skipSpace();
    auto vtmp = make_shared<VT>();
    if (!rdJson(ctx, *vtmp)) return ctx.fail(typeid(arr), "rdJson(vtmp)");
    arr[ktmp] = vtmp;

    ctx.skipSpace();
    if (*ctx.s == ',') {
      ctx.s++;
    }
    else if (*ctx.s == '}') {
      break;
    }
    else {
      return ctx.fail(typeid(arr), "Expected , or }");
    }
  }
  ctx.s++;
  return true;
}

/*
  Json - pair<FIRST, SECOND>
*/
template <typename FIRST, typename SECOND>
void wrJsonSize(WrJsonContext &ctx, pair<FIRST, SECOND> const &it)
{
  ctx.size += 3;
  wrJsonSize(ctx, it.first);
  wrJsonSize(ctx, it.second);
}

template <typename FIRST, typename SECOND>
void wrJson(WrJsonContext &ctx, pair<FIRST, SECOND> const &it)
{
  *ctx.s++ = '[';
  wrJson(ctx, it.first);
  *ctx.s++ = ',';
  wrJson(ctx, it.second);
  *ctx.s++ = ']';
}

template <typename FIRST, typename SECOND>
bool rdJson(RdJsonContext &ctx, pair<FIRST, SECOND> &it)
{
  ctx.skipSpace();
  if (*ctx.s != '[') return ctx.fail(typeid(it), "expected [");
  ctx.s++;
  ctx.skipSpace();
  if (!rdJson(ctx, it.first)) return ctx.fail(typeid(it), "rdJson(it.first)");
  ctx.skipSpace();
  if (*ctx.s != ',') return ctx.fail(typeid(it), "expected ,");
  ctx.s++;
  ctx.skipSpace();
  if (!rdJson(ctx, it.second)) return ctx.fail(typeid(it), "rdJson(it.second)");
  ctx.skipSpace();
  if (*ctx.s != ']') return ctx.fail(typeid(it), "expected ]");
  ctx.s++;
  return true;
}
