
#include <lighttpd/core_lua.h>
#include <lighttpd/condition_lua.h>
#include <lighttpd/value_lua.h>

#define LUA_CONDITION "liCondition*"
#define LUA_COND_LVALUE "liConditionLValue*"
#define LUA_COND_LVALUE_T "liCondLValue"

/* helper */
#define lua_mt_register_srv(srv, L, name, func) do {\
	lua_pushlightuserdata(L, srv); \
	lua_pushcclosure(L, func, 1); \
	lua_setfield(L, -2, name); \
} while(0)

#define lua_mt_register_cmp(srv, L, name, func, cmp) do {\
	lua_pushlightuserdata(L, srv); \
	lua_pushinteger(L, cmp); \
	lua_pushcclosure(L, func, 2); \
	lua_setfield(L, -2, name); \
} while(0)

#define lua_mt_register(L, name, func) do {\
	lua_pushcclosure(L, func, 0); \
	lua_setfield(L, -2, name); \
} while(0)

/* save the top of the stack in dicts described by a '.' separated path */
static void lua_settop_in_dicts(lua_State *L, const gchar *path) {
	int ndx, curtable = 0;
	gchar** segments;
	size_t i;

	ndx = lua_gettop(L);
	segments = g_strsplit(path, ".", 10);
	LI_FORCE_ASSERT(segments[0]);
	for (i = 0; segments[i+1]; i++) {
		if (curtable == 0) {
			lua_getglobal(L, segments[i]);
		} else {
			lua_getfield(L, curtable, segments[i]);
		}
		if (lua_isnil(L, -1) || !lua_istable(L, -1)) {
			lua_pop(L, 1); /* pop nil */
			lua_newtable(L);
			lua_pushvalue(L, -1); /* save table in field */
			if (curtable == 0) {
				lua_setglobal(L, segments[i]);
			} else {
				lua_setfield(L, curtable, segments[i]);
			}
		}
		curtable = lua_gettop(L);
	}
	lua_pushvalue(L, ndx);
	lua_setfield(L, curtable, segments[i]);
	lua_pop(L, lua_gettop(L) - ndx + 1);
	g_strfreev(segments);
}

/* Get objects from lua */

liCondition* li_lua_get_condition(lua_State *L, int ndx) {
	if (!lua_isuserdata(L, ndx)) return NULL;
	if (!lua_getmetatable(L, ndx)) return NULL;
	luaL_getmetatable(L, LUA_CONDITION);
	if (lua_isnil(L, -1) || lua_isnil(L, -2) || !li_lua_equal(L, -1, -2)) {
		lua_pop(L, 2);
		return NULL;
	}
	lua_pop(L, 2);
	return *(liCondition**) lua_touserdata(L, ndx);
}

static liConditionLValue* lua_get_condition_lvalue(lua_State *L, int ndx) {
	if (!lua_isuserdata(L, ndx)) return NULL;
	if (!lua_getmetatable(L, ndx)) return NULL;
	luaL_getmetatable(L, LUA_COND_LVALUE);
	if (lua_isnil(L, -1) || lua_isnil(L, -2) || !li_lua_equal(L, -1, -2)) {
		lua_pop(L, 2);
		return NULL;
	}
	lua_pop(L, 2);
	return *(liConditionLValue**) lua_touserdata(L, ndx);
}

static liCondLValue lua_get_cond_lvalue_t(lua_State *L, int ndx) {
	if (!lua_isuserdata(L, ndx)) return -1;
	if (!lua_getmetatable(L, ndx)) return -1;
	luaL_getmetatable(L, LUA_COND_LVALUE_T);
	if (lua_isnil(L, -1) || lua_isnil(L, -2) || !li_lua_equal(L, -1, -2)) {
		lua_pop(L, 2);
		return -1;
	}
	lua_pop(L, 2);
	return *(liCondLValue*) lua_touserdata(L, ndx);
}

/* Garbage collection */

static int lua_condition_gc(lua_State *L) {
	liServer *srv;
	liCondition **c = (liCondition**) luaL_checkudata(L, 1, LUA_CONDITION);
	if (!c || !*c) return 0;

	srv = (liServer*) lua_touserdata(L, lua_upvalueindex(1));
	li_condition_release(srv, *c);
	return 0;
}

static int lua_cond_lvalue_gc(lua_State *L) {
	liConditionLValue **lvalue = (liConditionLValue**) luaL_checkudata(L, 1, LUA_COND_LVALUE);
	if (!lvalue || !*lvalue) return 0;

	li_condition_lvalue_release(*lvalue);
	return 0;
}

/* new metatables and push */

static HEDLEY_NEVER_INLINE void init_condition_mt(liServer *srv, lua_State *L) {
	lua_mt_register_srv(srv, L, "__gc", lua_condition_gc);
}

static void lua_push_condition_metatable(liServer *srv, lua_State *L) {
	if (li_lua_new_protected_metatable(L, LUA_CONDITION)) {
		init_condition_mt(srv, L);
	}
}

int li_lua_push_condition(liServer *srv, lua_State *L, liCondition *c) {
	liCondition **pc;

	if (NULL == c) {
		lua_pushnil(L);
		return 1;
	}

	pc = (liCondition**) lua_newuserdata(L, sizeof(liCondition*));
	*pc = c;

	lua_push_condition_metatable(srv, L);
	lua_setmetatable(L, -2);
	return 1;
}


/* cond_lvalue metatable (except __gc) */

/* ::_tostring */
static int lua_cond_lvalue_tostring(lua_State *L) {
	liConditionLValue *lvalue = lua_get_condition_lvalue(L, 1);
	if (!lvalue) return 0;
	lua_pushstring(L, li_cond_lvalue_to_string(lvalue->type));
	if (lvalue->key) {
		lua_pushstring(L, "['");
		lua_pushstring(L, lvalue->key->str);
		lua_pushstring(L, "']");
		lua_concat(L, 4);
	}
	return 1;
}

static int lua_cond_lvalue_bool(lua_State *L) {
	liServer *srv;
	liCondition *c;
	liConditionLValue *lvalue;
	liCompOperator cmpop;

	lvalue = lua_get_condition_lvalue(L, 1);
	srv = (liServer*) lua_touserdata(L, lua_upvalueindex(1));
	cmpop = (liCompOperator) lua_tointeger(L, lua_upvalueindex(2));

	c = li_condition_new_bool(srv, lvalue, cmpop == LI_CONFIG_COND_EQ);
	if (c) {
		li_condition_lvalue_acquire(lvalue);
		li_lua_push_condition(srv, L, c);
		return 1;
	}
	return 0;
}

static int lua_cond_lvalue_cmp(lua_State *L) {
	liServer *srv;
	GString *sval;
	liCondition *c;
	liConditionLValue *lvalue;
	liCompOperator cmpop;

	lvalue = lua_get_condition_lvalue(L, 1);
	srv = (liServer*) lua_touserdata(L, lua_upvalueindex(1));
	cmpop = (liCompOperator) lua_tointeger(L, lua_upvalueindex(2));

	if (NULL == (sval = li_lua_togstring(L, 2))) return 0;
	c = li_condition_new_string(srv, cmpop, lvalue, sval);
	if (c) {
		li_condition_lvalue_acquire(lvalue);
		li_lua_push_condition(srv, L, c);
		return 1;
	}
	return 0;
}

static HEDLEY_NEVER_INLINE void init_cond_lvalue_mt(liServer *srv, lua_State *L) {
	lua_mt_register(L, "__gc", lua_cond_lvalue_gc);
	lua_mt_register(L, "__tostring", lua_cond_lvalue_tostring);

	lua_mt_register_cmp(srv, L, "eq", lua_cond_lvalue_cmp, LI_CONFIG_COND_EQ);
	lua_mt_register_cmp(srv, L, "ne", lua_cond_lvalue_cmp, LI_CONFIG_COND_NE);
	lua_mt_register_cmp(srv, L, "prefix", lua_cond_lvalue_cmp, LI_CONFIG_COND_PREFIX);
	lua_mt_register_cmp(srv, L, "notprefix", lua_cond_lvalue_cmp, LI_CONFIG_COND_NOPREFIX);
	lua_mt_register_cmp(srv, L, "suffix", lua_cond_lvalue_cmp, LI_CONFIG_COND_SUFFIX);
	lua_mt_register_cmp(srv, L, "notsuffix", lua_cond_lvalue_cmp, LI_CONFIG_COND_NOSUFFIX);
	lua_mt_register_cmp(srv, L, "match", lua_cond_lvalue_cmp, LI_CONFIG_COND_MATCH);
	lua_mt_register_cmp(srv, L, "nomatch", lua_cond_lvalue_cmp, LI_CONFIG_COND_NOMATCH);
	lua_mt_register_cmp(srv, L, "ip", lua_cond_lvalue_cmp, LI_CONFIG_COND_IP);
	lua_mt_register_cmp(srv, L, "notip", lua_cond_lvalue_cmp, LI_CONFIG_COND_NOTIP);
	lua_mt_register_cmp(srv, L, "gt", lua_cond_lvalue_cmp, LI_CONFIG_COND_GT);
	lua_mt_register_cmp(srv, L, "ge", lua_cond_lvalue_cmp, LI_CONFIG_COND_GE);
	lua_mt_register_cmp(srv, L, "lt", lua_cond_lvalue_cmp, LI_CONFIG_COND_LT);
	lua_mt_register_cmp(srv, L, "le", lua_cond_lvalue_cmp, LI_CONFIG_COND_LE);

	lua_mt_register_cmp(srv, L, "is", lua_cond_lvalue_bool, LI_CONFIG_COND_EQ);
	lua_mt_register_cmp(srv, L, "isnot", lua_cond_lvalue_bool, LI_CONFIG_COND_NE);

	lua_pushvalue(L, -1);
	lua_setfield(L, -2, "__index");
}

static void lua_push_cond_lvalue_metatable(liServer *srv, lua_State *L) {
	if (li_lua_new_protected_metatable(L, LUA_COND_LVALUE)) {
		init_cond_lvalue_mt(srv, L);
	}
}

static int lua_push_cond_lvalue(liServer *srv, lua_State *L, liConditionLValue *lvalue) {
	liConditionLValue **pv;

	pv = (liConditionLValue**) lua_newuserdata(L, sizeof(liConditionLValue*));
	*pv = lvalue;

	lua_push_cond_lvalue_metatable(srv, L);
	lua_setmetatable(L, -2);
	return 1;
}

/* cond_lvalue_t metatable */
static int lua_cond_lvalue_t_tostring(lua_State *L) {
	liCondLValue t = lua_get_cond_lvalue_t(L, 1);
	lua_pushstring(L, li_cond_lvalue_to_string(t));
	return 1;
}

static int lua_cond_lvalue_t_index(lua_State *L) {
	liServer *srv;
	GString *key;
	liCondLValue t = lua_get_cond_lvalue_t(L, 1);

	srv = (liServer*) lua_touserdata(L, lua_upvalueindex(1));
	if (t < LI_COND_LVALUE_FIRST_WITH_KEY || t >= LI_COND_LVALUE_END) return 0;
	if (NULL == (key = li_lua_togstring(L, 2))) return 0;
	lua_push_cond_lvalue(srv, L, li_condition_lvalue_new(t, key));
	return 1;
}

static HEDLEY_NEVER_INLINE void init_cond_lvalue_t_mt(liServer *srv, lua_State *L) {
	lua_mt_register(L, "__tostring", lua_cond_lvalue_t_tostring);
	lua_mt_register_srv(srv, L, "__index", lua_cond_lvalue_t_index);
}

static void lua_push_cond_lvalue_t_metatable(liServer *srv, lua_State *L) {
	if (li_lua_new_protected_metatable(L, LUA_COND_LVALUE_T)) {
		init_cond_lvalue_t_mt(srv, L);
	}
}

void li_lua_init_condition_mt(liServer *srv, lua_State *L) {
	lua_push_condition_metatable(srv, L);
	lua_pop(L, 1);

	lua_push_cond_lvalue_metatable(srv, L);
	lua_pop(L, 1);

	lua_push_cond_lvalue_t_metatable(srv, L);
	lua_pop(L, 1);
}

/* cond_lvalue_t */

static int lua_push_cond_lvalue_t(liServer *srv, lua_State *L, liCondLValue t) {
	liCondLValue *pt;

	pt = (liCondLValue*) lua_newuserdata(L, sizeof(liCondLValue));
	*pt = t;

	lua_push_cond_lvalue_t_metatable(srv, L);
	lua_setmetatable(L, -2);
	return 1;
}

void li_lua_set_global_condition_lvalues(liServer *srv, lua_State *L) {
	size_t i;

	for (i = 0; i < LI_COND_LVALUE_FIRST_WITH_KEY; i++) {
		lua_push_cond_lvalue(srv, L, li_condition_lvalue_new(i, NULL));
		lua_settop_in_dicts(L, li_cond_lvalue_to_string(i));
	}

	for ( ; i < LI_COND_LVALUE_END; i++) {
		lua_push_cond_lvalue_t(srv, L, i);
		lua_settop_in_dicts(L, li_cond_lvalue_to_string(i));
	}
}
