#include "json_object.h"

#include <fstream>
#include <sstream>

#include "rapidjson/stringbuffer.h"
#include "rapidjson/allocators.h"
#include "rapidjson/writer.h"
#include "rapidjson/prettywriter.h"

#include "utils/Log.h"

namespace base {

std::string rapidjson_to_string(const rapidjson::Document& doc) {
  using namespace rapidjson;
  StringBuffer buffer;
  Writer<StringBuffer> writer(buffer);
  doc.Accept(writer);
  return buffer.GetString();
}
std::string rapidjson_to_pretty_string(const rapidjson::Document& doc) {
  using namespace rapidjson;
  StringBuffer buffer;
  PrettyWriter<StringBuffer> writer(buffer);
  doc.Accept(writer);
  return buffer.GetString();
}


JSONObject::JSONObject() {
  doc_.SetObject();
}

JSONObject::~JSONObject() {
}


JSONObject::JSONObject(const rapidjson::Value& value) {
  doc_.SetObject();
  doc_.CopyFrom(value, doc_.GetAllocator());
}

JSONObject::JSONObject(const rapidjson::Value::Object& obj) {
  using namespace rapidjson;
  doc_.SetObject();
  for (Value::MemberIterator it = obj.MemberBegin(); it != obj.MemberEnd();
      ++it) {
    doc_.AddMember(Value(it->name, doc_.GetAllocator()).Move(),
        Value(it->value, doc_.GetAllocator()).Move(), doc_.GetAllocator());
  }
}

JSONObject::JSONObject(const rapidjson::Value::ConstObject& obj) {
  using namespace rapidjson;
  doc_.SetObject();
  for (Value::ConstMemberIterator it = obj.MemberBegin(); it != obj.MemberEnd();
      ++it) {
    doc_.AddMember(Value(it->name, doc_.GetAllocator()).Move(),
        Value(it->value, doc_.GetAllocator()).Move(), doc_.GetAllocator());
  }
}

JSONObject::JSONObject(JSONObject&& obj) {
  doc_.SetObject();
  doc_.Swap(obj.doc_);
}

JSONObject::JSONObject(const JSONObject& obj) {
  doc_.SetObject();
  doc_.CopyFrom(obj.doc_, doc_.GetAllocator());
}

JSONObject& JSONObject::Put(const std::string& name, int32_t v) {
  using namespace rapidjson;
  doc_.AddMember(Value(name.c_str(), doc_.GetAllocator()).Move(),
      Value().SetInt(v), doc_.GetAllocator());
  return *this;
}

JSONObject& JSONObject::Put(const std::string& name, uint32_t v) {
  using namespace rapidjson;
  doc_.AddMember(Value(name.c_str(), doc_.GetAllocator()).Move(),
      Value().SetUint(v), doc_.GetAllocator());
  return *this;
}

JSONObject& JSONObject::Put(const std::string& name, int64_t v) {
  using namespace rapidjson;
  doc_.AddMember(Value(name.c_str(), doc_.GetAllocator()).Move(),
      Value().SetInt64(v), doc_.GetAllocator());
  return *this;
}

JSONObject& JSONObject::Put(const std::string& name, uint64_t v) {
  using namespace rapidjson;
  doc_.AddMember(Value(name.c_str(), doc_.GetAllocator()).Move(),
      Value().SetUint64(v), doc_.GetAllocator());
  return *this;
}

JSONObject& JSONObject::Put(const std::string& name, const std::string& v) {
  using namespace rapidjson;
  doc_.AddMember(Value(name.c_str(), doc_.GetAllocator()).Move(),
      Value().SetString(v.c_str(), doc_.GetAllocator()), doc_.GetAllocator());
  return *this;
}

JSONObject& JSONObject::Put(const std::string& name, const JSONObject& v) {
  using namespace rapidjson;
  doc_.AddMember(Value(name.c_str(), doc_.GetAllocator()).Move(),
      Value(v.doc_, doc_.GetAllocator()).Move(), doc_.GetAllocator());
  return *this;
}

JSONObject& JSONObject::Put(const std::string& name, const JSONArray& v) {
  using namespace rapidjson;
  doc_.AddMember(Value(name.c_str(), doc_.GetAllocator()).Move(),
      Value(v.doc_, doc_.GetAllocator()).Move(), doc_.GetAllocator());
  return *this;
}

bool JSONObject::ParseObject(const std::string& json_str) {
  doc_.Parse(json_str.c_str());
  return !doc_.HasParseError() && doc_.IsObject();
}

bool JSONObject::ParseObjectFromFile(const std::string& path) {
  std::ifstream ifs(path.c_str(), std::ios::in | std::ios::binary);
  if (!ifs.is_open()) {
    return false;
  }
  std::stringstream ss;
  ss << ifs.rdbuf();
  ifs.close();
  return ParseObject(ss.str());
}

std::string JSONObject::ToString() const {
  return rapidjson_to_string(doc_);
}

std::string JSONObject::ToPrettyString() const {
  return rapidjson_to_pretty_string(doc_);
}

/**
 * if not found, throw std::string
 */
int JSONObject::GetInt(const std::string& name) {
  if (doc_.HasMember(name.c_str())) {
    return doc_[name.c_str()].GetInt();
  }
  throw name;
}

/**
 * if not found, throw std::string
 */
uint32_t JSONObject::GetUint(const std::string& name) {
  if (doc_.HasMember(name.c_str())) {
    return doc_[name.c_str()].GetUint();
  }
  throw name;
}

uint32_t JSONObject::GetUint(const std::string& name, uint32_t default_value) {
  if (doc_.HasMember(name.c_str())) {
    return doc_[name.c_str()].GetUint();
  }
  return default_value;
}

int JSONObject::GetInt(const std::string& name, int default_value) {
  if (doc_.HasMember(name.c_str()) && doc_[name].IsInt()) {
    return doc_[name.c_str()].GetInt();
  }
  return default_value;
}

int64_t JSONObject::GetInt64(const std::string& name, int64_t default_value) {
  if (doc_.HasMember(name.c_str())) {
    return doc_[name.c_str()].GetInt64();
  }
  return default_value;
}

/**
 * if not found, throw std::string
 */
std::string JSONObject::GetString(const std::string& name) {
  if (doc_.HasMember(name.c_str())) {
    return doc_[name.c_str()].GetString();
  }
  throw name;
}

std::string JSONObject::GetString(const std::string& name,
    const std::string& default_value) {
  if (doc_.HasMember(name.c_str()) && doc_[name].IsString()) {
    return doc_[name.c_str()].GetString();
  }
  return default_value;
}

bool JSONObject::GetBool(const std::string& name, bool default_value) {
  if (doc_.HasMember(name) && doc_[name].IsBool()) {
    return doc_[name].GetBool();
  }
  return default_value;
}

/**
 * if not found, return empty array
 */
JSONArray JSONObject::GetArray(const std::string& name) const {
  if (doc_.HasMember(name.c_str()) && doc_[name.c_str()].IsArray()
      && !doc_[name].IsNull()) {
    return (doc_[name.c_str()].GetArray());
  }
  JSONArray arr;
  return arr;
}

/**
 * if not found, return empty object
 */
JSONObject JSONObject::GetObject(const std::string& name) {
  if (doc_.HasMember(name) && doc_[name].IsObject() && !doc_[name].IsNull()) {
    return doc_[name];
  }
  JSONObject obj;
  return obj;
}

}
