/* $Id$ */
/*
 * Copyright (C) 2013 Teluu Inc. (http://www.teluu.com)
 *
 * This program 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 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
#include <pjlib-util/json.h>
#include <pjlib-util/errno.h>
#include <pjlib-util/scanner.h>
#include <pj/assert.h>
#include <pj/ctype.h>
#include <pj/except.h>
#include <pj/string.h>

#define EL_INIT(p_el, nm, typ)	do { \
				    if (nm) { \
					p_el->name = *nm; \
				    } else { \
					p_el->name.ptr = (char*)""; \
					p_el->name.slen = 0; \
				    } \
				    p_el->type = typ; \
				} while (0)

struct write_state;
struct parse_state;

#define NO_NAME	1

static pj_status_t elem_write(const pj_json_elem *elem,
                              struct write_state *st,
                              unsigned flags);
static pj_json_elem* parse_elem_throw(struct parse_state *st,
                                      pj_json_elem *elem);


PJ_DEF(void) pj_json_elem_null(pj_json_elem *el, pj_str_t *name)
{
    EL_INIT(el, name, PJ_JSON_VAL_NULL);
}

PJ_DEF(void) pj_json_elem_bool(pj_json_elem *el, pj_str_t *name,
                                pj_bool_t val)
{
    EL_INIT(el, name, PJ_JSON_VAL_BOOL);
    el->value.is_true = val;
}

PJ_DEF(void) pj_json_elem_number(pj_json_elem *el, pj_str_t *name,
                                  float val)
{
    EL_INIT(el, name, PJ_JSON_VAL_NUMBER);
    el->value.num = val;
}

PJ_DEF(void) pj_json_elem_string( pj_json_elem *el, pj_str_t *name,
                                  pj_str_t *value)
{
    EL_INIT(el, name, PJ_JSON_VAL_STRING);
    el->value.str = *value;
}

PJ_DEF(void) pj_json_elem_array(pj_json_elem *el, pj_str_t *name)
{
    EL_INIT(el, name, PJ_JSON_VAL_ARRAY);
    pj_list_init(&el->value.children);
}

PJ_DEF(void) pj_json_elem_obj(pj_json_elem *el, pj_str_t *name)
{
    EL_INIT(el, name, PJ_JSON_VAL_OBJ);
    pj_list_init(&el->value.children);
}

PJ_DEF(void) pj_json_elem_add(pj_json_elem *el, pj_json_elem *child)
{
    pj_assert(el->type == PJ_JSON_VAL_OBJ || el->type == PJ_JSON_VAL_ARRAY);
    pj_list_push_back(&el->value.children, child);
}

struct parse_state
{
    pj_pool_t		*pool;
    pj_scanner		 scanner;
    pj_json_err_info 	*err_info;
    pj_cis_t		 float_spec;	/* numbers with dot! */
};

static pj_status_t parse_children(struct parse_state *st,
                                  pj_json_elem *parent)
{
    char end_quote = (parent->type == PJ_JSON_VAL_ARRAY)? ']' : '}';

    pj_scan_get_char(&st->scanner);

    while (*st->scanner.curptr != end_quote) {
	pj_json_elem *child;

	while (*st->scanner.curptr == ',')
	    pj_scan_get_char(&st->scanner);

	if (*st->scanner.curptr == end_quote)
	    break;

	child = parse_elem_throw(st, NULL);
	if (!child)
	    return PJLIB_UTIL_EINJSON;

	pj_json_elem_add(parent, child);
    }

    pj_scan_get_char(&st->scanner);
    return PJ_SUCCESS;
}

/* Return 0 if success or the index of the invalid char in the string */
static unsigned parse_quoted_string(struct parse_state *st,
                                    pj_str_t *output)
{
    pj_str_t token;
    char *op, *ip, *iend;

    pj_scan_get_quote(&st->scanner, '"', '"', &token);

    /* Remove the quote characters */
    token.ptr++;
    token.slen-=2;

    if (pj_strchr(&token, '\\') == NULL) {
	*output = token;
	return 0;
    }

    output->ptr = op = pj_pool_alloc(st->pool, token.slen);

    ip = token.ptr;
    iend = token.ptr + token.slen;

    while (ip != iend) {
	if (*ip == '\\') {
	    ++ip;
	    if (ip==iend) {
		goto on_error;
	    }
	    if (*ip == 'u') {
		ip++;
		if (iend - ip < 4) {
		    ip = iend -1;
		    goto on_error;
		}
		/* Only use the last two hext digits because we're on
		 * ASCII */
		*op++ = (char)(pj_hex_digit_to_val(ip[2]) * 16 +
			       pj_hex_digit_to_val(ip[3]));
		ip += 4;
	    } else if (*ip=='"' || *ip=='\\' || *ip=='/') {
		*op++ = *ip++;
	    } else if (*ip=='b') {
		*op++ = '\b';
		ip++;
	    } else if (*ip=='f') {
		*op++ = '\f';
		ip++;
	    } else if (*ip=='n') {
		*op++ = '\n';
		ip++;
	    } else if (*ip=='r') {
		*op++ = '\r';
		ip++;
	    } else if (*ip=='t') {
		*op++ = '\t';
		ip++;
	    } else {
		goto on_error;
	    }
	} else {
	    *op++ = *ip++;
	}
    }

    output->slen = op - output->ptr;
    return 0;

on_error:
    output->slen = op - output->ptr;
    return (unsigned)(ip - token.ptr);
}

static pj_json_elem* parse_elem_throw(struct parse_state *st,
                                      pj_json_elem *elem)
{
    pj_str_t name = {NULL, 0}, value = {NULL, 0};
    pj_str_t token;

    if (!elem)
	elem = pj_pool_alloc(st->pool, sizeof(*elem));

    /* Parse name */
    if (*st->scanner.curptr == '"') {
	pj_scan_get_char(&st->scanner);
	pj_scan_get_until_ch(&st->scanner, '"', &token);
	pj_scan_get_char(&st->scanner);

	if (*st->scanner.curptr == ':') {
	    pj_scan_get_char(&st->scanner);
	    name = token;
	} else {
	    value = token;
	}
    }

    if (value.slen) {
	/* Element with string value and no name */
	pj_json_elem_string(elem, &name, &value);
	return elem;
    }

    /* Parse value */
    if (pj_cis_match(&st->float_spec, *st->scanner.curptr) ||
	*st->scanner.curptr == '-')
    {
	float val;
	pj_bool_t neg = PJ_FALSE;

	if (*st->scanner.curptr == '-') {
	    pj_scan_get_char(&st->scanner);
	    neg = PJ_TRUE;
	}

	pj_scan_get(&st->scanner, &st->float_spec, &token);
	val = pj_strtof(&token);
	if (neg) val = -val;

	pj_json_elem_number(elem, &name, val);

    } else if (*st->scanner.curptr == '"') {
	unsigned err;
	char *start = st->scanner.curptr;

	err = parse_quoted_string(st, &token);
	if (err) {
	    st->scanner.curptr = start + err;
	    return NULL;
	}

	pj_json_elem_string(elem, &name, &token);

    } else if (pj_isalpha(*st->scanner.curptr)) {

	if (pj_scan_strcmp(&st->scanner, "false", 5)==0) {
	    pj_json_elem_bool(elem, &name, PJ_FALSE);
	    pj_scan_advance_n(&st->scanner, 5, PJ_TRUE);
	} else if (pj_scan_strcmp(&st->scanner, "true", 4)==0) {
	    pj_json_elem_bool(elem, &name, PJ_TRUE);
	    pj_scan_advance_n(&st->scanner, 4, PJ_TRUE);
	} else if (pj_scan_strcmp(&st->scanner, "null", 4)==0) {
	    pj_json_elem_null(elem, &name);
	    pj_scan_advance_n(&st->scanner, 4, PJ_TRUE);
	} else {
	    return NULL;
	}

    } else if (*st->scanner.curptr == '[') {
	pj_json_elem_array(elem, &name);
	if (parse_children(st, elem) != PJ_SUCCESS)
	    return NULL;

    } else if (*st->scanner.curptr == '{') {
	pj_json_elem_obj(elem, &name);
	if (parse_children(st, elem) != PJ_SUCCESS)
	    return NULL;

    } else {
	return NULL;
    }

    return elem;
}

static void on_syntax_error(pj_scanner *scanner)
{
    PJ_UNUSED_ARG(scanner);
    PJ_THROW(11);
}

PJ_DEF(pj_json_elem*) pj_json_parse(pj_pool_t *pool,
                                    char *buffer,
                                    unsigned *size,
                                    pj_json_err_info *err_info)
{
    pj_cis_buf_t cis_buf;
    struct parse_state st;
    pj_json_elem *root;
    PJ_USE_EXCEPTION;

    PJ_ASSERT_RETURN(pool && buffer && size, NULL);

    if (!*size)
	return NULL;

    pj_bzero(&st, sizeof(st));
    st.pool = pool;
    st.err_info = err_info;
    pj_scan_init(&st.scanner, buffer, *size,
                 PJ_SCAN_AUTOSKIP_WS | PJ_SCAN_AUTOSKIP_NEWLINE,
                 &on_syntax_error);
    pj_cis_buf_init(&cis_buf);
    pj_cis_init(&cis_buf, &st.float_spec);
    pj_cis_add_str(&st.float_spec, ".0123456789");

    PJ_TRY {
	root = parse_elem_throw(&st, NULL);
    }
    PJ_CATCH_ANY {
	root = NULL;
    }
    PJ_END

    if (!root && err_info) {
	err_info->line = st.scanner.line;
	err_info->col = pj_scan_get_col(&st.scanner) + 1;
	err_info->err_char = *st.scanner.curptr;
    }

    *size = (unsigned)((buffer + *size) - st.scanner.curptr);

    pj_scan_fini(&st.scanner);

    return root;
}

struct buf_writer_data
{
    char	*pos;
    unsigned	 size;
};

static pj_status_t buf_writer(const char *s,
			      unsigned size,
			      void *user_data)
{
    struct buf_writer_data *buf_data = (struct buf_writer_data*)user_data;
    if (size+1 >= buf_data->size)
	return PJ_ETOOBIG;

    pj_memcpy(buf_data->pos, s, size);
    buf_data->pos += size;
    buf_data->size -= size;

    return PJ_SUCCESS;
}

PJ_DEF(pj_status_t) pj_json_write(const pj_json_elem *elem,
                                  char *buffer, unsigned *size)
{
    struct buf_writer_data buf_data;
    pj_status_t status;

    PJ_ASSERT_RETURN(elem && buffer && size, PJ_EINVAL);

    buf_data.pos = buffer;
    buf_data.size = *size;

    status = pj_json_writef(elem, &buf_writer, &buf_data);
    if (status != PJ_SUCCESS)
	return status;

    *buf_data.pos = '\0';
    *size = (unsigned)(buf_data.pos - buffer);
    return PJ_SUCCESS;
}

#define MAX_INDENT 		100
#ifndef PJ_JSON_NAME_MIN_LEN
#  define PJ_JSON_NAME_MIN_LEN	20
#endif
#define ESC_BUF_LEN		64
#ifndef PJ_JSON_INDENT_SIZE
#  define PJ_JSON_INDENT_SIZE	3
#endif

struct write_state
{
    pj_json_writer	 writer;
    void 		*user_data;
    char		 indent_buf[MAX_INDENT];
    int			 indent;
    char		 space[PJ_JSON_NAME_MIN_LEN];
};

#define CHECK(expr) do { \
			status=expr; if (status!=PJ_SUCCESS) return status; } \
		    while (0)

static pj_status_t write_string_escaped(const pj_str_t *value,
                                        struct write_state *st)
{
    const char *ip = value->ptr;
    const char *iend = value->ptr + value->slen;
    char buf[ESC_BUF_LEN];
    char *op = buf;
    char *oend = buf + ESC_BUF_LEN;
    pj_status_t status;

    while (ip != iend) {
	/* Write to buffer to speedup writing instead of calling
	 * the callback one by one for each character.
	 */
	while (ip != iend && op != oend) {
	    if (oend - op < 2)
		break;

	    if (*ip == '"') {
		*op++ = '\\';
		*op++ = '"';
		ip++;
	    } else if (*ip == '\\') {
		*op++ = '\\';
		*op++ = '\\';
		ip++;
	    } else if (*ip == '/') {
		*op++ = '\\';
		*op++ = '/';
		ip++;
	    } else if (*ip == '\b') {
		*op++ = '\\';
		*op++ = 'b';
		ip++;
	    } else if (*ip == '\f') {
		*op++ = '\\';
		*op++ = 'f';
		ip++;
	    } else if (*ip == '\n') {
		*op++ = '\\';
		*op++ = 'n';
		ip++;
	    } else if (*ip == '\r') {
		*op++ = '\\';
		*op++ = 'r';
		ip++;
	    } else if (*ip == '\t') {
		*op++ = '\\';
		*op++ = 't';
		ip++;
	    } else if ((*ip >= 32 && *ip < 127)) {
		/* unescaped */
		*op++ = *ip++;
	    } else {
		/* escaped */
		if (oend - op < 6)
		    break;
		*op++ = '\\';
		*op++ = 'u';
		*op++ = '0';
		*op++ = '0';
		pj_val_to_hex_digit(*ip, op);
		op+=2;
		ip++;
	    }
	}

	CHECK( st->writer( buf, (unsigned)(op-buf), st->user_data) );
	op = buf;
    }

    return PJ_SUCCESS;
}

static pj_status_t write_children(const pj_json_list *list,
                                  const char quotes[2],
                                  struct write_state *st)
{
    unsigned flags = (quotes[0]=='[') ? NO_NAME : 0;
    pj_status_t status;

    //CHECK( st->writer( st->indent_buf, st->indent, st->user_data) );
    CHECK( st->writer( &quotes[0], 1, st->user_data) );
    CHECK( st->writer( " ", 1, st->user_data) );

    if (!pj_list_empty(list)) {
	pj_bool_t indent_added = PJ_FALSE;
	pj_json_elem *child = list->next;

	if (child->name.slen == 0) {
	    /* Simple list */
	    while (child != (pj_json_elem*)list) {
		status = elem_write(child, st, flags);
		if (status != PJ_SUCCESS)
		    return status;

		if (child->next != (pj_json_elem*)list)
		    CHECK( st->writer( ", ", 2, st->user_data) );
		child = child->next;
	    }
	} else {
	    if (st->indent < sizeof(st->indent_buf)) {
		st->indent += PJ_JSON_INDENT_SIZE;
		indent_added = PJ_TRUE;
	    }
	    CHECK( st->writer( "\n", 1, st->user_data) );
	    while (child != (pj_json_elem*)list) {
		status = elem_write(child, st, flags);
		if (status != PJ_SUCCESS)
		    return status;

		if (child->next != (pj_json_elem*)list)
		    CHECK( st->writer( ",\n", 2, st->user_data) );
		else
		    CHECK( st->writer( "\n", 1, st->user_data) );
		child = child->next;
	    }
	    if (indent_added) {
		st->indent -= PJ_JSON_INDENT_SIZE;
	    }
	    CHECK( st->writer( st->indent_buf, st->indent, st->user_data) );
	}
    }
    CHECK( st->writer( &quotes[1], 1, st->user_data) );

    return PJ_SUCCESS;
}

static pj_status_t elem_write(const pj_json_elem *elem,
                              struct write_state *st,
                              unsigned flags)
{
    pj_status_t status;

    if (elem->name.slen) {
	CHECK( st->writer( st->indent_buf, st->indent, st->user_data) );
	if ((flags & NO_NAME)==0) {
	    CHECK( st->writer( "\"", 1, st->user_data) );
	    CHECK( write_string_escaped(&elem->name, st) );
	    CHECK( st->writer( "\": ", 3, st->user_data) );
	    if (elem->name.slen < PJ_JSON_NAME_MIN_LEN /*&&
		elem->type != PJ_JSON_VAL_OBJ &&
		elem->type != PJ_JSON_VAL_ARRAY*/)
	    {
		CHECK( st->writer( st->space,
		                   (unsigned)(PJ_JSON_NAME_MIN_LEN -
					      elem->name.slen),
				   st->user_data) );
	    }
	}
    }

    switch (elem->type) {
    case PJ_JSON_VAL_NULL:
	CHECK( st->writer( "null", 4, st->user_data) );
	break;
    case PJ_JSON_VAL_BOOL:
	if (elem->value.is_true)
	    CHECK( st->writer( "true", 4, st->user_data) );
	else
	    CHECK( st->writer( "false", 5, st->user_data) );
	break;
    case PJ_JSON_VAL_NUMBER:
	{
	    char num_buf[65];
	    int len;

	    if (elem->value.num == (int)elem->value.num)
		len = pj_ansi_snprintf(num_buf, sizeof(num_buf), "%d",
		                       (int)elem->value.num);
	    else
		len = pj_ansi_snprintf(num_buf, sizeof(num_buf), "%f",
		                       elem->value.num);

	    if (len < 0 || len >= sizeof(num_buf))
		return PJ_ETOOBIG;
	    CHECK( st->writer( num_buf, len, st->user_data) );
	}
	break;
    case PJ_JSON_VAL_STRING:
	CHECK( st->writer( "\"", 1, st->user_data) );
	CHECK( write_string_escaped( &elem->value.str, st) );
	CHECK( st->writer( "\"", 1, st->user_data) );
	break;
    case PJ_JSON_VAL_ARRAY:
	CHECK( write_children(&elem->value.children, "[]", st) );
	break;
    case PJ_JSON_VAL_OBJ:
	CHECK( write_children(&elem->value.children, "{}", st) );
	break;
    default:
	pj_assert(!"Unhandled value type");
    }

    return PJ_SUCCESS;
}

#undef CHECK

PJ_DEF(pj_status_t) pj_json_writef( const pj_json_elem *elem,
                                    pj_json_writer writer,
                                    void *user_data)
{
    struct write_state st;

    PJ_ASSERT_RETURN(elem && writer, PJ_EINVAL);

    st.writer 		= writer;
    st.user_data	= user_data;
    st.indent 		= 0;
    pj_memset(st.indent_buf, ' ', MAX_INDENT);
    pj_memset(st.space, ' ', PJ_JSON_NAME_MIN_LEN);

    return elem_write(elem, &st, 0);
}

