
#include <lighttpd/base.h>
#include <lighttpd/core_lua.h>
#include <lighttpd/actions_lua.h>

#define LUA_FILTER "liFilter*"

typedef int (*lua_Filter_Attrib)(liFilter *f, lua_State *L);

static int lua_filter_attr_read_in(liFilter *f, lua_State *L) {
	li_lua_push_chunkqueue(L, f->in);
	return 1;
}

static int lua_filter_attr_read_out(liFilter *f, lua_State *L) {
	li_lua_push_chunkqueue(L, f->out);
	return 1;
}

#define AR(m) { #m, lua_filter_attr_read_##m, NULL }
#define AW(m) { #m, NULL, lua_filter_attr_write_##m }
#define ARW(m) { #m, lua_filter_attr_read_##m, lua_filter_attr_write_##m }

static const struct {
	const char* key;
	lua_Filter_Attrib read_attr, write_attr;
} filter_attribs[] = {
	AR(in),
	AR(out),

	{ NULL, NULL, NULL }
};

static int lua_filter_index(lua_State *L) {
	liFilter *f;
	const char *key;
	int i;

	if (lua_gettop(L) != 2) {
		lua_pushstring(L, "incorrect number of arguments");
		lua_error(L);
	}

	if (li_lua_metatable_index(L)) return 1;

	f = li_lua_get_filter(L, 1);
	if (!f) return 0;

	if (lua_isnumber(L, 2)) return 0;
	if (!lua_isstring(L, 2)) return 0;

	key = lua_tostring(L, 2);
	for (i = 0; filter_attribs[i].key ; i++) {
		if (0 == strcmp(key, filter_attribs[i].key)) {
			if (filter_attribs[i].read_attr)
				return filter_attribs[i].read_attr(f, L);
			break;
		}
	}

	lua_pushstring(L, "cannot read attribute ");
	lua_pushstring(L, key);
	lua_pushstring(L, " in filter");
	lua_concat(L, 3);
	lua_error(L);

	return 0;
}

static int lua_filter_newindex(lua_State *L) {
	liFilter *f;
	const char *key;
	int i;

	if (lua_gettop(L) != 3) {
		lua_pushstring(L, "incorrect number of arguments");
		lua_error(L);
	}

	f = li_lua_get_filter(L, 1);
	if (!f) return 0;

	if (lua_isnumber(L, 2)) return 0;
	if (!lua_isstring(L, 2)) return 0;

	key = lua_tostring(L, 2);
	for (i = 0; filter_attribs[i].key ; i++) {
		if (0 == strcmp(key, filter_attribs[i].key)) {
			if (filter_attribs[i].write_attr)
				return filter_attribs[i].write_attr(f, L);
			break;
		}
	}

	lua_pushstring(L, "cannot write attribute ");
	lua_pushstring(L, key);
	lua_pushstring(L, "in filter");
	lua_concat(L, 3);
	lua_error(L);

	return 0;
}

static const luaL_Reg filter_mt[] = {
	{ "__index", lua_filter_index },
	{ "__newindex", lua_filter_newindex },

	{ NULL, NULL }
};

static HEDLEY_NEVER_INLINE void init_filter_mt(lua_State *L) {
	li_lua_setfuncs(L, filter_mt);
}

static void lua_push_filter_metatable(lua_State *L) {
	if (li_lua_new_protected_metatable(L, LUA_FILTER)) {
		init_filter_mt(L);
	}
}

void li_lua_init_filter_mt(lua_State *L) {
	lua_push_filter_metatable(L);
	lua_pop(L, 1);
}

liFilter* li_lua_get_filter(lua_State *L, int ndx) {
	if (!lua_isuserdata(L, ndx)) return NULL;
	if (!lua_getmetatable(L, ndx)) return NULL;
	luaL_getmetatable(L, LUA_FILTER);
	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 *(liFilter**) lua_touserdata(L, ndx);
}

int li_lua_push_filter(lua_State *L, liFilter *f) {
	liFilter **pf;

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

	pf = (liFilter**) lua_newuserdata(L, sizeof(liFilter*));
	*pf = f;

	lua_push_filter_metatable(L);
	lua_setmetatable(L, -2);
	return 1;
}



typedef struct filter_lua_config filter_lua_config;
struct filter_lua_config {
	liLuaState *LL;
	int class_ref;
};

typedef struct filter_lua_state filter_lua_state;
struct filter_lua_state {
	liLuaState *LL;
	int env_ref;
	int object_ref;
};

static filter_lua_state* filter_lua_state_new(liVRequest *vr, filter_lua_config *config) {
	int object_ref = LUA_NOREF;
	liServer *srv = vr->wrk->srv;
	lua_State *L = config->LL->L;
	int env_ref;

	li_lua_lock(config->LL);
	env_ref = li_lua_environment_create(config->LL, vr);
	li_lua_environment_activate(config->LL, env_ref); /* +1 */

	lua_rawgeti(L, LUA_REGISTRYINDEX, config->class_ref); /* +1 */
	li_lua_push_vrequest(L, vr); /* +1 */

	if (li_lua_call_object(srv, vr, L, "new", 2, 1, FALSE)) { /* -2, +1 on success */
		if (!lua_isnil(L, -1)) {
			object_ref = luaL_ref(L, LUA_REGISTRYINDEX); /* -1 */
		} else { /* no error; nil is interpreted as "don't need this filter for this request" */
			lua_pop(L, 1); /* -1 */
		}
	} else {
		VR_ERROR(vr, "%s", "li_lua_call_object failed");
		li_vrequest_error(vr);
	}

	li_lua_environment_restore(config->LL); /* -1 */
	if (LUA_NOREF == object_ref) {
		li_lua_environment_free(L, env_ref);
		env_ref = LUA_NOREF;
	}

	li_lua_unlock(config->LL);

	if (LUA_NOREF != object_ref) {
		filter_lua_state *state = g_slice_new0(filter_lua_state);
		state->LL = config->LL;
		state->object_ref = object_ref;
		state->env_ref = env_ref;

		return state;
	} else {
		return NULL;
	}
}

static void filter_lua_state_free(liVRequest *vr, filter_lua_state *state) {
	liServer *srv = NULL != vr ? vr->wrk->srv : NULL;
	lua_State *L = state->LL->L;

	li_lua_lock(state->LL);
	li_lua_environment_activate(state->LL, state->env_ref); /* +1 */

	lua_rawgeti(L, LUA_REGISTRYINDEX, state->object_ref); /* +1 */
	li_lua_push_vrequest(L, vr); /* +1 */
	li_lua_call_object(srv, vr, L, "finished", 2, 0, TRUE); /* -2 */
	li_lua_environment_restore(state->LL); /* -1 */

	luaL_unref(L, LUA_REGISTRYINDEX, state->object_ref);
	li_lua_environment_free(L, state->env_ref);

	li_lua_unlock(state->LL);

	g_slice_free(filter_lua_state, state);
}

static void filter_lua_free(liVRequest *vr, liFilter *f) {
	filter_lua_state *state = (filter_lua_state*) f->param;

	filter_lua_state_free(vr, state);
}

static liHandlerResult filter_lua_handle(liVRequest *vr, liFilter *f) {
	liServer *srv = NULL != vr ? vr->wrk->srv : NULL;
	filter_lua_state *state = (filter_lua_state*) f->param;
	lua_State *L = state->LL->L;
	liHandlerResult res;

	li_lua_lock(state->LL);
	li_lua_environment_activate(state->LL, state->env_ref); /* +1 */

	lua_rawgeti(L, LUA_REGISTRYINDEX, state->object_ref); /* +1 */
	li_lua_push_vrequest(L, vr); /* +1 */
	li_lua_push_chunkqueue(L, f->out); /* +1 */
	li_lua_push_chunkqueue(L, f->in); /* +1 */
	if (li_lua_call_object(srv, vr, L, "handle", 4, 1, FALSE)) { /* -4, +1 on success */
		res = LI_HANDLER_GO_ON;
		if (!lua_isnil(L, -1)) {
			int rc = lua_tointeger(L, -1);
			switch (rc) {
			case LI_HANDLER_GO_ON:
			case LI_HANDLER_COMEBACK:
			case LI_HANDLER_WAIT_FOR_EVENT:
			case LI_HANDLER_ERROR:
				res = rc;
				break;
			default:
				VR_ERROR(vr, "lua filter returned an error or an unknown value (%i)", rc);
				res = LI_HANDLER_ERROR;
			}
		}
		lua_pop(L, 1);
	} else {
		res = LI_HANDLER_ERROR;
	}

	li_lua_environment_restore(state->LL); /* -1 */
	li_lua_unlock(state->LL);

	return res;
}

static liHandlerResult filter_lua_in(liVRequest *vr, gpointer param, gpointer *context) {
	filter_lua_config *config = param;
	filter_lua_state *state = filter_lua_state_new(vr, config);
	UNUSED(context);

	if (state) {
		li_vrequest_add_filter_in(vr, filter_lua_handle, filter_lua_free, NULL, state);
	}

	return LI_HANDLER_GO_ON;
}

static liHandlerResult filter_lua_out(liVRequest *vr, gpointer param, gpointer *context) {
	filter_lua_config *config = param;
	filter_lua_state *state = filter_lua_state_new(vr, config);
	UNUSED(context);

	if (state) {
		li_vrequest_add_filter_out(vr, filter_lua_handle, filter_lua_free, NULL, state);
	}

	return LI_HANDLER_GO_ON;
}

static void filter_lua_action_free(liServer *srv, gpointer param) {
	filter_lua_config *config = param;
	lua_State *L = config->LL->L;
	UNUSED(srv);

	li_lua_lock(config->LL);
	luaL_unref(L, LUA_REGISTRYINDEX, config->class_ref);
	li_lua_unlock(config->LL);

	g_slice_free(filter_lua_config, config);
}

static int filter_lua_action_create(lua_State *L, liActionFuncCB act_cb) {
	liLuaState *LL = li_lua_state_get(L);
	liServer *srv = lua_touserdata(L, lua_upvalueindex(1));
	liAction *act;
	filter_lua_config *config;

	if (lua_gettop(L) != 1 || lua_isnil(L, 1)) {
		int n = lua_gettop(L);
		lua_pushstring(L, "expected exactly one parameter for lighty.filter_[in/out], got ");
		lua_pushinteger(L, n);
		lua_concat(L, 2);
		return lua_error(L);
	}

	config = g_slice_new0(filter_lua_config);
	config->LL = LL;
	config->class_ref = luaL_ref(L, LUA_REGISTRYINDEX);

	act = li_action_new_function(act_cb, NULL, filter_lua_action_free, config);
	return li_lua_push_action(srv, L, act);
}

static int filter_lua_in_create(lua_State *L) {
	return filter_lua_action_create(L, filter_lua_in);
}

static int filter_lua_out_create(lua_State *L) {
	return filter_lua_action_create(L, filter_lua_out);
}

void li_lua_init_filters(lua_State *L, liServer* srv) {
	lua_pushlightuserdata(L, srv);
	lua_pushcclosure(L, filter_lua_in_create, 1);
	lua_setfield(L, -2, "filter_in");

	lua_pushlightuserdata(L, srv);
	lua_pushcclosure(L, filter_lua_out_create, 1);
	lua_setfield(L, -2, "filter_out");
}

liFilter* li_lua_vrequest_add_filter_in(lua_State *L, liVRequest *vr, int state_ndx) {
	filter_lua_state *state;
	liLuaState *LL = li_lua_state_get(L);

	lua_pushvalue(L, state_ndx);

	state = g_slice_new0(filter_lua_state);
	state->LL = LL;
	state->object_ref = luaL_ref(L, LUA_REGISTRYINDEX);

	return li_vrequest_add_filter_in(vr, filter_lua_handle, filter_lua_free, NULL, state);
}

liFilter* li_lua_vrequest_add_filter_out(lua_State *L, liVRequest *vr, int state_ndx) {
	filter_lua_state *state;
	liLuaState *LL = li_lua_state_get(L);

	lua_pushvalue(L, state_ndx);

	state = g_slice_new0(filter_lua_state);
	state->LL = LL;
	state->object_ref = luaL_ref(L, LUA_REGISTRYINDEX);

	return li_vrequest_add_filter_out(vr, filter_lua_handle, filter_lua_free, NULL, state);
}
