/**
 * @file json/decode.c  JSON decoder
 *
 * Copyright (C) 2010 - 2015 Creytiv.com
 */

#include <re_types.h>
#include <re_fmt.h>
#include <re_mem.h>
#include <re_list.h>
#include <re_hash.h>
#include <re_odict.h>
#include <re_json.h>


static inline long double mypower10(uint64_t e)
{
	long double p = 10, n = 1;

	while (e > 0) {
		if (e & 1)
			n *= p;

		p *= p;
		e >>= 1;
	}

	return n;
}


static bool is_string(struct pl *c, const struct pl *pl)
{
	if (pl->l < 2)
		return false;

	if (pl->p[0] != '"'|| pl->p[pl->l-1] != '"')
		return false;

	c->p = pl->p + 1;
	c->l = pl->l - 2;

	return true;
}


static bool is_number(long double *d, bool *isfloat, const struct pl *pl)
{
	bool neg = false, pos = false, frac = false, exp = false;
	long double v = 0, mul = 1;
	const char *p;
	int64_t e = 0;

	if (!pl->l)
		return false;

	p = &pl->p[pl->l];

	while (p > pl->p) {

		const char ch = *--p;

		if (ch == 'e' || ch == 'E') {

			if (exp || frac)
				return false;

			exp = true;
			e   = neg ? -v : v;
			v   = 0;
			mul = 1;
			neg = false;
			pos = false;
		}
		else if (pos || neg) {
			return false;
		}
		else if (ch == '.') {

			if (frac)
				return false;

			frac = true;
			v /= mul;
			mul = 1;
		}
		else if ('0' <= ch && ch <= '9') {
			v += mul * (ch - '0');
			mul *= 10;
		}
		else if (ch == '-') {
			neg = true;
		}
		else if (ch == '+') {
			pos = true;
		}
		else {
			return false;
		}
	}

	*isfloat = (frac || exp);

	if (exp) {
		if (e < 0)
			v /= mypower10(-e);
		else
			v *= mypower10(e);
	}

	if (neg)
		v = -v;

	*d = v;

	return true;
}


static int decode_name(char **str, const struct pl *pl)
{
	struct pl pls;

	if (!pl->p)
		return EBADMSG;

	if (!is_string(&pls, pl))
		return EBADMSG;

	return re_sdprintf(str, "%H", utf8_decode, &pls);
}


static int decode_value(struct json_value *val, const struct pl *pl)
{
	long double dbl;
	struct pl pls;
	bool isfloat;
	int err = 0;

	if (!pl->p)
		return EBADMSG;

	if (is_string(&pls, pl)) {

		err = re_sdprintf(&val->v.str, "%H", utf8_decode, &pls);
		val->type = JSON_STRING;
	}
	else if (is_number(&dbl, &isfloat, pl)) {

		if (isfloat) {
			val->type  = JSON_DOUBLE;
			val->v.dbl = dbl;
		}
		else {
			val->type      = JSON_INT;
			val->v.integer = dbl;
		}
	}
	else if (!pl_strcasecmp(pl, "false")) {

		val->v.boolean = false;
		val->type  = JSON_BOOL;
	}
	else if (!pl_strcasecmp(pl, "true")) {

		val->v.boolean = true;
		val->type  = JSON_BOOL;
	}
	else if (!pl_strcasecmp(pl, "null")) {

		val->type = JSON_NULL;
	}
	else {
		re_printf("json: value of unknown type: <%r>\n", pl);
		err = EBADMSG;
	}

	return err;
}


static int object_entry(const struct pl *pl_name, const struct pl *pl_val,
			json_object_entry_h *oeh, void *arg)
{
	struct json_value val;
	char *name;
	int err;

	err = decode_name(&name, pl_name);
	if (err)
		return err;

	err = decode_value(&val, pl_val);
	if (err)
		goto out;

	if (oeh)
		err = oeh(name, &val, arg);

	if (val.type == JSON_STRING)
		mem_deref(val.v.str);

 out:
	mem_deref(name);

	return err;
}


static int array_entry(unsigned idx, const struct pl *pl_val,
		       json_array_entry_h *aeh, void *arg)
{
	struct json_value val;
	int err;

	err = decode_value(&val, pl_val);
	if (err)
		return err;

	if (aeh)
		err = aeh(idx, &val, arg);

	if (val.type == JSON_STRING)
		mem_deref(val.v.str);

	return err;
}


static int object_start(const struct pl *pl_name, unsigned idx,
			struct json_handlers *h)
{
	char *name = NULL;
	int err = 0;

	if (pl_name->p) {

		err = decode_name(&name, pl_name);
		if (err)
			return err;
	}

	if (h->oh)
		err = h->oh(name, idx, h);

	mem_deref(name);

	return err;
}


static int array_start(const struct pl *pl_name, unsigned idx,
		       struct json_handlers *h)
{
	char *name = NULL;
	int err = 0;

	if (pl_name->p) {

		err = decode_name(&name, pl_name);
		if (err)
			return err;
	}

	if (h->ah)
		err = h->ah(name, idx, h);

	mem_deref(name);

	return err;
}


static inline int chkval(struct pl *val, const char *p)
{
	if (!val->p || p<val->p)
		return EINVAL;

	val->l = p - val->p;

	return 0;
}


static int _json_decode(const char **str, size_t *len,
			unsigned depth, unsigned maxdepth,
			json_object_h *oh, json_array_h *ah,
			json_object_entry_h *oeh, json_array_entry_h *aeh,
			void *arg)
{
	bool esc = false, inquot = false, inobj = false, inarray = false;
	struct pl name = PL_INIT, val = PL_INIT;
	size_t ws = 0;
	unsigned idx = 0;
	int err;

	for (; *len>0; ++(*str), --(*len)) {

		if (inquot) {
			if (esc)
				esc = false;
			else if (**str == '\"')
				inquot = false;
			else if (**str == '\\')
				esc = true;

			continue;
		}

		switch (**str) {

		case ':':
			if (!inobj || name.p || chkval(&val, *str - ws))
				return EBADMSG;

			name = val;
			val  = pl_null;
			break;

		case ',':
			if (chkval(&val, *str - ws))
				break;

			if (inobj) {

				if (!name.p)
					return EBADMSG;

				err = object_entry(&name, &val, oeh, arg);
				if (err)
					return err;
			}
			else if (inarray) {

				err = array_entry(idx, &val, aeh, arg);
				if (err)
					return err;

				++idx;
			}
			else
				return EBADMSG;

			name = pl_null;
			val  = pl_null;
			break;

		case '{':
			if (inobj || inarray) {

				struct json_handlers h = {oh,ah,oeh,aeh,arg};

				if (depth >= maxdepth)
					return EOVERFLOW;

				if (inobj && !name.p)
					return EBADMSG;

				err = object_start(&name, idx, &h);
				if (err)
					return err;

				name = pl_null;

				err = _json_decode(str, len, depth + 1,
						   maxdepth, h.oh, h.ah,
						   h.oeh, h.aeh, h.arg);
				if (err)
					return err;

				if (inarray)
					++idx;
			}
			else {
				inobj = true;
			}
			break;

		case '[':
			if (inobj || inarray) {

				struct json_handlers h = {oh,ah,oeh,aeh,arg};

				if (depth >= maxdepth)
					return EOVERFLOW;

				if (inobj && !name.p)
					return EBADMSG;

				err = array_start(&name, idx, &h);
				if (err)
					return err;

				name = pl_null;

				err = _json_decode(str, len, depth + 1,
						   maxdepth, h.oh, h.ah,
						   h.oeh, h.aeh, h.arg);
				if (err)
					return err;

				if (inarray)
					++idx;
			}
			else {
				inarray = true;
				idx = 0;
			}
			break;

		case '}':
			if (!inobj)
				return EBADMSG;

			if (chkval(&val, *str - ws))
				return 0;

			if (!name.p)
				return EBADMSG;

			return object_entry(&name, &val, oeh, arg);

		case ']':
			if (!inarray)
				return EBADMSG;

			if (chkval(&val, *str - ws))
				return 0;

			return array_entry(idx, &val, aeh, arg);

		case ' ':
		case '\t':
		case '\r':
		case '\n':
			++ws;
			break;

		default:
			if (val.p)
				break;

			if (**str == '\"')
				inquot = true;

			val.p = *str;
			val.l = 0;
			ws = 0;
			break;
		}
	}

	if (inobj || inarray)
		return EBADMSG;

	return 0;
}


int json_decode(const char *str, size_t len, unsigned maxdepth,
		json_object_h *oh, json_array_h *ah,
		json_object_entry_h *oeh, json_array_entry_h *aeh, void *arg)
{
	if (!str)
		return EINVAL;

	return _json_decode(&str, &len, 0, maxdepth, oh, ah, oeh, aeh, arg);
}
