// Copyright (c) From https://github.com/nbsdx/SimpleJSON
//               2022 Binbin Zhang (binbzha@qq.com)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef UTILS_JSON_H_
#define UTILS_JSON_H_

#include <cctype>
#include <cmath>
#include <cstdint>
#include <deque>
#include <initializer_list>
#include <iostream>
#include <map>
#include <ostream>
#include <string>
#include <type_traits>
#include <utility>

namespace json {

using std::deque;
using std::enable_if;
using std::initializer_list;
using std::is_convertible;
using std::is_floating_point;
using std::is_integral;
using std::is_same;
using std::map;
using std::string;

namespace {  // NOLINT
string json_escape(const string& str) {
  string output;
  for (unsigned i = 0; i < str.length(); ++i) switch (str[i]) {
      case '\"':
        output += "\\\"";
        break;
      case '\\':
        output += "\\\\";
        break;
      case '\b':
        output += "\\b";
        break;
      case '\f':
        output += "\\f";
        break;
      case '\n':
        output += "\\n";
        break;
      case '\r':
        output += "\\r";
        break;
      case '\t':
        output += "\\t";
        break;
      default:
        output += str[i];
        break;
    }
  return std::move(output);
}
}  // namespace

class JSON {
  union BackingData {
    BackingData(double d) : Float(d) {}
    BackingData(int l) : Int(l) {}
    BackingData(bool b) : Bool(b) {}
    BackingData(string s) : String(new string(s)) {}
    BackingData() : Int(0) {}

    deque<JSON>* List;
    map<string, JSON>* Map;
    string* String;
    double Float;
    int Int;
    bool Bool;
  } Internal;

 public:
  enum class Class { Null, Object, Array, String, Floating, Integral, Boolean };

  template <typename Container>
  class JSONWrapper {
    Container* object;

   public:
    explicit JSONWrapper(Container* val) : object(val) {}
    explicit JSONWrapper(std::nullptr_t) : object(nullptr) {}

    typename Container::iterator begin() {
      return object ? object->begin() : typename Container::iterator();
    }
    typename Container::iterator end() {
      return object ? object->end() : typename Container::iterator();
    }
    typename Container::const_iterator begin() const {
      return object ? object->begin() : typename Container::iterator();
    }
    typename Container::const_iterator end() const {
      return object ? object->end() : typename Container::iterator();
    }
  };

  template <typename Container>
  class JSONConstWrapper {
    const Container* object;

   public:
    explicit JSONConstWrapper(const Container* val) : object(val) {}
    explicit JSONConstWrapper(std::nullptr_t) : object(nullptr) {}

    typename Container::const_iterator begin() const {
      return object ? object->begin() : typename Container::const_iterator();
    }
    typename Container::const_iterator end() const {
      return object ? object->end() : typename Container::const_iterator();
    }
  };

  JSON() : Internal(), Type(Class::Null) {}

  explicit JSON(initializer_list<JSON> list) : JSON() {
    SetType(Class::Object);
    for (auto i = list.begin(), e = list.end(); i != e; ++i, ++i)
      operator[](i->ToString()) = *std::next(i);
  }

  JSON(JSON&& other) : Internal(other.Internal), Type(other.Type) {
    other.Type = Class::Null;
    other.Internal.Map = nullptr;
  }

  JSON& operator=(JSON&& other) {
    ClearInternal();
    Internal = other.Internal;
    Type = other.Type;
    other.Internal.Map = nullptr;
    other.Type = Class::Null;
    return *this;
  }

  JSON(const JSON& other) {
    switch (other.Type) {
      case Class::Object:
        Internal.Map = new map<string, JSON>(other.Internal.Map->begin(),
                                             other.Internal.Map->end());
        break;
      case Class::Array:
        Internal.List = new deque<JSON>(other.Internal.List->begin(),
                                        other.Internal.List->end());
        break;
      case Class::String:
        Internal.String = new string(*other.Internal.String);
        break;
      default:
        Internal = other.Internal;
    }
    Type = other.Type;
  }

  JSON& operator=(const JSON& other) {
    ClearInternal();
    switch (other.Type) {
      case Class::Object:
        Internal.Map = new map<string, JSON>(other.Internal.Map->begin(),
                                             other.Internal.Map->end());
        break;
      case Class::Array:
        Internal.List = new deque<JSON>(other.Internal.List->begin(),
                                        other.Internal.List->end());
        break;
      case Class::String:
        Internal.String = new string(*other.Internal.String);
        break;
      default:
        Internal = other.Internal;
    }
    Type = other.Type;
    return *this;
  }

  ~JSON() {
    switch (Type) {
      case Class::Array:
        delete Internal.List;
        break;
      case Class::Object:
        delete Internal.Map;
        break;
      case Class::String:
        delete Internal.String;
        break;
      default: {
      };
    }
  }

  template <typename T>
  explicit JSON(T b, typename enable_if<is_same<T, bool>::value>::type* = 0)
      : Internal(b), Type(Class::Boolean) {}

  template <typename T>
  explicit JSON(T i, typename enable_if<is_integral<T>::value &&
                                        !is_same<T, bool>::value>::type* = 0)
      : Internal(static_cast<int>(i)), Type(Class::Integral) {}

  template <typename T>
  explicit JSON(T f, typename enable_if<is_floating_point<T>::value>::type* = 0)
      : Internal(static_cast<double>(f)), Type(Class::Floating) {}

  template <typename T>
  explicit JSON(T s,
                typename enable_if<is_convertible<T, string>::value>::type* = 0)
      : Internal(string(s)), Type(Class::String) {}

  explicit JSON(std::nullptr_t) : Internal(), Type(Class::Null) {}

  static JSON Make(Class type) {
    JSON ret;
    ret.SetType(type);
    return ret;
  }

  static JSON Load(const string&);

  template <typename T>
  void append(T arg) {
    SetType(Class::Array);
    Internal.List->emplace_back(arg);
  }

  template <typename T, typename... U>
  void append(T arg, U... args) {
    append(arg);
    append(args...);
  }

  template <typename T>
  typename enable_if<is_same<T, bool>::value, JSON&>::type operator=(T b) {
    SetType(Class::Boolean);
    Internal.Bool = b;
    return *this;
  }

  template <typename T>
  typename enable_if<is_integral<T>::value && !is_same<T, bool>::value,
                     JSON&>::type
  operator=(T i) {
    SetType(Class::Integral);
    Internal.Int = i;
    return *this;
  }

  template <typename T>
  typename enable_if<is_floating_point<T>::value, JSON&>::type operator=(T f) {
    SetType(Class::Floating);
    Internal.Float = f;
    return *this;
  }

  template <typename T>
  typename enable_if<is_convertible<T, string>::value, JSON&>::type operator=(
      T s) {
    SetType(Class::String);
    *Internal.String = string(s);
    return *this;
  }

  JSON& operator[](const string& key) {
    SetType(Class::Object);
    return Internal.Map->operator[](key);
  }

  JSON& operator[](unsigned index) {
    SetType(Class::Array);
    if (index >= Internal.List->size()) Internal.List->resize(index + 1);
    return Internal.List->operator[](index);
  }

  JSON& at(const string& key) { return operator[](key); }

  const JSON& at(const string& key) const { return Internal.Map->at(key); }

  JSON& at(unsigned index) { return operator[](index); }

  const JSON& at(unsigned index) const { return Internal.List->at(index); }

  int length() const {
    if (Type == Class::Array)
      return Internal.List->size();
    else
      return -1;
  }

  bool hasKey(const string& key) const {
    if (Type == Class::Object)
      return Internal.Map->find(key) != Internal.Map->end();
    return false;
  }

  int size() const {
    if (Type == Class::Object)
      return Internal.Map->size();
    else if (Type == Class::Array)
      return Internal.List->size();
    else
      return -1;
  }

  Class JSONType() const { return Type; }

  /// Functions for getting primitives from the JSON object.
  bool IsNull() const { return Type == Class::Null; }

  string ToString() const {
    bool b;
    return std::move(ToString(&b));
  }
  string ToString(bool* ok) const {
    *ok = (Type == Class::String);
    return *ok ? std::move(json_escape(*Internal.String)) : string("");
  }

  double ToFloat() const {
    bool b;
    return ToFloat(&b);
  }
  double ToFloat(bool* ok) const {
    *ok = (Type == Class::Floating);
    return *ok ? Internal.Float : 0.0;
  }

  int ToInt() const {
    bool b;
    return ToInt(&b);
  }
  int ToInt(bool* ok) const {
    *ok = (Type == Class::Integral);
    return *ok ? Internal.Int : 0;
  }

  bool ToBool() const {
    bool b;
    return ToBool(&b);
  }
  bool ToBool(bool* ok) const {
    *ok = (Type == Class::Boolean);
    return *ok ? Internal.Bool : false;
  }

  JSONWrapper<map<string, JSON>> ObjectRange() {
    if (Type == Class::Object)
      return JSONWrapper<map<string, JSON>>(Internal.Map);
    return JSONWrapper<map<string, JSON>>(nullptr);
  }

  JSONWrapper<deque<JSON>> ArrayRange() {
    if (Type == Class::Array) return JSONWrapper<deque<JSON>>(Internal.List);
    return JSONWrapper<deque<JSON>>(nullptr);
  }

  JSONConstWrapper<map<string, JSON>> ObjectRange() const {
    if (Type == Class::Object)
      return JSONConstWrapper<map<string, JSON>>(Internal.Map);
    return JSONConstWrapper<map<string, JSON>>(nullptr);
  }

  JSONConstWrapper<deque<JSON>> ArrayRange() const {
    if (Type == Class::Array)
      return JSONConstWrapper<deque<JSON>>(Internal.List);
    return JSONConstWrapper<deque<JSON>>(nullptr);
  }

  string dump(int depth = 1, string tab = "  ") const {
    string pad = "";
    for (int i = 0; i < depth; ++i, pad += tab) {
    }

    switch (Type) {
      case Class::Null:
        return "null";
      case Class::Object: {
        string s = "{\n";
        bool skip = true;
        for (auto& p : *Internal.Map) {
          if (!skip) s += ",\n";
          s += (pad + "\"" + p.first + "\" : " + p.second.dump(depth + 1, tab));
          skip = false;
        }
        s += ("\n" + pad.erase(0, 2) + "}");
        return s;
      }
      case Class::Array: {
        string s = "[";
        bool skip = true;
        for (auto& p : *Internal.List) {
          if (!skip) s += ", ";
          s += p.dump(depth + 1, tab);
          skip = false;
        }
        s += "]";
        return s;
      }
      case Class::String:
        return "\"" + json_escape(*Internal.String) + "\"";
      case Class::Floating:
        return std::to_string(Internal.Float);
      case Class::Integral:
        return std::to_string(Internal.Int);
      case Class::Boolean:
        return Internal.Bool ? "true" : "false";
      default:
        return "";
    }
    return "";
  }

  friend std::ostream& operator<<(std::ostream&, const JSON&);

 private:
  void SetType(Class type) {
    if (type == Type) return;

    ClearInternal();

    switch (type) {
      case Class::Null:
        Internal.Map = nullptr;
        break;
      case Class::Object:
        Internal.Map = new map<string, JSON>();
        break;
      case Class::Array:
        Internal.List = new deque<JSON>();
        break;
      case Class::String:
        Internal.String = new string();
        break;
      case Class::Floating:
        Internal.Float = 0.0;
        break;
      case Class::Integral:
        Internal.Int = 0;
        break;
      case Class::Boolean:
        Internal.Bool = false;
        break;
    }

    Type = type;
  }

 private:
  /* beware: only call if YOU know that Internal is allocated. No checks
  performed here. This function should be called in a constructed JSON just
  before you are going to overwrite Internal...
*/
  void ClearInternal() {
    switch (Type) {
      case Class::Object:
        delete Internal.Map;
        break;
      case Class::Array:
        delete Internal.List;
        break;
      case Class::String:
        delete Internal.String;
        break;
      default: {
      };
    }
  }

 private:
  Class Type = Class::Null;
};

JSON Array() { return std::move(JSON::Make(JSON::Class::Array)); }

template <typename... T>
JSON Array(T... args) {
  JSON arr = JSON::Make(JSON::Class::Array);
  arr.append(args...);
  return std::move(arr);
}

JSON Object() { return std::move(JSON::Make(JSON::Class::Object)); }

std::ostream& operator<<(std::ostream& os, const JSON& json) {
  os << json.dump();
  return os;
}

namespace {  // NOLINT
JSON parse_next(const string&, size_t&);

void consume_ws(const string& str, size_t& offset) {  // NOLINT
  while (isspace(str[offset])) ++offset;
}

JSON parse_object(const string& str, size_t& offset) {  // NOLINT
  JSON Object = JSON::Make(JSON::Class::Object);

  ++offset;
  consume_ws(str, offset);
  if (str[offset] == '}') {
    ++offset;
    return std::move(Object);
  }

  while (true) {
    JSON Key = parse_next(str, offset);
    consume_ws(str, offset);
    if (str[offset] != ':') {
      std::cerr << "Error: Object: Expected colon, found '" << str[offset]
                << "'\n";
      break;
    }
    consume_ws(str, ++offset);
    JSON Value = parse_next(str, offset);
    Object[Key.ToString()] = Value;

    consume_ws(str, offset);
    if (str[offset] == ',') {
      ++offset;
      continue;
    } else if (str[offset] == '}') {
      ++offset;
      break;
    } else {
      std::cerr << "ERROR: Object: Expected comma, found '" << str[offset]
                << "'\n";
      break;
    }
  }

  return std::move(Object);
}

JSON parse_array(const string& str, size_t& offset) {  // NOLINT
  JSON Array = JSON::Make(JSON::Class::Array);
  unsigned index = 0;

  ++offset;
  consume_ws(str, offset);
  if (str[offset] == ']') {
    ++offset;
    return std::move(Array);
  }

  while (true) {
    Array[index++] = parse_next(str, offset);
    consume_ws(str, offset);

    if (str[offset] == ',') {
      ++offset;
      continue;
    } else if (str[offset] == ']') {
      ++offset;
      break;
    } else {
      std::cerr << "ERROR: Array: Expected ',' or ']', found '" << str[offset]
                << "'\n";
      return std::move(JSON::Make(JSON::Class::Array));
    }
  }

  return std::move(Array);
}

JSON parse_string(const string& str, size_t& offset) {  // NOLINT
  JSON String;
  string val;
  for (char c = str[++offset]; c != '\"'; c = str[++offset]) {
    if (c == '\\') {
      switch (str[++offset]) {
        case '\"':
          val += '\"';
          break;
        case '\\':
          val += '\\';
          break;
        case '/':
          val += '/';
          break;
        case 'b':
          val += '\b';
          break;
        case 'f':
          val += '\f';
          break;
        case 'n':
          val += '\n';
          break;
        case 'r':
          val += '\r';
          break;
        case 't':
          val += '\t';
          break;
        case 'u': {
          val += "\\u";
          for (unsigned i = 1; i <= 4; ++i) {
            c = str[offset + i];
            if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') ||
                (c >= 'A' && c <= 'F')) {
              val += c;
            } else {
              std::cerr << "ERROR: String: Expected hex character in unicode "
                           "escape, found '"
                        << c << "'\n";
              return std::move(JSON::Make(JSON::Class::String));
            }
          }
          offset += 4;
        } break;
        default:
          val += '\\';
          break;
      }
    } else {
      val += c;
    }
  }
  ++offset;
  String = val;
  return std::move(String);
}

JSON parse_number(const string& str, size_t& offset) {  // NOLINT
  JSON Number;
  string val, exp_str;
  char c;
  bool isDouble = false;
  int exp = 0;
  while (true) {
    c = str[offset++];
    if ((c == '-') || (c >= '0' && c <= '9')) {
      val += c;
    } else if (c == '.') {
      val += c;
      isDouble = true;
    } else {
      break;
    }
  }
  if (c == 'E' || c == 'e') {
    c = str[offset++];
    if (c == '-') {
      ++offset;
      exp_str += '-';
    }
    while (true) {
      c = str[offset++];
      if (c >= '0' && c <= '9') {
        exp_str += c;
      } else if (!isspace(c) && c != ',' && c != ']' && c != '}') {
        std::cerr << "ERROR: Number: Expected a number for exponent, found '"
                  << c << "'\n";
        return std::move(JSON::Make(JSON::Class::Null));
      } else {
        break;
      }
    }
    exp = std::stol(exp_str);
  } else if (!isspace(c) && c != ',' && c != ']' && c != '}') {
    std::cerr << "ERROR: Number: unexpected character '" << c << "'\n";
    return std::move(JSON::Make(JSON::Class::Null));
  }
  --offset;

  if (isDouble) {
    Number = std::stod(val) * std::pow(10, exp);
  } else {
    if (!exp_str.empty())
      Number = std::stol(val) * std::pow(10, exp);
    else
      Number = std::stol(val);
  }
  return std::move(Number);
}

JSON parse_bool(const string& str, size_t& offset) {  // NOLINT
  JSON Bool;
  if (str.substr(offset, 4) == "true") {
    Bool = true;
  } else if (str.substr(offset, 5) == "false") {
    Bool = false;
  } else {
    std::cerr << "ERROR: Bool: Expected 'true' or 'false', found '"
              << str.substr(offset, 5) << "'\n";
    return std::move(JSON::Make(JSON::Class::Null));
  }
  offset += (Bool.ToBool() ? 4 : 5);
  return std::move(Bool);
}

JSON parse_null(const string& str, size_t& offset) {  // NOLINT
  JSON Null;
  if (str.substr(offset, 4) != "null") {
    std::cerr << "ERROR: Null: Expected 'null', found '"
              << str.substr(offset, 4) << "'\n";
    return std::move(JSON::Make(JSON::Class::Null));
  }
  offset += 4;
  return std::move(Null);
}

JSON parse_next(const string& str, size_t& offset) {  // NOLINT
  char value;
  consume_ws(str, offset);
  value = str[offset];
  switch (value) {
    case '[':
      return std::move(parse_array(str, offset));
    case '{':
      return std::move(parse_object(str, offset));
    case '\"':
      return std::move(parse_string(str, offset));
    case 't':
    case 'f':
      return std::move(parse_bool(str, offset));
    case 'n':
      return std::move(parse_null(str, offset));
    default:
      if ((value <= '9' && value >= '0') || value == '-')
        return std::move(parse_number(str, offset));
  }
  std::cerr << "ERROR: Parse: Unknown starting character '" << value << "'\n";
  return JSON();
}
}  // namespace

JSON JSON::Load(const string& str) {
  size_t offset = 0;
  return std::move(parse_next(str, offset));
}

}  // namespace json

#endif  // UTILS_JSON_H_
