/*
 * This file is part of sh4lt.
 *
 * sh4lt is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * sh4lt is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with sh4lt.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "./json-serializer.hpp"
#include "../jsoncpp/json/json.h"
#include "../utils/scope-exit.hpp"
#include <cstdint> // int64_t

namespace sh4lt::infotree::json {

auto any_to_json_value(const Any& any) -> Json::Value {
  switch (any.get_category()) {
    case AnyCategory::BOOLEAN:
      return any.copy_as<bool>();
      break;
    case AnyCategory::INTEGRAL:
      return any.copy_as<int64_t>();
      break;
    case AnyCategory::FLOATING_POINT:
      return any.copy_as<double>();
      break;
    case AnyCategory::OTHER:
      return Any::to_string(any);
      break;
    case AnyCategory::NONE:
      return Json::nullValue;
      break;
  }
  return Json::nullValue;
}

auto tree_to_json_value(InfoTree::ptrc tree) -> Json::Value;

auto array_to_json_value(InfoTree::ptrc tree) -> Json::Value {
  Json::Value object = Json::arrayValue;
  // could be optimized with an new method in InfoTree "get_childrens"
  auto child_keys = tree->get_child_keys(".");
  for (const auto& it: child_keys) {
    object.append(tree_to_json_value(InfoTree::get_subtree(tree, it))); 
  }
  return object;
}

auto tree_to_json_value(InfoTree::ptrc tree) -> Json::Value {
  if (tree->is_leaf()) {
    return any_to_json_value(tree->read_data());
  } else if (tree->is_array()) {
    return array_to_json_value(tree);
  } 
  // if not leaf or array, it is an object
  Json::Value object;
  // could be optimized with an new method in InfoTree "get_childrens"
  auto child_keys = tree->get_child_keys(".");
  for (const auto& it: child_keys) {
    object[it] = tree_to_json_value(InfoTree::get_subtree(tree, it)); 
  }
  return object;
}

auto serialize(InfoTree::ptrc tree) -> std::string {
  Json::StreamWriterBuilder builder;
  return Json::writeString(builder, tree_to_json_value(tree));
}

auto json_value_to_tree(const Json::Value& val) -> InfoTree::ptr {
  if(val.isNull()) {
    return InfoTree::make();
  } else if(val.isBool()) {
    return InfoTree::make(val.asBool());
  } else if(val.isInt()) {
    return InfoTree::make(val.asInt());
  } else if(val.isInt64()) {
    return InfoTree::make(val.asInt64());
  } else if(val.isUInt()) {
    return InfoTree::make(val.asUInt());
  } else if(val.isUInt64()) {
    return InfoTree::make(val.asUInt64());
  } else if(val.isDouble()) {
    return InfoTree::make(val.asDouble());
  } else if(val.isString()) {
    return InfoTree::make(val.asString());
  } else if(val.isObject()) {
    Json::Value::const_iterator itEnd = val.end();
    auto res = InfoTree::make();
    for (Json::Value::const_iterator it = val.begin(); it != itEnd; ++it) {
      res->graft(it.name(), json_value_to_tree(*it));
    }
    return res;
  } else if (val.isArray()) {
    Json::Value::const_iterator itEnd = val.end();
    auto res = InfoTree::make();
    unsigned int count = 0;
    for (Json::Value::const_iterator it = val.begin(); it != itEnd; ++it) {
      res->graft(std::to_string(count), json_value_to_tree(*it));
      ++count;
    }
    res->tag_as_array(true);
    return res;
  }
  // not reachable
  return InfoTree::make();
}

auto deserialize(const std::string& json_string) -> InfoTree::ptr {
  JSONCPP_STRING err;
  Json::CharReaderBuilder builder;
  Json::Value root;
  const std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
  if (!reader->parse(json_string.c_str(),
                     json_string.c_str() + json_string.length(), &root, &err)) {
    return nullptr;
  }
  return json_value_to_tree(root);
}

auto is_valid(const std::string& json_string) -> BoolLog {
  JSONCPP_STRING err;
  Json::CharReaderBuilder builder;
  Json::Value root;
  const std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
  if (!reader->parse(json_string.c_str(),
                     json_string.c_str() + json_string.length(),
                     &root,
                     &err)) {
    return {false, err};
  }
  return {true};
}

}  // namespace sh4lt
