/*
 *
 * json_writer.c
 *
 * pgpool: a language independent connection pool server for PostgreSQL
 * written by Tatsuo Ishii
 *
 * Copyright (c) 2003-2015	PgPool Global Development Group
 *
 * Permission to use, copy, modify, and distribute this software and
 * its documentation for any purpose and without fee is hereby
 * granted, provided that the above copyright notice appear in all
 * copies and that both that copyright notice and this permission
 * notice appear in supporting documentation, and that the name of the
 * author not be used in advertising or publicity pertaining to
 * distribution of the software without specific, written prior
 * permission. The author makes no representations about the
 * suitability of this software for any purpose.  It is provided "as
 * is" without express or implied warranty.
 *
 */


#include <stdio.h>
#include "parser/stringinfo.h"
#include "utils/palloc.h"
#include "utils/json_writer.h"

static void jw_put_string_escape(JsonNode *jNode, char *string);
static inline int jw_get_current_element_count(JsonNode *jNode);
static inline void jw_inc_current_element_count(JsonNode *jNode);
static inline JWElementType jw_get_current_element_type(JsonNode *jNode);


JsonNode *
jw_create(JWElementType rootElement, bool pretty_output)
{
	JsonNode   *jNode = palloc(sizeof(JsonNode));

	jNode->buf = makeStringInfo();
	jNode->stack_ptr = 0;
	jNode->pretty = pretty_output;
	jw_start_element(jNode, rootElement, NULL);
	return jNode;
}

JsonNode *
jw_create_with_array(bool pretty_output)
{
	return jw_create(JWARRAY, pretty_output);
}

JsonNode *
jw_create_with_object(bool pretty_output)
{
	return jw_create(JWOBJECT, pretty_output);
}

bool
jw_put_string(JsonNode *jNode, char *key, char *value)
{
	if (key == NULL || value == NULL)
		return false;
	if (jw_get_current_element_count(jNode) < 0)
		return false;
	if (jw_get_current_element_type(jNode) != JWOBJECT)
		return false;

	if (jw_get_current_element_count(jNode) > 0)
		appendStringInfoChar(jNode->buf, ',');
	jw_put_string_escape(jNode, key);
	appendStringInfoChar(jNode->buf, ':');
	jw_put_string_escape(jNode, value);
	jw_inc_current_element_count(jNode);
	return true;
}

static void
jw_put_string_escape(JsonNode *jNode, char *string)
{
	int			i;

	appendStringInfoChar(jNode->buf, '"');
	for (i = 0; string[i] != '\0'; i++)
	{
		switch (string[i])
		{
			case '\"':
				appendStringInfo(jNode->buf, "\\\"");
				break;
			case '\\':
				appendStringInfo(jNode->buf, "\\\\");
				break;
			case '\b':
				appendStringInfo(jNode->buf, "\\b");
				break;
			case '\f':
				appendStringInfo(jNode->buf, "\\f");
				break;
			case '\n':
				appendStringInfo(jNode->buf, "\\n");
				break;
			case '\r':
				appendStringInfo(jNode->buf, "\\r");
				break;
			case '\t':
				appendStringInfo(jNode->buf, "\\t");
				break;
			default:
				appendStringInfoChar(jNode->buf, string[i]);
				break;
		}
	}
	appendStringInfoChar(jNode->buf, '"');
}

/* for compatibility reasons we pack bool in int*/
bool
jw_put_bool(JsonNode *jNode, char *key, bool value)
{
	return jw_put_int(jNode, key, value ? 1 : 0);
}

bool
jw_put_int(JsonNode *jNode, char *key, int value)
{
	if (key == NULL)
		return false;
	if (jw_get_current_element_count(jNode) < 0)
		return false;
	if (jw_get_current_element_type(jNode) != JWOBJECT)
		return false;

	if (jw_get_current_element_count(jNode) > 0)
		appendStringInfoChar(jNode->buf, ',');
	appendStringInfo(jNode->buf, "\"%s\":%d", key, value);
	jw_inc_current_element_count(jNode);
	return true;
}

bool
jw_put_long(JsonNode *jNode, char *key, long value)
{
	if (key == NULL)
		return false;
	if (jw_get_current_element_count(jNode) < 0)
		return false;
	if (jw_get_current_element_type(jNode) != JWOBJECT)
		return false;

	if (jw_get_current_element_count(jNode) > 0)
		appendStringInfoChar(jNode->buf, ',');
	appendStringInfo(jNode->buf, "\"%s\":%ld", key, value);
	jw_inc_current_element_count(jNode);
	return true;
}

bool
jw_put_null(JsonNode *jNode, char *key)
{
	if (key == NULL)
		return false;
	if (jw_get_current_element_count(jNode) < 0)
		return false;
	if (jw_get_current_element_type(jNode) != JWOBJECT)
		return false;

	if (jw_get_current_element_count(jNode) > 0)
		appendStringInfoChar(jNode->buf, ',');
	appendStringInfo(jNode->buf, "\"%s\":null", key);
	jw_inc_current_element_count(jNode);
	return true;
}

bool
jw_put_string_value(JsonNode *jNode, char *value)
{
	if (value == NULL)
		return false;
	if (jw_get_current_element_count(jNode) < 0)
		return false;
	if (jw_get_current_element_type(jNode) != JWARRAY)
		return false;

	if (jw_get_current_element_count(jNode) > 0)
		appendStringInfoChar(jNode->buf, ',');
	appendStringInfo(jNode->buf, "\"%s\"", value);
	jw_inc_current_element_count(jNode);
	return true;
}

bool
jw_put_bool_value(JsonNode *jNode, bool value)
{
	return jw_put_int_value(jNode, value ? 1 : 0);
}

bool
jw_put_int_value(JsonNode *jNode, int value)
{
	if (jw_get_current_element_count(jNode) < 0)
		return false;
	if (jw_get_current_element_type(jNode) != JWARRAY)
		return false;

	if (jw_get_current_element_count(jNode) > 0)
		appendStringInfoChar(jNode->buf, ',');
	appendStringInfo(jNode->buf, "%d", value);
	jw_inc_current_element_count(jNode);
	return true;
}

bool
jw_put_long_value(JsonNode *jNode, long value)
{
	if (jw_get_current_element_count(jNode) < 0)
		return false;
	if (jw_get_current_element_type(jNode) != JWARRAY)
		return false;

	if (jw_get_current_element_count(jNode) > 0)
		appendStringInfoChar(jNode->buf, ',');
	appendStringInfo(jNode->buf, "%ld", value);
	jw_inc_current_element_count(jNode);
	return true;
}

bool
jw_put_null_value(JsonNode *jNode)
{
	if (jw_get_current_element_count(jNode) < 0)
		return false;
	if (jw_get_current_element_type(jNode) != JWARRAY)
		return false;

	if (jw_get_current_element_count(jNode) > 0)
		appendStringInfoChar(jNode->buf, ',');
	appendStringInfoString(jNode->buf, "null");
	jw_inc_current_element_count(jNode);
	return true;
}

bool
jw_start_element(JsonNode *jNode, JWElementType element, char *key)
{
	char		ch;

	if (jNode->stack_ptr >= MAX_STACK_DEPTH)
		return false;

	if (jw_get_current_element_count(jNode) > 0)
		appendStringInfoChar(jNode->buf, ',');

	if (element == JWOBJECT)
	{
		ch = '{';
	}
	else if (element == JWARRAY)
	{
		ch = '[';
	}
	else
		return false;

	if (key == NULL)
		appendStringInfoChar(jNode->buf, ch);
	else
		appendStringInfo(jNode->buf, "\"%s\": %c", key, ch);

	jw_inc_current_element_count(jNode);

	jNode->stack[jNode->stack_ptr].elementCount = 0;
	jNode->stack[jNode->stack_ptr++].elementType = element;
	return true;
}

bool
jw_start_array(JsonNode *jNode, char *key)
{
	if (jw_get_current_element_type(jNode) == JWARRAY)
		return jw_start_element(jNode, JWARRAY, NULL);
	return jw_start_element(jNode, JWARRAY, key);
}

bool
jw_start_object(JsonNode *jNode, char *key)
{
	if (jw_get_current_element_type(jNode) == JWARRAY)
		return jw_start_element(jNode, JWOBJECT, NULL);
	return jw_start_element(jNode, JWOBJECT, key);
}

bool
jw_end_element(JsonNode *jNode)
{
	char		ch;
	JWElementType element;

	if (jNode->stack_ptr <= 0)
		return false;
	/* pop the element from stack */
	element = jNode->stack[--jNode->stack_ptr].elementType;
	if (element == JWOBJECT)
		ch = '}';
	else if (element == JWARRAY)
		ch = ']';
	else
		return false;
	appendStringInfoChar(jNode->buf, ch);
	return true;
}

bool
jw_finish_document(JsonNode *jNode)
{
	while (jNode->stack_ptr > 0)
	{
		if (!jw_end_element(jNode))
			return false;
	}
	return true;
}

char *
jw_get_json_string(JsonNode *jNode)
{
	return jNode->buf->data;
}

int
jw_get_json_length(JsonNode *jNode)
{
	return jNode->buf->len;
}

void
jw_destroy(JsonNode *jNode)
{
	pfree(jNode->buf->data);
	pfree(jNode->buf);
	pfree(jNode);
}

static inline int
jw_get_current_element_count(JsonNode *jNode)
{
	if (jNode->stack_ptr <= 0)
		return -1;
	return jNode->stack[jNode->stack_ptr - 1].elementCount;
}

static inline void
jw_inc_current_element_count(JsonNode *jNode)
{
	if (jNode->stack_ptr <= 0)
		return;
	jNode->stack[jNode->stack_ptr - 1].elementCount++;
}

static inline JWElementType
jw_get_current_element_type(JsonNode *jNode)
{
	if (jNode->stack_ptr <= 0)
		return -1;
	return jNode->stack[jNode->stack_ptr - 1].elementType;
}
