/*
Copyright (C) 2022 The University of Notre Dame
This software is distributed under the GNU General Public License.
See the file COPYING for details.
*/

#include "jx.h"
#include "buffer.h"
#include "stringtools.h"
#include "xxmalloc.h"

#include <assert.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct jx_pair *jx_pair(struct jx *key, struct jx *value, struct jx_pair *next)
{
	struct jx_pair *pair = calloc(1, sizeof(*pair));
	pair->key = key;
	pair->value = value;
	pair->next = next;
	return pair;
}

struct jx_item *jx_item(struct jx *value, struct jx_item *next)
{
	struct jx_item *item = calloc(1, sizeof(*item));
	item->value = value;
	item->next = next;
	return item;
}

struct jx_comprehension *jx_comprehension(const char *variable, struct jx *elements, struct jx *condition, struct jx_comprehension *next)
{
	assert(variable);
	assert(elements);
	struct jx_comprehension *comp = calloc(1, sizeof(*comp));
	comp->variable = strdup(variable);
	comp->elements = elements;
	comp->condition = condition;
	comp->next = next;
	return comp;
}

static struct jx *jx_create(jx_type_t type)
{
	struct jx *j = xxcalloc(1, sizeof(*j));
	j->type = type;
	return j;
}

struct jx *jx_null()
{
	return jx_create(JX_NULL);
}

struct jx *jx_symbol(const char *symbol_name)
{
	struct jx *j = jx_create(JX_SYMBOL);
	j->u.symbol_name = strdup(symbol_name);
	return j;
}

struct jx *jx_string(const char *string_value)
{
	assert(string_value);
	return jx_string_nocopy(strdup(string_value));
}

struct jx *jx_string_nocopy(char *string_value)
{
	struct jx *j = jx_create(JX_STRING);
	j->u.string_value = string_value;
	return j;
}

struct jx *jx_format(const char *fmt, ...)
{
	va_list va;
	struct jx *j;
	buffer_t B[1];
	char *str;

	buffer_init(B);
	buffer_abortonfailure(B, 1);
	va_start(va, fmt);
	buffer_putvfstring(B, fmt, va);
	va_end(va);
	buffer_dup(B, &str);
	buffer_free(B);

	j = jx_create(JX_STRING);
	j->u.string_value = str;

	return j;
}

struct jx *jx_integer(jx_int_t integer_value)
{
	struct jx *j = jx_create(JX_INTEGER);
	j->u.integer_value = integer_value;
	return j;
}

struct jx *jx_double(double double_value)
{
	struct jx *j = jx_create(JX_DOUBLE);
	j->u.double_value = double_value;
	return j;
}

struct jx *jx_boolean(int boolean_value)
{
	struct jx *j = jx_create(JX_BOOLEAN);
	j->u.boolean_value = !!boolean_value;
	return j;
}

struct jx *jx_object(struct jx_pair *pairs)
{
	struct jx *j = jx_create(JX_OBJECT);
	j->u.pairs = pairs;
	return j;
}

struct jx *jx_objectv(const char *key, struct jx *value, ...)
{
	va_list args;
	va_start(args, value);

	struct jx *object = jx_object(0);

	while (key) {
		assert(value);
		jx_insert(object, jx_string(key), value);
		key = va_arg(args, char *);
		value = va_arg(args, struct jx *);
	}

	va_end(args);
	return object;
}

struct jx *jx_array(struct jx_item *items)
{
	struct jx *j = jx_create(JX_ARRAY);
	j->u.items = items;
	return j;
}

struct jx *jx_operator(jx_operator_t type, struct jx *left, struct jx *right)
{
	struct jx *j = jx_create(JX_OPERATOR);
	j->u.oper.type = type;
	j->u.oper.left = left;
	j->u.oper.right = right;
	return j;
}

struct jx *jx_error(struct jx *err)
{
	if (!err)
		return NULL;
	struct jx *j = jx_create(JX_ERROR);
	j->u.err = err;
	return j;
}

struct jx *jx_arrayv(struct jx *value, ...)
{
	va_list args;
	va_start(args, value);

	struct jx *array = jx_array(0);
	while (value) {
		jx_array_append(array, value);
		value = va_arg(args, struct jx *);
	}

	va_end(args);
	return array;
}

struct jx *jx_lookup_guard(struct jx *j, const char *key, int *found)
{
	struct jx_pair *p;

	if (found)
		*found = 0;

	if (!j || j->type != JX_OBJECT)
		return 0;

	for (p = j->u.pairs; p; p = p->next) {
		if (p && p->key && p->key->type == JX_STRING) {
			if (!strcmp(p->key->u.string_value, key)) {
				if (found)
					*found = 1;
				return p->value;
			}
		}
	}

	return 0;
}

struct jx *jx_lookup(struct jx *j, const char *key)
{
	return jx_lookup_guard(j, key, NULL);
}

const char *jx_lookup_string(struct jx *object, const char *key)
{
	struct jx *j = jx_lookup(object, key);
	if (j && jx_istype(j, JX_STRING)) {
		return j->u.string_value;
	} else {
		return 0;
	}
}

char *jx_lookup_string_dup(struct jx *object, const char *key)
{
	const char *str = jx_lookup_string(object, key);
	if (str) {
		return strdup(str);
	} else {
		return 0;
	}
}

jx_int_t jx_lookup_integer(struct jx *object, const char *key)
{
	struct jx *j = jx_lookup(object, key);
	if (j && jx_istype(j, JX_INTEGER)) {
		return j->u.integer_value;
	} else {
		return 0;
	}
}

int jx_lookup_boolean(struct jx *object, const char *key)
{
	struct jx *j = jx_lookup(object, key);
	if (j && jx_istype(j, JX_BOOLEAN)) {
		return !!j->u.boolean_value;
	} else {
		return 0;
	}
}

double jx_lookup_double(struct jx *object, const char *key)
{
	struct jx *j = jx_lookup(object, key);
	if (j && jx_istype(j, JX_DOUBLE)) {
		return j->u.double_value;
	} else {
		return 0;
	}
}

struct jx *jx_remove(struct jx *object, struct jx *key)
{
	if (!object || object->type != JX_OBJECT)
		return 0;

	struct jx_pair *p;
	struct jx_pair *last = 0;

	for (p = object->u.pairs; p; p = p->next) {
		if (jx_equals(key, p->key)) {
			struct jx *value = p->value;
			if (last) {
				last->next = p->next;
			} else {
				object->u.pairs = p->next;
			}
			p->value = 0;
			p->next = 0;
			jx_pair_delete(p);
			return value;
		}
		last = p;
	}

	return 0;
}

int jx_insert(struct jx *j, struct jx *key, struct jx *value)
{
	if (!j || j->type != JX_OBJECT)
		return 0;
	j->u.pairs = jx_pair(key, value, j->u.pairs);
	return 1;
}

int jx_insert_unless_empty(struct jx *object, struct jx *key, struct jx *value)
{
	switch (value->type) {
	case JX_OBJECT:
	case JX_ARRAY:
		/* C99 says union members have the same start address, so
		 * just pick one, they're both pointers. */
		if (value->u.pairs == NULL) {
			jx_delete(key);
			jx_delete(value);
			return -1;
		} else {
			return jx_insert(object, key, value);
		}
		break;
	default:
		return jx_insert(object, key, value);
		break;
	}
}

void jx_insert_boolean(struct jx *j, const char *key, int value)
{
	jx_insert(j, jx_string(key), jx_boolean(value));
}

void jx_insert_integer(struct jx *j, const char *key, jx_int_t value)
{
	jx_insert(j, jx_string(key), jx_integer(value));
}

void jx_insert_double(struct jx *j, const char *key, double value)
{
	jx_insert(j, jx_string(key), jx_double(value));
}

void jx_insert_string(struct jx *j, const char *key, const char *value)
{
	jx_insert(j, jx_string(key), jx_string(value));
}

void jx_array_insert(struct jx *array, struct jx *value)
{
	array->u.items = jx_item(value, array->u.items);
}

void jx_array_append(struct jx *array, struct jx *value)
{
	struct jx_item **i;
	for (i = &array->u.items; *i; i = &(*i)->next) {
	}
	*i = jx_item(value, 0);
}

struct jx *jx_array_index(struct jx *j, int nth)
{
	if (!jx_istype(j, JX_ARRAY))
		return NULL;
	if (nth < 0)
		return NULL;
	struct jx_item *item = j->u.items;

	for (int i = 0; i < nth; i++) {
		if (!item)
			return NULL;
		item = item->next;
	}
	return item ? item->value : NULL;
}

int jx_array_length(struct jx *array)
{
	if (!jx_istype(array, JX_ARRAY))
		return -1;
	int count = 0;
	for (struct jx_item *i = array->u.items; i; i = i->next)
		++count;
	return count;
}

struct jx *jx_array_concat(struct jx *array, ...)
{
	struct jx *result = jx_array(NULL);
	struct jx_item **tail = &result->u.items;
	va_list ap;
	va_start(ap, array);
	for (struct jx *a = array; a; a = va_arg(ap, struct jx *)) {
		if (!jx_istype(a, JX_ARRAY)) {
			break;
		}
		*tail = a->u.items;
		while (*tail)
			tail = &(*tail)->next;
		free(a);
	}
	va_end(ap);
	return result;
}

struct jx *jx_array_shift(struct jx *array)
{
	if (!jx_istype(array, JX_ARRAY))
		return NULL;
	struct jx_item *i = array->u.items;
	struct jx *result = NULL;
	if (i) {
		result = i->value;
		array->u.items = i->next;
		free(i);
	}
	return result;
}

void jx_pair_delete(struct jx_pair *pair)
{
	if (!pair)
		return;
	jx_delete(pair->key);
	jx_delete(pair->value);
	jx_comprehension_delete(pair->comp);
	jx_pair_delete(pair->next);
	free(pair);
}

void jx_item_delete(struct jx_item *item)
{
	if (!item)
		return;
	jx_delete(item->value);
	jx_comprehension_delete(item->comp);
	jx_item_delete(item->next);
	free(item);
}

void jx_comprehension_delete(struct jx_comprehension *comp)
{
	if (!comp)
		return;
	free(comp->variable);
	jx_delete(comp->elements);
	jx_delete(comp->condition);
	jx_comprehension_delete(comp->next);
	free(comp);
}

void jx_delete(struct jx *j)
{
	if (!j)
		return;

	switch (j->type) {
	case JX_DOUBLE:
	case JX_BOOLEAN:
	case JX_INTEGER:
	case JX_NULL:
		break;
	case JX_SYMBOL:
		free(j->u.symbol_name);
		break;
	case JX_STRING:
		free(j->u.string_value);
		break;
	case JX_ARRAY:
		jx_item_delete(j->u.items);
		break;
	case JX_OBJECT:
		jx_pair_delete(j->u.pairs);
		break;
	case JX_OPERATOR:
		jx_delete(j->u.oper.left);
		jx_delete(j->u.oper.right);
		break;
	case JX_ERROR:
		jx_delete(j->u.err);
		break;
	}
	free(j);
}

int jx_isatomic(struct jx *j)
{
	switch (j->type) {
	case JX_BOOLEAN:
	case JX_STRING:
	case JX_INTEGER:
	case JX_DOUBLE:
		return 1;
	default:
		return 0;
	}
}

int jx_istype(struct jx *j, jx_type_t type)
{
	return j && j->type == type;
}

int jx_istrue(struct jx *j)
{
	return j && j->type == JX_BOOLEAN && j->u.boolean_value;
}

int jx_isfalse(struct jx *j)
{
	return j && j->type == JX_BOOLEAN && !j->u.boolean_value;
}

int jx_comprehension_equals(struct jx_comprehension *j, struct jx_comprehension *k)
{
	if (!j && !k)
		return 1;
	if (!j || !k)
		return 0;
	return !strcmp(j->variable, k->variable) && jx_equals(j->elements, k->elements) && jx_equals(j->condition, k->condition) && jx_comprehension_equals(j->next, k->next);
}

int jx_pair_equals(struct jx_pair *j, struct jx_pair *k)
{
	if (!j && !k)
		return 1;
	if (!j || !k)
		return 0;
	return jx_equals(j->key, k->key) && jx_equals(j->value, k->value) && jx_comprehension_equals(j->comp, k->comp) && jx_pair_equals(j->next, k->next);
}

int jx_item_equals(struct jx_item *j, struct jx_item *k)
{
	if (!j && !k)
		return 1;
	if (!j || !k)
		return 0;
	return jx_equals(j->value, k->value) && jx_comprehension_equals(j->comp, k->comp) && jx_item_equals(j->next, k->next);
}

int jx_equals(struct jx *j, struct jx *k)
{
	if (!j && !k)
		return 1;
	if (!j || !k)
		return 0;
	if (j->type != k->type)
		return 0;

	switch (j->type) {
	case JX_NULL:
		return 1;
	case JX_DOUBLE:
		return j->u.double_value == k->u.double_value;
	case JX_BOOLEAN:
		return j->u.boolean_value == k->u.boolean_value;
	case JX_INTEGER:
		return j->u.integer_value == k->u.integer_value;
	case JX_SYMBOL:
		return !strcmp(j->u.symbol_name, k->u.symbol_name);
	case JX_STRING:
		return !strcmp(j->u.string_value, k->u.string_value);
	case JX_ARRAY:
		return jx_item_equals(j->u.items, k->u.items);
	case JX_OBJECT:
		return jx_pair_equals(j->u.pairs, k->u.pairs);
	case JX_OPERATOR:
		return j->u.oper.type == k->u.oper.type && jx_equals(j->u.oper.left, k->u.oper.right) && jx_equals(j->u.oper.right, j->u.oper.right);
	case JX_ERROR:
		return jx_equals(j->u.err, k->u.err);
	}

	/* not reachable, but some compilers complain. */
	return 0;
}

struct jx_comprehension *jx_comprehension_copy(struct jx_comprehension *c)
{
	struct jx_comprehension *head = 0;
	struct jx_comprehension **nc = &head;

	while (c) {
		*nc = calloc(1, sizeof(struct jx_comprehension));
		(*nc)->line = c->line;
		(*nc)->variable = strdup(c->variable);
		(*nc)->elements = jx_copy(c->elements);
		(*nc)->condition = jx_copy(c->condition);

		nc = &(*nc)->next;
		c = c->next;
	}

	return head;
}

struct jx_pair *jx_pair_copy(struct jx_pair *p)
{
	struct jx_pair *head = 0;
	struct jx_pair **np = &head;

	while (p) {
		*np = calloc(1, sizeof(struct jx_pair));
		(*np)->line = p->line;
		(*np)->key = jx_copy(p->key);
		(*np)->value = jx_copy(p->value);
		(*np)->comp = jx_comprehension_copy(p->comp);

		np = &(*np)->next;
		p = p->next;
	}

	return head;
}

struct jx_item *jx_item_copy(struct jx_item *i)
{
	struct jx_item *head = 0;
	struct jx_item **ni = &head;

	while (i) {
		*ni = calloc(1, sizeof(struct jx_item));
		(*ni)->line = i->line;
		(*ni)->value = jx_copy(i->value);
		(*ni)->comp = jx_comprehension_copy(i->comp);

		ni = &(*ni)->next;
		i = i->next;
	}

	return head;
}

struct jx *jx_copy(struct jx *j)
{
	if (!j)
		return 0;
	struct jx *c = 0;

	switch (j->type) {
	case JX_NULL:
		c = jx_null();
		break;
	case JX_DOUBLE:
		c = jx_double(j->u.double_value);
		break;
	case JX_BOOLEAN:
		c = jx_boolean(j->u.boolean_value);
		break;
	case JX_INTEGER:
		c = jx_integer(j->u.integer_value);
		break;
	case JX_SYMBOL:
		c = jx_symbol(j->u.symbol_name);
		break;
	case JX_STRING:
		c = jx_string(j->u.string_value);
		break;
	case JX_ARRAY:
		c = jx_array(jx_item_copy(j->u.items));
		break;
	case JX_OBJECT:
		c = jx_object(jx_pair_copy(j->u.pairs));
		break;
	case JX_OPERATOR:
		c = jx_operator(j->u.oper.type, jx_copy(j->u.oper.left), jx_copy(j->u.oper.right));
		break;
	case JX_ERROR:
		c = jx_error(jx_copy(j->u.err));
		break;
	}

	if (c) {
		c->line = j->line;
	}

	return c;
}

struct jx *jx_merge(struct jx *j, ...)
{
	va_list ap;
	va_start(ap, j);
	struct jx *result = jx_object(NULL);
	for (struct jx *next = j; jx_istype(next, JX_OBJECT); next = va_arg(ap, struct jx *)) {
		for (struct jx_pair *p = next->u.pairs; p; p = p->next) {
			jx_delete(jx_remove(result, p->key));
			jx_insert(result, jx_copy(p->key), jx_copy(p->value));
		}
	}
	va_end(ap);
	return result;
}

static int jx_pair_is_constant(struct jx_pair *p)
{
	if (!p)
		return 1;
	if (p->comp)
		return 0;
	return jx_is_constant(p->key) && jx_is_constant(p->value) && jx_pair_is_constant(p->next);
}

static int jx_item_is_constant(struct jx_item *i)
{
	if (!i)
		return 1;
	if (i->comp)
		return 0;
	return jx_is_constant(i->value) && jx_item_is_constant(i->next);
}

int jx_is_constant(struct jx *j)
{
	if (!j)
		return 0;
	switch (j->type) {
	case JX_SYMBOL:
		return 0;
	case JX_DOUBLE:
	case JX_BOOLEAN:
	case JX_INTEGER:
	case JX_STRING:
	case JX_NULL:
		return 1;
	case JX_ARRAY:
		return jx_item_is_constant(j->u.items);
	case JX_OBJECT:
		return jx_pair_is_constant(j->u.pairs);
	case JX_ERROR:
	case JX_OPERATOR:
		return 0;
	}

	/* not reachable, but some compilers complain. */
	return 0;
}

void jx_export(struct jx *j)
{
	if (!j || !jx_istype(j, JX_OBJECT))
		return;

	struct jx_pair *p;
	for (p = j->u.pairs; p; p = p->next) {
		if (p->key->type == JX_STRING && p->value->type == JX_STRING) {
			setenv(p->key->u.string_value, p->value->u.string_value, 1);
		}
	}
}

struct jx *jx_iterate_array(struct jx *j, void **i)
{
	struct jx_item **x = (struct jx_item **)i;
	assert(x);

	if (*x) {
		*x = (*x)->next;
	} else if (jx_istype(j, JX_ARRAY)) {
		*x = j->u.items;
	}
	return *x ? (*x)->value : NULL;
}

static void advance_object_iter(struct jx *j, void **i)
{
	struct jx_pair **p = (struct jx_pair **)i;
	assert(p);

	if (*p) {
		*p = (*p)->next;
	} else if (jx_istype(j, JX_OBJECT)) {
		*p = j->u.pairs;
	}
}

const char *jx_get_key(void **i)
{
	assert(i);
	struct jx_pair *p = *i;
	return p ? p->key->u.string_value : NULL;
}

struct jx *jx_get_value(void **i)
{
	assert(i);
	struct jx_pair *p = *i;
	return p ? p->value : NULL;
}

const char *jx_iterate_keys(struct jx *j, void **i)
{
	advance_object_iter(j, i);
	return jx_get_key(i);
}

struct jx *jx_iterate_values(struct jx *j, void **i)
{
	advance_object_iter(j, i);
	return jx_get_value(i);
}
