/*
 * Copyright (c) 2013 Pavel Shramov <shramov@mexmat.net>
 *
 * json2pb is free software; you can redistribute it and/or modify
 * it under the terms of the MIT license. See LICENSE for details.
 */

#include <errno.h>
#include <jansson.h>

// Backport json_boolean if needed (for jansson <2.4)
// This macro comes from the Jansson docs, and is thus
// Copyright (c) 2009-2014 Petri Lehtinen <petri@digip.org>, distributed under
// the MIT license.
#ifndef json_boolean
#define json_boolean(val) ((val) ? json_true() : json_false())
#endif

#include <google/protobuf/message.h>
#include <google/protobuf/descriptor.h>
#include <google/protobuf/util/json_util.h>
#include <google/protobuf/struct.pb.h>

#include <json2pb.h>

#include <stdexcept>
#include <cstdio>

namespace {
#include "bin2ascii.h"
}

using google::protobuf::Message;
using google::protobuf::MessageFactory;
using google::protobuf::Descriptor;
using google::protobuf::FieldDescriptor;
using google::protobuf::EnumDescriptor;
using google::protobuf::EnumValueDescriptor;
using google::protobuf::Reflection;

struct json_autoptr {
	json_t * ptr;
	json_autoptr(json_t *json) : ptr(json) {}
	~json_autoptr() { if (ptr) json_decref(ptr); }
	json_t * release() { json_t *tmp = ptr; ptr = 0; return tmp; }
};

class j2pb_error : public std::exception {
	std::string _error;
public:
	j2pb_error(const std::string &e) : _error(e) {}
	j2pb_error(const FieldDescriptor *field, const std::string &e) : _error(field->name() + ": " + e) {}
	virtual ~j2pb_error() throw() {};

	virtual const char *what() const throw () { return _error.c_str(); };
};

static json_t * _pb2json(const Message& msg);
static json_t * _struct2json(const google::protobuf::Struct& msg);
static json_t * _field2json(const Message& msg, const FieldDescriptor *field, size_t index)
{
	const Reflection *ref = msg.GetReflection();
	const bool repeated = field->is_repeated();
	json_t *jf = 0;
	switch (field->cpp_type())
	{
#define _CONVERT(type, ctype, fmt, sfunc, afunc)		\
		case FieldDescriptor::type: {			\
			const ctype value = (repeated)?		\
				ref->afunc(msg, field, index):	\
				ref->sfunc(msg, field);		\
			jf = fmt(value);			\
			break;					\
		}
        
#define _CONVERT_AS_STRING(type, ctype, sfunc, afunc)		\
		case FieldDescriptor::type: {			\
			const ctype value = (repeated)?		\
				ref->afunc(msg, field, index):	\
				ref->sfunc(msg, field);		\
            std::string quoted = std::to_string(value); \
			jf = json_string(quoted.c_str());			\
			break;					\
		}

		_CONVERT(CPPTYPE_DOUBLE, double, json_real, GetDouble, GetRepeatedDouble);
		_CONVERT(CPPTYPE_FLOAT, double, json_real, GetFloat, GetRepeatedFloat);
		_CONVERT_AS_STRING(CPPTYPE_INT64, json_int_t, GetInt64, GetRepeatedInt64);
		_CONVERT_AS_STRING(CPPTYPE_UINT64, json_int_t, GetUInt64, GetRepeatedUInt64);
		_CONVERT(CPPTYPE_INT32, json_int_t, json_integer, GetInt32, GetRepeatedInt32);
		_CONVERT(CPPTYPE_UINT32, json_int_t, json_integer, GetUInt32, GetRepeatedUInt32);
		_CONVERT(CPPTYPE_BOOL, bool, json_boolean, GetBool, GetRepeatedBool);
#undef _CONVERT_AS_STRING
#undef _CONVERT
		case FieldDescriptor::CPPTYPE_STRING: {
			std::string scratch;
			const std::string &value = (repeated)?
				ref->GetRepeatedStringReference(msg, field, index, &scratch):
				ref->GetStringReference(msg, field, &scratch);
			if (field->type() == FieldDescriptor::TYPE_BYTES)
				jf = json_string(b64_encode(value).c_str());
			else
				jf = json_string(value.c_str());
			break;
		}
		case FieldDescriptor::CPPTYPE_MESSAGE: {
			const Message& mf = (repeated)?
				ref->GetRepeatedMessage(msg, field, index):
				ref->GetMessage(msg, field);
            if (mf.GetDescriptor()->full_name() == google::protobuf::Struct::descriptor()->full_name()) {
                jf = _struct2json((const google::protobuf::Struct&) mf);
            } else {
			    jf = _pb2json(mf);
            }
			break;
		}
		case FieldDescriptor::CPPTYPE_ENUM: {
			const EnumValueDescriptor* ef = (repeated)?
				ref->GetRepeatedEnum(msg, field, index):
				ref->GetEnum(msg, field);

			jf = json_integer(ef->number());
			break;
		}
		default:
			break;
	}
	if (!jf) throw j2pb_error(field, "Fail to convert to json");
	return jf;
}

static json_t * _pb2json(const Message& msg)
{
	const Descriptor *d = msg.GetDescriptor();
	const Reflection *ref = msg.GetReflection();
	if (!d || !ref) return 0;

	json_t *root = json_object();
	json_autoptr _auto(root);

    //std::cerr << msg.DebugString() << std::endl;

	std::vector<const FieldDescriptor *> fields;
	ref->ListFields(msg, &fields);

	for (size_t i = 0; i != fields.size(); i++)
	{
		const FieldDescriptor *field = fields[i];
		json_t *jf = 0;
		if(field->is_repeated()) {
			size_t count = ref->FieldSize(msg, field);
			if (!count) continue;

			json_autoptr array(json_array());
			for (size_t j = 0; j < count; j++)
				json_array_append_new(array.ptr, _field2json(msg, field, j));
			jf = array.release();
		} else if (ref->HasField(msg, field))
			jf = _field2json(msg, field, 0);
		else
			continue;

		const std::string &name = (field->is_extension())?field->full_name():field->name();
		json_object_set_new(root, name.c_str(), jf);
	}
	return _auto.release();
}

static json_t * _struct2json(const google::protobuf::Struct& msg) {

    // Cheat by making Protobuf serialize the struct instead of us having to walk it.
    std::string buffer;
    google::protobuf::util::JsonPrintOptions opts;
    auto status = google::protobuf::util::MessageToJsonString(msg, &buffer, opts);
    
    if (!status.ok()) {
        throw std::runtime_error("Could not serialize " + msg.GetTypeName() + ": " + status.ToString());
    }
    
    // Now parse back
    json_t *root;
	json_error_t error;
	root = json_loadb(buffer.c_str(), buffer.size(), 0, &error);
    
    // No need to keep the buffer around after the JSON is parsed, which is good because it is a local.

	if (!root)
		throw j2pb_error(std::string("Load failed: ") + error.text);

	json_autoptr _auto(root);

	if (!json_is_object(root))
		throw j2pb_error("Malformed JSON: not an object");

    return _auto.release();
}

static bool string2bool(const char* str) {
    // If the string is empty, *str == 0.
    return (*str == 't' || *str == 'T' || *str == 'y' || *str == 'Y' || (*str >= '1' && *str <= '9')); 
}

static void _json2pb(Message& msg, json_t *root);
static void _json2struct(google::protobuf::Struct& msg, json_t *root);
static void _json2field(Message &msg, const FieldDescriptor *field, json_t *jf)
{
	const Reflection *ref = msg.GetReflection();
	const bool repeated = field->is_repeated();
	json_error_t error;

	switch (field->cpp_type())
	{
#define _SET_OR_ADD(sfunc, afunc, value)			\
		do {						\
			if (repeated)				\
				ref->afunc(&msg, field, value);	\
			else					\
				ref->sfunc(&msg, field, value);	\
		} while (0)

#define _CONVERT_WITH_STRING(type, ctype, fmt, fromstringfunc, sfunc, afunc) 		\
		case FieldDescriptor::type: {			\
			ctype value;				\
			int r = json_unpack_ex(jf, &error, JSON_STRICT, fmt, &value); \
            if (r) { \
                if (!json_is_string(jf)) throw j2pb_error(field, std::string("Failed to unpack or view as string: ") + error.text); \
			    const char * string_value = json_string_value(jf); \
                value = fromstringfunc(string_value); \
            } \
			_SET_OR_ADD(sfunc, afunc, value);	\
			break;					\
		}

		_CONVERT_WITH_STRING(CPPTYPE_DOUBLE, double, "F", atof, SetDouble, AddDouble);
		_CONVERT_WITH_STRING(CPPTYPE_FLOAT, double, "F", atof, SetFloat, AddFloat);
		_CONVERT_WITH_STRING(CPPTYPE_INT64, json_int_t, "I", std::stoll, SetInt64, AddInt64);
		_CONVERT_WITH_STRING(CPPTYPE_UINT64, json_int_t, "I", std::stoull, SetUInt64, AddUInt64);
		_CONVERT_WITH_STRING(CPPTYPE_INT32, json_int_t, "I", atoi, SetInt32, AddInt32);
		_CONVERT_WITH_STRING(CPPTYPE_UINT32, json_int_t, "I", std::stoul, SetUInt32, AddUInt32);
		_CONVERT_WITH_STRING(CPPTYPE_BOOL, int, "b", string2bool, SetBool, AddBool);

		case FieldDescriptor::CPPTYPE_STRING: {
			if (!json_is_string(jf))
				throw j2pb_error(field, "Not a string");
			const char * value = json_string_value(jf);
			if(field->type() == FieldDescriptor::TYPE_BYTES)
				_SET_OR_ADD(SetString, AddString, b64_decode(value));
			else
				_SET_OR_ADD(SetString, AddString, value);
			break;
		}
		case FieldDescriptor::CPPTYPE_MESSAGE: {
			Message *mf = (repeated)?
				ref->AddMessage(&msg, field):
				ref->MutableMessage(&msg, field);
            if (mf->GetDescriptor()->full_name() == google::protobuf::Struct::descriptor()->full_name()) {
                _json2struct((google::protobuf::Struct&) *mf, jf);
            } else {
			    _json2pb(*mf, jf);
            }
			break;
		}
		case FieldDescriptor::CPPTYPE_ENUM: {
			const EnumDescriptor *ed = field->enum_type();
			const EnumValueDescriptor *ev = 0;
			if (json_is_integer(jf)) {
				ev = ed->FindValueByNumber(json_integer_value(jf));
			} else if (json_is_string(jf)) {
				ev = ed->FindValueByName(json_string_value(jf));
			} else
				throw j2pb_error(field, "Not an integer or string");
			if (!ev)
				throw j2pb_error(field, "Enum value not found");
			_SET_OR_ADD(SetEnum, AddEnum, ev);
			break;
		}
		default:
			break;
            
#undef _CONVERT_WITH_STRING
#undef _SET_OR_ADD
	}
}

static void _json2pb(Message& msg, json_t *root)
{
	const Descriptor *d = msg.GetDescriptor();
	const Reflection *ref = msg.GetReflection();
	if (!d || !ref) throw j2pb_error("No descriptor or reflection");

	for (void *i = json_object_iter(root); i; i = json_object_iter_next(root, i))
	{
		const char *name = json_object_iter_key(i);
		json_t *jf = json_object_iter_value(i);

		const FieldDescriptor *field = d->FindFieldByName(name);
		if (!field)
			field = ref->FindKnownExtensionByName(name);
			//field = d->file()->FindExtensionByName(name);

		if (!field) throw j2pb_error("Unknown field: " + std::string(name));

		int r = 0;
		if (field->is_repeated()) {
			if (!json_is_array(jf))
				throw j2pb_error(field, "Not array");
			for (size_t j = 0; j < json_array_size(jf); j++)
				_json2field(msg, field, json_array_get(jf, j));
		} else
			_json2field(msg, field, jf);
	}
}

static int json_dump_std_string(const char *buf, size_t size, void *data)
{
	std::string *s = (std::string *) data;
	s->append(buf, size);
	return 0;
}

static void _json2struct(google::protobuf::Struct& msg, json_t *root)
{
    // Don't take ownership of the root

    // Serialize the JSON to a string again
    std::string buf;
	json_dump_callback(root, json_dump_std_string, &buf, 0);
    
    // Parse it as a struct
    auto status = google::protobuf::util::JsonStringToMessage(buf, &msg);
    if (!status.ok()) {
        throw std::runtime_error("Could not deserialize " + msg.GetTypeName() + ": " + status.ToString());
    }
}

void json2pb(Message &msg, const char *buf, size_t size)
{
	json_t *root;
	json_error_t error;

	root = json_loadb(buf, size, 0, &error);

	if (!root)
		throw j2pb_error(std::string("Load failed: ") + error.text);

	json_autoptr _auto(root);

	if (!json_is_object(root))
		throw j2pb_error("Malformed JSON: not an object");

	_json2pb(msg, root);
}

void json2pb(Message &msg, FILE *fp)
{
    json_t *root;
	json_error_t error;

	root = json_loadf(fp, JSON_DISABLE_EOF_CHECK, &error);

	if (!root)
		throw j2pb_error(std::string("Load failed: ") + error.text);

	json_autoptr _auto(root);

	if (!json_is_object(root))
		throw j2pb_error("Malformed JSON: not an object");

	_json2pb(msg, root);
}

void json2pb(Message &msg, const std::string& data)
{
    json2pb(msg, data.c_str(), data.size());
}

std::string pb2json(const Message &msg)
{
	std::string r;

	json_t *root = _pb2json(msg);
	json_autoptr _auto(root);
	json_dump_callback(root, json_dump_std_string, &r, JSON_SORT_KEYS);
	return r;
}
