#include <php.h>
#undef PACKAGE_BUGREPORT
#undef PACKAGE_NAME
#undef PACKAGE_STRING
#undef PACKAGE_TARNAME
#undef PACKAGE_VERSION
#include <lasso/lasso.h>
#include "php_lasso.h"
#include "../../lasso/utils.h"

/* utility functions */
static void free_glist(GList **list, GFunc free_function);

#if (GLIB_MAJOR_VERSION == 2 && GLIB_MINOR_VERSION < 14)
  /* copy of private struct and g_hash_table_get_keys from GLib internals
   * (as this function is useful but new in 2.14) */

typedef struct _GHashNode  GHashNode;

struct _GHashNode
{
  gpointer   key;
  gpointer   value;
  GHashNode *next;
  guint      key_hash;
};

struct _GHashTable
{
  gint             size;
  gint             nnodes;
  GHashNode      **nodes;
  GHashFunc        hash_func;
  GEqualFunc       key_equal_func;
  volatile gint    ref_count;
  GDestroyNotify   key_destroy_func;
  GDestroyNotify   value_destroy_func;
};

static GList *
g_hash_table_get_keys (GHashTable *hash_table)
{
  GHashNode *node;
  gint i;
  GList *retval;

  g_return_val_if_fail (hash_table != NULL, NULL);

  retval = NULL;
  for (i = 0; i < hash_table->size; i++)
    for (node = hash_table->nodes[i]; node; node = node->next)
      retval = g_list_prepend (retval, node->key);

  return retval;
}

#endif

/* Define the Lasso PHP module */

int le_lasso_server;

ZEND_GET_MODULE(lasso)

/* Wrapper around GObject to get the dynamic typename */

typedef struct {
	GObject *obj;
	char *typename;
} PhpGObjectPtr;

static PhpGObjectPtr*
PhpGObjectPtr_New(GObject *obj)
{
	PhpGObjectPtr *self;

	if (obj == NULL) {
		return NULL;
	}

	self = (PhpGObjectPtr *)emalloc(sizeof(PhpGObjectPtr));
	self->obj = obj;
	self->typename = estrdup(G_OBJECT_TYPE_NAME(obj));

	return self;
}

PHP_FUNCTION(lasso_get_object_typename)
{
	PhpGObjectPtr *self;
	zval *zval_self;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &zval_self) == FAILURE) {
		RETURN_FALSE;
	}

	ZEND_FETCH_RESOURCE(self, PhpGObjectPtr *, &zval_self, -1, PHP_LASSO_SERVER_RES_NAME, le_lasso_server);
	RETURN_STRING(self->typename, 1);
}

/* List handling */
static void
free_glist(GList **list, GFunc free_function) {
    g_return_if_fail(list);
    if (*list) {
        if (free_function) {
            g_list_foreach(*list, free_function, NULL);
        }
        g_list_free(*list);
    }
    *list = NULL;
}
/* Conversion functions */

static char*
get_string_from_xml_node(xmlNode *xmlnode)
{
	xmlOutputBufferPtr buf;
	char *xmlString;

	if (xmlnode == NULL) {
		return NULL;
	}

	buf = xmlAllocOutputBuffer(NULL);
	if (buf == NULL) {
		xmlString = NULL;
	} else {
		xmlNodeDumpOutput(buf, NULL, xmlnode, 0, 1, NULL);
		xmlOutputBufferFlush(buf);
		if (buf->conv == NULL) {
			xmlString = estrdup((char*)buf->buffer->content);
		} else {
			xmlString = estrdup((char*)buf->conv->content);
		}
		xmlOutputBufferClose(buf);
	}

	return xmlString;
}

static xmlNode*
get_xml_node_from_string(char *string)
{
	xmlDoc *doc;
	xmlNode *node;

	doc = xmlReadDoc((xmlChar*)string, NULL, NULL, XML_PARSE_NONET);
	node = xmlDocGetRootElement(doc);
	if (node != NULL) {
		node = xmlCopyNode(node, 1);
	}
	lasso_release_doc(doc);

	return node;
}

static GList*
get_list_from_array_of_strings(zval* array)
{
	HashTable* hashtable;
	HashPosition pointer;
	int size;
	zval** data;
	zval temp;
	GList* result = NULL;

	hashtable = Z_ARRVAL_P(array);
	size = zend_hash_num_elements(hashtable);
	for (zend_hash_internal_pointer_reset_ex(hashtable, &pointer);
			zend_hash_get_current_data_ex(hashtable, (void**) &data, &pointer) == SUCCESS;
			zend_hash_move_forward_ex(hashtable, &pointer)) {
		temp = **data;
		zval_copy_ctor(&temp);
		convert_to_string(&temp);
		result = g_list_append(result, g_strndup(Z_STRVAL(temp), Z_STRLEN(temp)));
		zval_dtor(&temp);
	}
	return result;
}

static void
set_array_from_list_of_strings(GList* list, zval **array) {
	GList* item;

	array_init(*array);
	for (item = g_list_first(list); item != NULL; item = g_list_next(item)) {
		if (item->data != NULL) {
			add_next_index_string(*array, item->data, 1);
		} else {
			add_next_index_null(*array);
		}
	}
}

static GList*
get_list_from_array_of_xmlnodes(zval* array)
{
	HashTable* hashtable;
	HashPosition pointer;
	int size;
	zval** data;
	zval temp;
    char *temp_str;
	GList* result = NULL;

	hashtable = Z_ARRVAL_P(array);
	size = zend_hash_num_elements(hashtable);
	for (zend_hash_internal_pointer_reset_ex(hashtable, &pointer);
			zend_hash_get_current_data_ex(hashtable, (void**) &data, &pointer) == SUCCESS;
			zend_hash_move_forward_ex(hashtable, &pointer)) {
		temp = **data;
		zval_copy_ctor(&temp);
		convert_to_string(&temp);
		temp_str = estrndup(Z_STRVAL(temp), Z_STRLEN(temp));
		result = g_list_append(result, get_xml_node_from_string(temp_str));
		zval_dtor(&temp);
	}
	return result;
}

static void
set_array_from_list_of_xmlnodes(GList* list, zval **array) {
	GList* item;

	array_init(*array);
	for (item = g_list_first(list); item != NULL; item = g_list_next(item)) {
		if (item->data != NULL) {
			add_next_index_string(*array, get_string_from_xml_node(item->data), 1);
		} else {
			add_next_index_null(*array);
		}
	}
}

static GList*
get_list_from_array_of_objects(zval *array)
{
	HashTable *hashtable;
	HashPosition pointer;
	int size;
	zval **data;
	PhpGObjectPtr *cvt_temp;
	GList *result = NULL;

	hashtable = Z_ARRVAL_P(array);
	size = zend_hash_num_elements(hashtable);
	for (zend_hash_internal_pointer_reset_ex(hashtable, &pointer);
			zend_hash_get_current_data_ex(hashtable, (void**) &data, &pointer) == SUCCESS;
			zend_hash_move_forward_ex(hashtable, &pointer)) {
		cvt_temp = (PhpGObjectPtr*) zend_fetch_resource(data TSRMLS_CC, -1, PHP_LASSO_SERVER_RES_NAME, NULL, 1, le_lasso_server);
		if (cvt_temp != NULL) {
			result = g_list_append(result, cvt_temp->obj);
		} else {
			result = g_list_append(result, NULL);
		}
	}
	return result;
}

static void
set_array_from_list_of_objects(GList *list, zval **array)
{
	GList *item = NULL;
	zval *zval_item = NULL;

	array_init(*array);
	for (item = g_list_first(list); item != NULL; item = g_list_next(item)) {
		if (item->data != NULL) {
		    zval_item = emalloc(sizeof(PhpGObjectPtr));
			ZEND_REGISTER_RESOURCE(zval_item, PhpGObjectPtr_New(item->data), le_lasso_server);
			add_next_index_zval(*array, zval_item);
		} else {
			add_next_index_null(*array);
		}
	}
}

/* FIXME: This function doesn't work yet */
static GHashTable*
get_hashtable_from_array_of_objects(zval *array)
{
	HashTable *hashtable = NULL;
	HashPosition pointer;
	int size;
	char *key = NULL;
	unsigned int key_len;
	unsigned long index;
	zval **data = NULL;
	PhpGObjectPtr *cvt_temp = NULL;
	GHashTable *result = NULL;

	result = g_hash_table_new(g_str_hash, g_str_equal);
	hashtable = Z_ARRVAL_P(array);
	size = zend_hash_num_elements(hashtable);
	for (zend_hash_internal_pointer_reset_ex(hashtable, &pointer);
			zend_hash_get_current_data_ex(hashtable, (void**) &data, &pointer) == SUCCESS;
			zend_hash_move_forward_ex(hashtable, &pointer)) {
		cvt_temp = (PhpGObjectPtr*) zend_fetch_resource(data TSRMLS_CC, -1, PHP_LASSO_SERVER_RES_NAME, NULL, 1, le_lasso_server);
		if (zend_hash_get_current_key_ex(hashtable, &key, &key_len, &index, 0, &pointer) == HASH_KEY_IS_STRING) {
			if (cvt_temp != NULL) {
				g_hash_table_insert(result, key, g_object_ref(cvt_temp->obj));
			} else {
				g_hash_table_insert(result, key, NULL);
			}
		} else {
			if (cvt_temp != NULL) {
				g_hash_table_insert(result, (gpointer)index, g_object_ref(cvt_temp->obj));
			} else {
				g_hash_table_insert(result, (gpointer)index, NULL);
			}
		}
	}
	return result;
}

static void
set_array_from_hashtable_of_objects(GHashTable *hashtable, zval **array)
{
	GList *keys = NULL;
	GObject *item = NULL;
	zval *zval_item = NULL;

	array_init(*array);
	for (keys = g_hash_table_get_keys(hashtable); keys; keys = g_list_next(keys)) {
		item = g_hash_table_lookup(hashtable, keys->data);
		if (item) {
			zval_item = emalloc(sizeof(PhpGObjectPtr));
			ZEND_REGISTER_RESOURCE(zval_item, PhpGObjectPtr_New(item), le_lasso_server);
			add_assoc_zval(*array, (char*)keys->data, zval_item);
		} else {
			add_assoc_null(*array, (char*)keys->data);
		}
	}
	g_list_free(keys);
}

