/* Copyright (c) 2013, Vsevolod Stakhov
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *       * Redistributions of source code must retain the above copyright
 *         notice, this list of conditions and the following disclaimer.
 *       * Redistributions in binary form must reproduce the above copyright
 *         notice, this list of conditions and the following disclaimer in the
 *         documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL AUTHOR BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "lua_common.h"

/**
 * @file lua rcl bindings
 */

static gint lua_rcl_obj_push_array (lua_State *L, ucl_object_t *obj);
static gint lua_rcl_obj_push_simple (lua_State *L, ucl_object_t *obj, gboolean allow_array);
static ucl_object_t* lua_rcl_table_get (lua_State *L, gint idx);
static ucl_object_t* lua_rcl_elt_get (lua_State *L, gint idx);

/**
 * Push a single element of an object to lua
 * @param L
 * @param key
 * @param obj
 */
static void
lua_rcl_obj_push_elt (lua_State *L, const char *key, ucl_object_t *obj)
{
	lua_pushstring (L, key);
	lua_rcl_obj_push (L, obj, TRUE);
	lua_settable (L, -3);
}

/**
 * Push a single object to lua
 * @param L
 * @param obj
 * @return
 */
static gint
lua_rcl_obj_push_obj (lua_State *L, ucl_object_t *obj, gboolean allow_array)
{
	ucl_object_t *cur;
	ucl_object_iter_t it = NULL;

	if (allow_array && obj->next != NULL) {
		/* Actually we need to push this as an array */
		return lua_rcl_obj_push_array (L, obj);
	}

	lua_newtable (L);
	while ((cur = ucl_iterate_object (obj, &it, true)) != NULL) {
		lua_rcl_obj_push_elt (L, ucl_object_key (cur), cur);
	}

	return 1;
}

/**
 * Push an array to lua as table indexed by integers
 * @param L
 * @param obj
 * @return
 */
static gint
lua_rcl_obj_push_array (lua_State *L, ucl_object_t *obj)
{
	ucl_object_t *cur;
	gint i = 1;

	lua_newtable (L);

	LL_FOREACH (obj, cur) {
		lua_rcl_obj_push (L, cur, FALSE);
		lua_rawseti (L, -2, i);
		i ++;
	}

	return 1;
}

/**
 * Push a simple object to lua depending on its actual type
 */
static gint
lua_rcl_obj_push_simple (lua_State *L, ucl_object_t *obj, gboolean allow_array)
{
	if (allow_array && obj->next != NULL) {
		/* Actually we need to push this as an array */
		return lua_rcl_obj_push_array (L, obj);
	}

	switch (obj->type) {
	case UCL_BOOLEAN:
		lua_pushboolean (L, ucl_obj_toboolean (obj));
		break;
	case UCL_STRING:
		lua_pushstring (L, ucl_obj_tostring (obj));
		break;
	case UCL_INT:
#if LUA_VERSION_NUM >= 501
		lua_pushinteger (L, ucl_obj_toint (obj));
#else
		lua_pushnumber (L, ucl_obj_toint (obj));
#endif
		break;
	case UCL_FLOAT:
	case UCL_TIME:
		lua_pushnumber (L, ucl_obj_todouble (obj));
		break;
	default:
		lua_pushnil (L);
		break;
	}

	return 1;
}

/**
 * Push an object to lua
 * @param L lua state
 * @param obj object to push
 */
gint
lua_rcl_obj_push (lua_State *L, ucl_object_t *obj, gboolean allow_array)
{
	switch (obj->type) {
	case UCL_OBJECT:
		return lua_rcl_obj_push_obj (L, obj, allow_array);
	case UCL_ARRAY:
		return lua_rcl_obj_push_array (L, obj->value.av);
	default:
		return lua_rcl_obj_push_simple (L, obj, allow_array);
	}
}

/**
 * Parse lua table into object top
 * @param L
 * @param top
 * @param idx
 */
static ucl_object_t *
lua_rcl_table_get (lua_State *L, gint idx)
{
	ucl_object_t *obj, *top = NULL;
	gsize keylen;
	const gchar *k;

	/* Table iterate */
	lua_pushvalue (L, idx);
	lua_pushnil (L);
	while (lua_next (L, -2) != 0) {
		/* copy key to avoid modifications */
		lua_pushvalue (L, -2);
		k = lua_tolstring (L, -1, &keylen);
		obj = lua_rcl_elt_get (L, -2);
		top = ucl_object_insert_key (top, obj, k, keylen, true);
		lua_pop (L, 2);
	}
	lua_pop (L, 1);

	return top;
}

/**
 * Get a single element from lua to object obj
 * @param L
 * @param obj
 * @param idx
 */
static ucl_object_t *
lua_rcl_elt_get (lua_State *L, gint idx)
{
	gint type;
	ucl_object_t *obj;

	type = lua_type (L, idx);

	switch (type) {
	case LUA_TFUNCTION:
		lua_pushvalue (L, idx);
		obj = ucl_object_new ();
		obj->type = UCL_USERDATA;
		obj->value.ud = GINT_TO_POINTER (luaL_ref (L, LUA_REGISTRYINDEX));
		break;
	case LUA_TSTRING:
		obj = ucl_object_fromstring_common (lua_tostring (L, idx), 0, 0);
		break;
	case LUA_TNUMBER:
		obj = ucl_object_fromdouble (lua_tonumber (L, idx));
		break;
	case LUA_TBOOLEAN:
		obj = ucl_object_frombool (lua_toboolean (L, idx));
		break;
	case LUA_TTABLE:
		obj = lua_rcl_table_get (L, idx);
		break;
	}

	return obj;
}

/**
 * Extract rcl object from lua object
 * @param L
 * @return
 */
ucl_object_t *
lua_rcl_obj_get (lua_State *L, gint idx)
{
	ucl_object_t *obj;
	gint t;

	t = lua_type (L, idx);
	switch (t) {
	case LUA_TTABLE:
		/* We assume all tables as objects, not arrays */
		obj = lua_rcl_table_get (L, idx);
		break;
	default:
		obj = lua_rcl_elt_get (L, idx);
		break;
	}

	return obj;
}
