/***************************************************************************
  
  c_json.c
  
  (c) Benoît Minisini <benoit.minisini@gambas-basic.org>

  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, 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., 51 Franklin Street, Fifth Floor, Boston,
  MA 02110-1301, USA.
  
***************************************************************************/

#define __C_HASH_C

#include "main.h"
#include "c_json.h"

//-------------------------------------------------------------------------

static char _null;
static char _undefined;

static char *_str = NULL;
static int _pos = 0;
static int _len = 0;
static const char *_error = NULL;
static bool _use_null = FALSE;
static char _quote;

static bool read_value(GB_VARIANT_VALUE *value);

//-------------------------------------------------------------------------

static bool raise_error(const char *msg)
{
	_error = msg;
	return TRUE;
}

static bool end_of_string()
{
	return raise_error("End of string");
}

//-------------------------------------------------------------------------

#define is_letter(_car) (((_car) >= 'A' && (_car) <= 'Z') || ((_car) >= 'a' && (_car) <= 'z'))
#define is_digit(_car) ((_car) >= '0' && (_car) <= '9')

#define read_hexa(_car) (is_digit(_car) ? ((_car) - '0') : ((_car) >= 'A' && (_car) <= 'F') ? ((_car) - 'A' + 10) : ((_car) >= 'a' && (_car) <= 'f') ? ((_car) - 'a' + 10) : -1)

#define check_eof() \
	if (_pos >= _len) return end_of_string(); \

#define ignore_spaces() \
{ \
	for(;;) \
	{ \
		check_eof(); \
		if (_str[_pos] > 32) \
			break; \
		_pos++; \
	} \
}

#define read_char(_var) \
{ \
	check_eof(); \
	_var = _str[_pos++]; \
}

static bool read_token(char **token, int *len)
{
	char c;
	
	ignore_spaces();
	
	*token = &_str[_pos];
	*len = 1;
	
	read_char(c);
	if (!is_letter(c))
		return FALSE;
	
	while (_pos < _len)
	{
		if (!is_letter(_str[_pos]))
			return FALSE;
		_pos++;
		(*len)++;
	}
	
	return FALSE;
}

static int read_unicode(const char *str)
{
	uint code = 0;
	int d;
	
	d = read_hexa(str[0]);
	if (d < 0)
		return -1;
	code += d << 12;

	d = read_hexa(str[1]);
	if (d < 0)
		return -1;
	code += d << 8;

	d = read_hexa(str[2]);
	if (d < 0)
		return -1;
	code += d << 4;
	
	d = read_hexa(str[3]);
	if (d < 0)
		return -1;
	code += d;
	
	return code;
}

static bool read_string_len(char **pstr, int *plen)
{
	char *result = NULL;
	char c;
	char buffer[8];
	int len;
	int code;
	
	for(;;)
	{
		if (_pos >= _len)
		{
			GB.FreeString(&result);
			return raise_error("Non terminated string");
		}
		
		c = _str[_pos++];
		if (c == '"')
			break;
		if (c == '\\')
		{
			c = _str[_pos++];
			if (c == 'n')
				c = '\n';
			else if (c == 't')
				c = '\t';
			else if (c == '\\')
				c = '\\';
			else if (c == '/')
				c = '/';
			else if (c == '"')
				c = '"';
			else if (c == 'r')
				c = '\r';
			else if (c == 'f')
				c = '\f';
			else if (c == 'b')
				c = '\b';
			else if (c == 'u')
			{
				if (_pos >= (_len - 4))
				{
					GB.FreeString(&result);
					return raise_error("Non terminated string");
				}
				
				code = read_unicode(&_str[_pos]);
				if (code < 0)
				{
					GB.FreeString(&result);
					return raise_error("Bad unicode character at position &1");
				}
				
				_pos += 4;
				
				len = GB.FromUnicode((uint)code, buffer);
				result = GB.AddString(result, buffer, len);
				continue;
			}
			else
			{
				GB.FreeString(&result);
				return raise_error("Bad escape character at position &1");
			}
		}
		result = GB.AddChar(result, c);
	}
	
	*pstr = result;
	*plen = GB.StringLength(result);
	return FALSE;
}

static bool read_string(GB_VARIANT_VALUE *value)
{
	char *str;
	int len;
	
	if (read_string_len(&str, &len))
		return TRUE;
	
	GB.FreeStringLater(str);
	
	if (len == 0 && _use_null)
	{
		value->type = GB_T_POINTER;
		value->value._pointer = (intptr_t)&_null;
	}
	else
	{
		value->type = GB_T_STRING;
		value->value._string = str;
	}
	
	return FALSE;
}

static bool read_object(GB_VARIANT_VALUE *value)
{
	GB_COLLECTION col;
	bool comma = FALSE;
	char *key;
	int len;
	GB_VARIANT elt;
	
	if (_use_null)
		col = GB.New(GB.FindClass("JSONCollection"), NULL, NULL);
	else
		GB.Collection.New(&col, GB_COMP_BINARY);
	
	for(;;)
	{
		ignore_spaces();
		
		if (_pos >= _len)
		{
			GB.Unref(POINTER(&col));
			return end_of_string();
		}
			
		if (_str[_pos] == '}')
		{
			_pos++;
			value->type = GB.GetClass(col);
			value->value._object = col;
			return FALSE;
		}
		
		if (comma)
		{
			if (_str[_pos] != ',')
			{
				GB.Unref(POINTER(&col));
				return raise_error("Comma expected at position &1");
			}
			_pos++;
		}
		else
			comma = TRUE;
		
		ignore_spaces();
		
		if (_str[_pos++] != '"')
		{
			GB.Unref(POINTER(&col));
			return raise_error("String expected at position &1");
		}
		
		if (read_string_len(&key, &len))
			return TRUE;
		
		ignore_spaces();
		
		if (_str[_pos++] != ':')
		{
			GB.Unref(POINTER(&col));
			return raise_error("Colon expected at position &1");
		}
		
		if (read_value(&elt.value))
		{
			GB.Unref(POINTER(&col));
			return TRUE;
		}
		
		elt.type = GB_T_VARIANT;
		GB.Collection.Set(col, key, len, &elt);
		GB.FreeString(&key);
	}
}

static bool read_array(GB_VARIANT_VALUE *value)
{
	GB_ARRAY array = NULL;
	GB_VARIANT elt;
	bool comma = FALSE;
	
	GB.Array.New(&array, GB_T_VARIANT, 0);
	
	for(;;)
	{
		ignore_spaces();
		
		if (_pos >= _len)
		{
			GB.Unref(POINTER(&array));
			return end_of_string();
		}
			
		if (_str[_pos] == ']')
		{
			_pos++;
			value->type = GB.GetClass(array);
			value->value._object = array;
			return FALSE;
		}
		
		if (comma)
		{
			if (_str[_pos] != ',')
			{
				GB.Unref(POINTER(&array));
				return raise_error("Comma expected at position &1");
			}
			_pos++;
		}
		else
			comma = TRUE;
		
		if (read_value(&elt.value))
		{
			GB.Unref(POINTER(&array));
			return TRUE;
		}
		
		elt.type = GB_T_VARIANT;
		GB.StoreVariant(&elt, GB.Array.Add(array));
	}
}

static bool read_number(GB_VARIANT_VALUE *value)
{
	GB_VALUE number;
	char c;
	int pos;
	
	pos = _pos - 1;
	
	while (_pos < _len)
	{
		c = _str[_pos];
		if (!is_digit(c) && c != '+' && c != '-' && c != 'E' && c != 'e' && c != '.')
			break;
		_pos++;
	}
	
	if (!GB.NumberFromString(GB_NB_READ_ALL, &_str[pos], _pos - pos, &number))
	{
		value->type = number.type;
	
		if (number.type == GB_T_INTEGER)
		{
			value->value._integer = number._integer.value;
			return FALSE;
		}
		else if (number.type == GB_T_LONG)
		{
			value->value._long = number._long.value;
			return FALSE;
		}
		else if (number.type == GB_T_FLOAT)
		{
			value->value._float = number._float.value;
			return FALSE;
		}
	}

	return raise_error("Incorrect number at position &1");
}

static bool read_value(GB_VARIANT_VALUE *value)
{
	char *token;
	int len;
	char c;
	
	if (read_token(&token, &len))
		return TRUE;
	
	if (len == 1)
	{
		c = *token;
		
		if (c == '{')
			return read_object(value);
		else if (c == '[')
			return read_array(value);
		else if (c == '"')
			return read_string(value);
		else if (c == '-' || is_digit(c))
			return read_number(value);
	}
	else if (len == 4)
	{
		if (strncmp(token, "true", 4) == 0)
		{
			value->type = GB_T_BOOLEAN;
			value->value._boolean = -1;
			return FALSE;
		}
		else if (strncmp(token, "null", 4) == 0)
		{
			if (_use_null)
			{
				value->type = GB_T_POINTER;
				value->value._pointer = (intptr_t)&_null;
			}
			else
			{
				value->type = GB_T_NULL;
			}
			return FALSE;
		}
	}
	else if (len == 5)
	{
		if (strncmp(token, "false", 4) == 0)
		{
			value->type = GB_T_BOOLEAN;
			value->value._boolean = 0;
			return FALSE;
		}
	}
	else if (len == 9)
	{
		if (_use_null && strncmp(token, "undefined", 9) == 0)
		{
			value->type = GB_T_POINTER;
			value->value._pointer = (intptr_t)&_undefined;
			return FALSE;
		}
	}
	
	return raise_error("Bad token at position &1");
}

BEGIN_METHOD(Json_FromString, GB_STRING value; GB_BOOLEAN use_null)

	GB_VARIANT_VALUE ret;
	char buffer[16];
	
	_str = STRING(value);
	_len = LENGTH(value);
	_pos = 0;
	_use_null = VARGOPT(use_null, FALSE);
	
	if (_len == 0)
	{
		GB.ReturnNull();
		return;
	}
	
	if (!read_value(&ret))
	{
		GB.ReturnVariant(&ret);
		return;
	}
	
	sprintf(buffer, "%d", _pos);
	GB.Error(_error, buffer);

END_METHOD

//-------------------------------------------------------------------------

#define add_result_len(__str, __len) (_str = GB.AddString(_str, (__str), (__len)))
#define add_result(__str) add_result_len((__str), strlen(__str))
#define add_char(__c) (_str = GB.AddChar(_str, (__c)))

static void write_string(const char *str, int len)
{
	char buffer[8];
	int i;
	unsigned char c;
	
	add_char(_quote);
	
	for (i = 0; i < len; i++)
	{
		c = str[i];
		
		if (c == _quote)
		{
			add_char('\\');
			add_char(_quote);
		}
		else if (c == '\\')
			add_result_len("\\\\", 2);
		else if (c == '\n')
			add_result_len("\\n", 2);
		else if (c == '\t')
			add_result_len("\\t", 2);
		else if (c == '\r')
			add_result_len("\\r", 2);
		else if (c == '\f')
			add_result_len("\\f", 2);
		else if (c == '\b')
			add_result_len("\\b", 2);
		else if (c < 32)
		{
			sprintf(buffer, "\\u00%02x", c);
			add_result_len(buffer, 6);
		}
		else
			add_char(c);
	}
	add_char(_quote);
}

static void write_value(GB_VALUE *value)
{
	char *str;
	int len;
	GB_DATE_SERIAL *serial;
	char buffer[64];
	int i;
	GB_VALUE elt;
	
	if (value->type == GB_T_VARIANT)
		GB.Conv(value, value->_variant.value.type);
	
	if (value->type >= GB_T_OBJECT)
	{
		if (!value->_object.value)
		{
			add_result("null");
		}
		else if (GB.Is(value->_object.value, CLASS_Array))
		{
			GB_ARRAY array = value->_object.value;
			
			add_char('[');
			
			for (i = 0; i < GB.Array.Count(array); i++)
			{
				if (i) add_char(',');
				elt.type = GB.Array.Type(array);
				GB.ReadValue(&elt, GB.Array.Get(array, i), elt.type);
				GB.BorrowValue(&elt);
				write_value(&elt);
			}
			
			add_char(']');
		}
		else if (GB.Is(value->_object.value, CLASS_Collection))
		{
			GB_COLLECTION col = value->_object.value;
			char *key;
			int len;
			GB_COLLECTION_ITER iter;
			bool comma = FALSE;
			
			add_char('{');
					
			GB.Collection.Enum(col, &iter, NULL, NULL, NULL);
			for(;;)
			{
				if (GB.Collection.Enum(col, &iter, (GB_VARIANT *)&elt, &key, &len))
					break;
				
				if (comma)
					add_char(',');
				else
					comma = TRUE;
				
				write_string(key, len);
				add_char(':');
				GB.BorrowValue(&elt);
				write_value(&elt);
			}
			
			add_char('}');
		}
		else
			add_result("undefined");
	}
	else
	{
		switch (value->type)
		{
			case GB_T_NULL:
				add_result("null");
				break;
				
			case GB_T_BOOLEAN:
				add_result(value->_boolean.value ? "true" : "false");
				break;
				
			case GB_T_BYTE:
			case GB_T_SHORT:
			case GB_T_INTEGER:
				add_result(GB.IntegerToString(value->_integer.value));
				break;

			case GB_T_LONG:
				add_result(GB.IntegerToString(value->_long.value));
				break;
				
			case GB_T_SINGLE:
				GB.NumberToString(FALSE, value->_single.value, NULL, &str, &len);
				add_result_len(str, len);
				break;
				
			case GB_T_FLOAT:
				GB.NumberToString(FALSE, value->_float.value, NULL, &str, &len);
				add_result_len(str, len);
				break;
				
			case GB_T_DATE:
				serial = GB.SplitDate(&value->_date, FALSE);
				sprintf(buffer, "\"%04d-%02d-%02dT%02d:%02d:%02d", serial->year, serial->month, serial->day, serial->hour, serial->min, serial->sec);
				add_result_len(buffer, 20);
				if (serial->msec)
				{
					sprintf(buffer, ".%03d", serial->msec);
					add_result_len(buffer, 3);
				}
				add_result_len("Z\"", 2);
				break;
				
			case GB_T_STRING:
			case GB_T_CSTRING:
				write_string(value->_string.value.addr + value->_string.value.start, value->_string.value.len);
				break;
				
			case GB_T_POINTER:
				if (value->_pointer.value == (intptr_t)&_null)
					add_result("null");
				else
					add_result("undefined");
				break;
				
			default:
				add_result("undefined");
		}
	}
	
	GB.ReleaseValue(value);
}

BEGIN_METHOD(Json_ToString, GB_VARIANT json)

	GB_VALUE *value = (GB_VALUE *)ARG(json);
	
	_str = NULL;
	_quote = '"';
	_error = NULL;
	
	GB.BorrowValue(value);
	write_value(value);
	
	GB.FreeStringLater(_str);
	GB.ReturnString(_str);

END_METHOD

BEGIN_METHOD(JS_call, GB_VARIANT value)

	GB_VALUE *value = (GB_VALUE *)ARG(value);
	
	_str = NULL;
	_quote = '\'';
	
	GB.BorrowValue(value);
	write_value(value);
	
	GB.FreeStringLater(_str);
	GB.ReturnString(_str);

END_METHOD

//-------------------------------------------------------------------------

BEGIN_PROPERTY(Json_Null)

	GB.ReturnPointer(&_null);

END_PROPERTY

BEGIN_PROPERTY(Json_Undefined)

	GB.ReturnPointer(&_undefined);

END_PROPERTY

//-------------------------------------------------------------------------

GB_DESC JsonDesc[] =
{
	GB_DECLARE_STATIC("JSON"),

	GB_STATIC_METHOD("FromString", "v", Json_FromString, "(Value)s[(UseNull)b]"),
	GB_STATIC_METHOD("Decode", "v", Json_FromString, "(Value)s[(UseNull)b]"),
	GB_STATIC_METHOD("ToString", "s", Json_ToString, "(Json)v"),
	GB_STATIC_METHOD("Encode", "s", Json_ToString, "(Json)v"),
	
	GB_STATIC_PROPERTY_READ("Null", "p", Json_Null),
	GB_STATIC_PROPERTY_READ("Undefined", "p", Json_Undefined),

	GB_END_DECLARE
};

GB_DESC JSDesc[] =
{
	GB_DECLARE_STATIC("JS"),
	
	GB_STATIC_METHOD("_call", "s", JS_call, "(Value)v"),
	
	GB_END_DECLARE
};
