/*
 * Copyright (c) 2009 Petri Lehtinen <petri@digip.org>
 *
 * Jansson is free software; you can redistribute it and/or modify
 * it under the terms of the MIT license. See LICENSE for details.
 */


#include "jansson.h"
#include "hashtable.h"
#include "jansson_private.h"
#include "utf.h"
#include "util.h"

#define container_of(ptr_, type_, member_)  \
    ((type_ *)((char *)ptr_ - (size_t)&((type_ *)0)->member_))

typedef struct {
	json_t                          json;
	hashtable_t                     hashtable;
} json_object_t;

typedef struct {
	json_t                          json;
	unsigned int                    size;
	unsigned int                    entries;
	json_t                        **table;
} json_array_t;

typedef struct {
	json_t                          json;
	char                           *value;
} json_string_t;

typedef struct {
	json_t                          json;
	double                          value;
} json_real_t;

typedef struct {
	json_t                          json;
	int                             value;
} json_integer_t;

#define json_to_object(json_)  container_of(json_, json_object_t, json)
#define json_to_array(json_)   container_of(json_, json_array_t, json)
#define json_to_string(json_)  container_of(json_, json_string_t, json)
#define json_to_real(json_)   container_of(json_, json_real_t, json)
#define json_to_integer(json_) container_of(json_, json_integer_t, json)

static inline void
json_init (json_t * json, json_type type)
{
	json->type = type;
	json->refcount = 1;
}


/*** object ***/

static unsigned int
hash_string (const void *key)
{
	const char                     *str = (const char *)key;
	unsigned int                    hash = 5381;
	unsigned int                    c;

	while ((c = (unsigned int)*str)) {
		hash = ((hash << 5) + hash) + c;
		str++;
	}

	return hash;
}

static int
string_equal (const void *key1, const void *key2)
{
	return strcmp ((const char *)key1, (const char *)key2) == 0;
}

static void
value_decref (void *value)
{
	json_decref ((json_t *) value);
}

json_t                         *
json_object (void)
{
	json_object_t                  *object = g_malloc (sizeof (json_object_t));
	if (!object)
		return NULL;
	json_init (&object->json, JSON_OBJECT);

	if (hashtable_init (&object->hashtable, hash_string, string_equal, g_free, value_decref)) {
		g_free (object);
		return NULL;
	}
	return &object->json;
}

static void
json_delete_object (json_object_t * object)
{
	hashtable_close (&object->hashtable);
	g_free (object);
}

json_t                         *
json_object_get (const json_t * json, const char *key)
{
	json_object_t                  *object;

	if (!json_is_object (json))
		return NULL;

	object = json_to_object (json);
	return hashtable_get (&object->hashtable, key);
}

int
json_object_set_new_nocheck (json_t * json, const char *key, json_t * value)
{
	json_object_t                  *object;

	if (!key || !value)
		return -1;

	if (!json_is_object (json)) {
		json_decref (value);
		return -1;
	}
	object = json_to_object (json);

	if (hashtable_set (&object->hashtable, strdup (key), value)) {
		json_decref (value);
		return -1;
	}

	return 0;
}

int
json_object_set_nocheck (json_t * json, const char *key, json_t * value)
{
	return json_object_set_new_nocheck (json, key, json_incref (value));
}

int
json_object_set_new (json_t * json, const char *key, json_t * value)
{
	if (!utf8_check_string (key, -1)) {
		json_decref (value);
		return -1;
	}

	return json_object_set_new_nocheck (json, key, value);
}

int
json_object_del (json_t * json, const char *key)
{
	json_object_t                  *object;

	if (!json_is_object (json))
		return -1;

	object = json_to_object (json);
	return hashtable_del (&object->hashtable, key);
}

void                           *
json_object_iter (json_t * json)
{
	json_object_t                  *object;

	if (!json_is_object (json))
		return NULL;

	object = json_to_object (json);
	return hashtable_iter (&object->hashtable);
}

void                           *
json_object_iter_next (json_t * json, void *iter)
{
	json_object_t                  *object;

	if (!json_is_object (json) || iter == NULL)
		return NULL;

	object = json_to_object (json);
	return hashtable_iter_next (&object->hashtable, iter);
}

const char                     *
json_object_iter_key (void *iter)
{
	if (!iter)
		return NULL;

	return (const char *)hashtable_iter_key (iter);
}

json_t                         *
json_object_iter_value (void *iter)
{
	if (!iter)
		return NULL;

	return (json_t *) hashtable_iter_value (iter);
}


/*** array ***/

json_t                         *
json_array (void)
{
	json_array_t                   *array = g_malloc (sizeof (json_array_t));
	if (!array)
		return NULL;
	json_init (&array->json, JSON_ARRAY);

	array->entries = 0;
	array->size = 0;
	array->table = NULL;

	return &array->json;
}

static void
json_delete_array (json_array_t * array)
{
	unsigned int                    i;

	for (i = 0; i < array->entries; i++)
		json_decref (array->table[i]);

	g_free (array->table);
	g_free (array);
}

unsigned int
json_array_size (const json_t * json)
{
	if (!json_is_array (json))
		return 0;

	return json_to_array (json)->entries;
}

json_t                         *
json_array_get (const json_t * json, unsigned int index)
{
	json_array_t                   *array;
	if (!json_is_array (json))
		return NULL;
	array = json_to_array (json);

	if (index >= array->entries)
		return NULL;

	return array->table[index];
}

int
json_array_set_new (json_t * json, unsigned int index, json_t * value)
{
	json_array_t                   *array;

	if (!value)
		return -1;

	if (!json_is_array (json)) {
		json_decref (value);
		return -1;
	}
	array = json_to_array (json);

	if (index >= array->entries) {
		json_decref (value);
		return -1;
	}

	json_decref (array->table[index]);
	array->table[index] = value;

	return 0;
}

int
json_array_append_new (json_t * json, json_t * value)
{
	json_array_t                   *array;

	if (!value)
		return -1;

	if (!json_is_array (json)) {
		json_decref (value);
		return -1;
	}
	array = json_to_array (json);

	if (array->entries == array->size) {
		array->size = max (8, array->size * 2);
		array->table = realloc (array->table, array->size * sizeof (json_t *));
		if (!array->table) {
			json_decref (value);
			return -1;
		}
	}

	array->table[array->entries] = value;
	array->entries++;

	return 0;
}


/*** string ***/

json_t                         *
json_string_nocheck (const char *value)
{
	json_string_t                  *string;

	if (!value)
		return NULL;

	string = g_malloc (sizeof (json_string_t));
	if (!string)
		return NULL;
	json_init (&string->json, JSON_STRING);

	string->value = strdup (value);
	if (!string->value) {
		g_free (string);
		return NULL;
	}

	return &string->json;
}

json_t                         *
json_string (const char *value)
{
	if (!value || !utf8_check_string (value, -1))
		return NULL;

	return json_string_nocheck (value);
}

const char                     *
json_string_value (const json_t * json)
{
	if (!json_is_string (json))
		return NULL;

	return json_to_string (json)->value;
}

static void
json_delete_string (json_string_t * string)
{
	g_free (string->value);
	g_free (string);
}


/*** integer ***/

json_t                         *
json_integer (int value)
{
	json_integer_t                 *integer = g_malloc (sizeof (json_integer_t));
	if (!integer)
		return NULL;
	json_init (&integer->json, JSON_INTEGER);

	integer->value = value;
	return &integer->json;
}

int
json_integer_value (const json_t * json)
{
	if (!json_is_integer (json))
		return 0;

	return json_to_integer (json)->value;
}

static void
json_delete_integer (json_integer_t * integer)
{
	g_free (integer);
}


/*** real ***/

json_t                         *
json_real (double value)
{
	json_real_t                    *real = g_malloc (sizeof (json_real_t));
	if (!real)
		return NULL;
	json_init (&real->json, JSON_REAL);

	real->value = value;
	return &real->json;
}

double
json_real_value (const json_t * json)
{
	if (!json_is_real (json))
		return 0;

	return json_to_real (json)->value;
}

static void
json_delete_real (json_real_t * real)
{
	g_free (real);
}


/*** number ***/

double
json_number_value (const json_t * json)
{
	if (json_is_integer (json))
		return json_integer_value (json);
	else if (json_is_real (json))
		return json_real_value (json);
	else
		return 0.0;
}


/*** simple values ***/

json_t                         *
json_true (void)
{
	static json_t                   the_true = {
		.type = JSON_TRUE,
		.refcount = 1
	};
	return json_incref (&the_true);
}


json_t                         *
json_false (void)
{
	static json_t                   the_false = {
		.type = JSON_FALSE,
		.refcount = 1
	};
	return json_incref (&the_false);
}


json_t                         *
json_null (void)
{
	static json_t                   the_null = {
		.type = JSON_NULL,
		.refcount = 1
	};
	return json_incref (&the_null);
}


/*** deletion ***/

void
json_delete (json_t * json)
{
	if (json_is_object (json))
		json_delete_object (json_to_object (json));

	else if (json_is_array (json))
		json_delete_array (json_to_array (json));

	else if (json_is_string (json))
		json_delete_string (json_to_string (json));

	else if (json_is_integer (json))
		json_delete_integer (json_to_integer (json));

	else if (json_is_real (json))
		json_delete_real (json_to_real (json));

	/* json_delete is not called for true, false or null */
}
