/**
 * Copyright (C) 2010-2016 Daniel-Constantin Mierla (asipto.com)
 *
 * This file is part of Kamailio, a free SIP server.
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 *
 * Kamailio is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version
 *
 * Kamailio is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/stat.h>

#include "../../core/sr_module.h"
#include "../../core/dprint.h"
#include "../../core/ut.h"
#include "../../core/mem/mem.h"
#include "../../core/locking.h"
#include "../../core/data_lump.h"
#include "../../core/data_lump_rpl.h"
#include "../../core/strutils.h"
#include "../../core/rpc.h"
#include "../../core/rpc_lookup.h"

#include "app_lua_api.h"
#include "app_lua_kemi_export.h"

#define KSRVERSION "2.0"

#define KSR_APP_LUA_LOG_EXPORTS (1 << 0)

#if defined(LUA_VERSION_NUM) && LUA_VERSION_NUM >= 504
#define luaL_openlib ksr_luaL_openlib
#endif

extern int _ksr_app_lua_log_mode;

void lua_sr_kemi_register_libs(lua_State *L);

static app_lua_openlibs_f app_lua_openlibs_cb = NULL;

/**
 *
 */
int app_lua_openlibs_register(app_lua_openlibs_f rfunc)
{
	app_lua_openlibs_cb = rfunc;
	return 0;
}

/**
 * reload enabled param
 * default: 0 (off)
 */
static unsigned int _app_lua_sr_reload = 1;
/**
 *
 */
static sr_lua_env_t _sr_L_env;

/**
 *
 */
static int *_app_lua_sv = NULL;

/**
 * @return the static Lua env
 */
sr_lua_env_t *sr_lua_env_get(void)
{
	return &_sr_L_env;
}

/**
 *
 */
static sr_lua_load_t *_sr_lua_load_list = NULL;
/**
 * set of locks to manage the shared variable.
 */
static gen_lock_set_t *sr_lua_locks = NULL;
static sr_lua_script_ver_t *sr_lua_script_ver = NULL;


int lua_sr_alloc_script_ver(void)
{
	int size = _sr_L_env.nload;

	sr_lua_script_ver =
			(sr_lua_script_ver_t *)shm_malloc(sizeof(sr_lua_script_ver_t));
	if(sr_lua_script_ver == NULL) {
		SHM_MEM_ERROR;
		return -1;
	}

	sr_lua_script_ver->version =
			(unsigned int *)shm_malloc(sizeof(unsigned int) * size);
	if(sr_lua_script_ver->version == NULL) {
		SHM_MEM_ERROR;
		goto error;
	}
	memset(sr_lua_script_ver->version, 0, sizeof(unsigned int) * size);
	sr_lua_script_ver->len = size;

	if((sr_lua_locks = lock_set_alloc(size)) == 0) {
		LM_CRIT("failed to alloc lock set\n");
		goto error;
	}
	if(lock_set_init(sr_lua_locks) == 0) {
		LM_CRIT("failed to init lock set\n");
		goto error;
	}

	return 0;
error:
	if(sr_lua_script_ver != NULL) {
		if(sr_lua_script_ver->version != NULL) {
			shm_free(sr_lua_script_ver->version);
			sr_lua_script_ver->version = NULL;
		}
		shm_free(sr_lua_script_ver);
		sr_lua_script_ver = NULL;
	}
	if(sr_lua_locks != NULL) {
		lock_set_destroy(sr_lua_locks);
		lock_set_dealloc(sr_lua_locks);
		sr_lua_locks = NULL;
	}
	return -1;
}

/**
 *
 */
int sr_lua_load_script(char *script)
{
	sr_lua_load_t *li;

	li = (sr_lua_load_t *)pkg_malloc(sizeof(sr_lua_load_t));
	if(li == NULL) {
		PKG_MEM_ERROR;
		return -1;
	}
	memset(li, 0, sizeof(sr_lua_load_t));
	li->script = script;
	li->version = 0;
	li->next = _sr_lua_load_list;
	_sr_lua_load_list = li;
	_sr_L_env.nload += 1;
	LM_DBG("loaded script:[%s].\n", script);
	LM_DBG("Now there are %d scripts loaded\n", _sr_L_env.nload);

	return 0;
}

/**
 *
 */
int sr_lua_reload_module(unsigned int reload)
{
	LM_DBG("reload:%d\n", reload);
	if(reload != 0) {
		_app_lua_sr_reload = 1;
		LM_DBG("reload param activated!\n");
	} else {
		_app_lua_sr_reload = 0;
		LM_DBG("reload param inactivated!\n");
	}
	return 0;
}

#if defined(LUA_VERSION_NUM) && LUA_VERSION_NUM >= 504
/**
 *
 */
void ksr_luaL_openlib_mode(lua_State *L, const char *libname,
		const luaL_Reg *lfuncs, int nup, int mode)
{
	char modname[256];
	char *submod = NULL;
	int tidx = 0;
	if(mode) {
		/* support for registering 'module.submodule' functions
		 * - 'module' functions must be registered first  */
		if(strlen(libname) > 254) {
			LM_ERR("module name is too long [%s]\n", libname);
			return;
		}
		strcpy(modname, libname);
		submod = strchr(modname, '.');
		if(submod != NULL) {
			*submod = '\0';
			submod++;
		}
		lua_getglobal(L, modname);
		if(lua_isnil(L, -1)) {
			if(submod != NULL) {
				LM_ERR("main module not registered yet [%s]\n", libname);
				return;
			}
			lua_pop(L, 1);
			lua_newtable(L);
			luaL_setfuncs(L, lfuncs, 0);
			lua_setglobal(L, modname);
			return;
		}
		tidx = lua_gettop(L);
		lua_newtable(L);
		luaL_setfuncs(L, lfuncs, 0);
		lua_setfield(L, tidx, submod);
		return;
	}
	lua_newtable(L);
	luaL_setfuncs(L, lfuncs, 0);
	lua_setglobal(L, libname);
}

/**
 *
 */
void ksr_luaL_openlib(
		lua_State *L, const char *libname, const luaL_Reg *lfuncs, int nup)
{
	ksr_luaL_openlib_mode(L, libname, lfuncs, nup, 1);
}
#endif

/**
 *
 */
void lua_sr_openlibs(lua_State *L)
{
	if(app_lua_openlibs_cb != NULL) {
		app_lua_openlibs_cb(L);
	}
	lua_sr_kemi_register_libs(L);
}

/**
 *
 */
int lua_sr_init_mod(void)
{
	/* allocate shm */
	if(lua_sr_alloc_script_ver() < 0) {
		LM_CRIT("failed to alloc shm for version\n");
		return -1;
	}

	memset(&_sr_L_env, 0, sizeof(sr_lua_env_t));

	return 0;
}

/**
 *
 */
int lua_sr_init_probe(void)
{
	lua_State *L;
	char *txt;
	sr_lua_load_t *li;
	struct stat sbuf;

	L = luaL_newstate();
	if(L == NULL) {
		LM_ERR("cannot open lua\n");
		return -1;
	}
	luaL_openlibs(L);
	lua_sr_openlibs(L);

	/* force loading lua lib now */
	if(luaL_dostring(L, "KSR.x.probe()") != 0) {
		txt = (char *)lua_tostring(L, -1);
		LM_ERR("error initializing Lua: %s\n", (txt) ? txt : "unknown");
		lua_pop(L, 1);
		lua_close(L);
		return -1;
	}

	/* test if files to be loaded exist */
	if(_sr_lua_load_list != NULL) {
		li = _sr_lua_load_list;
		while(li) {
			if(stat(li->script, &sbuf) != 0) {
				/* file does not exist */
				LM_ERR("cannot find script: %s (wrong path?)\n", li->script);
				lua_close(L);
				return -1;
			}
			li = li->next;
		}
	}
	lua_close(L);
	LM_DBG("Lua probe was ok!\n");
	return 0;
}

/**
 *
 */
int lua_sr_init_child(void)
{
	sr_lua_load_t *li;
	int ret;
	char *txt;

	memset(&_sr_L_env, 0, sizeof(sr_lua_env_t));
	_sr_L_env.L = luaL_newstate();
	if(_sr_L_env.L == NULL) {
		LM_ERR("cannot open lua\n");
		return -1;
	}
	luaL_openlibs(_sr_L_env.L);
	lua_sr_openlibs(_sr_L_env.L);

	/* set KSR lib version */
#if LUA_VERSION_NUM >= 502
	lua_pushstring(_sr_L_env.L, KSRVERSION);
	lua_setglobal(_sr_L_env.L, "KSRVERSION");
#else
	lua_pushstring(_sr_L_env.L, "KSRVERSION");
	lua_pushstring(_sr_L_env.L, KSRVERSION);
	lua_settable(_sr_L_env.L, LUA_GLOBALSINDEX);
#endif
	if(_sr_lua_load_list != NULL) {
		_sr_L_env.LL = luaL_newstate();
		if(_sr_L_env.LL == NULL) {
			LM_ERR("cannot open lua loading state\n");
			return -1;
		}
		luaL_openlibs(_sr_L_env.LL);
		lua_sr_openlibs(_sr_L_env.LL);

		/* set SR lib version */
#if LUA_VERSION_NUM >= 502
		lua_pushstring(_sr_L_env.LL, KSRVERSION);
		lua_setglobal(_sr_L_env.LL, "KSRVERSION");
#else
		lua_pushstring(_sr_L_env.LL, "KSRVERSION");
		lua_pushstring(_sr_L_env.LL, KSRVERSION);
		lua_settable(_sr_L_env.LL, LUA_GLOBALSINDEX);
#endif
		/* force loading lua lib now */
		if(luaL_dostring(_sr_L_env.LL, "KSR.x.probe()") != 0) {
			txt = (char *)lua_tostring(_sr_L_env.LL, -1);
			LM_ERR("error initializing Lua: %s\n", (txt) ? txt : "unknown");
			lua_pop(_sr_L_env.LL, 1);
			lua_sr_destroy();
			return -1;
		}

		li = _sr_lua_load_list;
		while(li) {
			ret = luaL_dofile(_sr_L_env.LL, (const char *)li->script);
			if(ret != 0) {
				LM_ERR("failed to load Lua script: %s (err: %d)\n", li->script,
						ret);
				txt = (char *)lua_tostring(_sr_L_env.LL, -1);
				LM_ERR("error from Lua: %s\n", (txt) ? txt : "unknown");
				lua_pop(_sr_L_env.LL, 1);
				lua_sr_destroy();
				return -1;
			}
			li = li->next;
		}
	}
	LM_DBG("Lua initialized!\n");
	return 0;
}

/**
 *
 */
void lua_sr_destroy(void)
{
	if(_sr_L_env.L != NULL) {
		lua_close(_sr_L_env.L);
		_sr_L_env.L = NULL;
	}
	if(_sr_L_env.LL != NULL) {
		lua_close(_sr_L_env.LL);
		_sr_L_env.LL = NULL;
	}
	memset(&_sr_L_env, 0, sizeof(sr_lua_env_t));

	if(sr_lua_script_ver != NULL) {
		shm_free(sr_lua_script_ver->version);
		shm_free(sr_lua_script_ver);
	}

	if(sr_lua_locks != NULL) {
		lock_set_destroy(sr_lua_locks);
		lock_set_dealloc(sr_lua_locks);
		sr_lua_locks = 0;
	}

	if(_app_lua_sv != NULL) {
		pkg_free(_app_lua_sv);
		_app_lua_sv = 0;
	}
}

/**
 *
 */
int lua_sr_list_script(sr_lua_load_t **list)
{
	*list = _sr_lua_load_list;
	return 0;
}

/**
 * Mark script in pos to be reloaded
 * pos -1: reload all scripts
 */
int lua_sr_reload_script(int pos)
{
	int i, len = sr_lua_script_ver->len;
	if(_sr_lua_load_list != NULL) {
		if(!sr_lua_script_ver) {
			LM_CRIT("shm for version not allocated\n");
			return -1;
		}
		if(_app_lua_sr_reload == 0) {
			LM_ERR("reload is not activated\n");
			return -3;
		}
		if(pos < 0) {
			// let's mark all the scripts to be reloaded
			for(i = 0; i < len; i++) {
				lock_set_get(sr_lua_locks, i);
				sr_lua_script_ver->version[i] += 1;
				lock_set_release(sr_lua_locks, i);
			}
		} else {
			if(pos < len) {
				lock_set_get(sr_lua_locks, pos);
				sr_lua_script_ver->version[pos] += 1;
				lock_set_release(sr_lua_locks, pos);
				LM_DBG("pos: %d set to reloaded\n", pos);
			} else {
				LM_ERR("pos out of range\n");
				return -2;
			}
		}
		return 0;
	}
	LM_ERR("No script loaded\n");
	return -1;
}

/**
 * Checks if loaded version matches the shared
 * counter. If not equal reloads the script.
 */
int sr_lua_reload_script(void)
{
	sr_lua_load_t *li = _sr_lua_load_list;
	int ret, i;
	char *txt;
	int sv_len = sr_lua_script_ver->len;

	if(li == NULL) {
		LM_DBG("No script loaded\n");
		return 0;
	}

	if(_app_lua_sv == NULL) {
		_app_lua_sv = (int *)pkg_malloc(sizeof(int) * sv_len);
		if(_app_lua_sv == NULL) {
			PKG_MEM_ERROR;
			return -1;
		}
	}

	for(i = 0; i < sv_len; i++) {
		lock_set_get(sr_lua_locks, i);
		_app_lua_sv[i] = sr_lua_script_ver->version[i];
		lock_set_release(sr_lua_locks, i);

		if(li->version != _app_lua_sv[i]) {
			LM_DBG("loaded version:%d needed: %d Let's reload <%s>\n",
					li->version, _app_lua_sv[i], li->script);
			ret = luaL_dofile(_sr_L_env.LL, (const char *)li->script);
			if(ret != 0) {
				LM_ERR("failed to load Lua script: %s (err: %d)\n", li->script,
						ret);
				txt = (char *)lua_tostring(_sr_L_env.LL, -1);
				LM_ERR("error from Lua: %s\n", (txt) ? txt : "unknown");
				lua_pop(_sr_L_env.LL, 1);
				return -1;
			}
			li->version = _app_lua_sv[i];
			LM_DBG("<%s> set to version %d\n", li->script, li->version);
		} else
			LM_DBG("No need to reload [%s] is version %d\n", li->script,
					li->version);
		li = li->next;
	}
	return 1;
}

/**
 *
 */
int lua_sr_initialized(void)
{
	if(_sr_L_env.L == NULL)
		return 0;

	return 1;
}

/**
 *
 */
int app_lua_return_int(lua_State *L, int v)
{
	lua_pushinteger(L, v);
	return 1;
}

/**
 *
 */
int app_lua_return_error(lua_State *L)
{
	lua_pushinteger(L, -1);
	return 1;
}

/**
 *
 */
int app_lua_return_boolean(lua_State *L, int b)
{
	if(b == SRLUA_FALSE)
		lua_pushboolean(L, SRLUA_FALSE);
	else
		lua_pushboolean(L, SRLUA_TRUE);
	return 1;
}

/**
 *
 */
int app_lua_return_false(lua_State *L)
{
	lua_pushboolean(L, SRLUA_FALSE);
	return 1;
}

/**
 *
 */
int app_lua_return_true(lua_State *L)
{
	lua_pushboolean(L, SRLUA_TRUE);
	return 1;
}

/**
 *
 */
int app_lua_dostring(sip_msg_t *msg, char *script)
{
	int ret;
	char *txt;
	sip_msg_t *bmsg;

	LM_DBG("executing Lua string: [[%s]]\n", script);
	LM_DBG("lua top index is: %d\n", lua_gettop(_sr_L_env.L));
	bmsg = _sr_L_env.msg;
	_sr_L_env.msg = msg;
	ret = luaL_dostring(_sr_L_env.L, script);
	if(ret != 0) {
		txt = (char *)lua_tostring(_sr_L_env.L, -1);
		LM_ERR("error from Lua: %s\n", (txt) ? txt : "unknown");
		lua_pop(_sr_L_env.L, 1);
	}
	_sr_L_env.msg = bmsg;
	return (ret == 0) ? 1 : -1;
}

/**
 *
 */
int app_lua_dofile(sip_msg_t *msg, char *script)
{
	int ret;
	char *txt;
	sip_msg_t *bmsg;

	LM_DBG("executing Lua file: [[%s]]\n", script);
	LM_DBG("lua top index is: %d\n", lua_gettop(_sr_L_env.L));
	bmsg = _sr_L_env.msg;
	_sr_L_env.msg = msg;
	ret = luaL_dofile(_sr_L_env.L, script);
	if(ret != 0) {
		txt = (char *)lua_tostring(_sr_L_env.L, -1);
		LM_ERR("error from Lua: %s\n", (txt) ? txt : "unknown");
		lua_pop(_sr_L_env.L, 1);
	}
	_sr_L_env.msg = bmsg;
	return (ret == 0) ? 1 : -1;
}

/**
 *
 */
int app_lua_runstring(sip_msg_t *msg, char *script)
{
	int ret;
	char *txt;
	sip_msg_t *bmsg;

	if(_sr_L_env.LL == NULL) {
		LM_ERR("lua loading state not initialized (call: %s)\n", script);
		return -1;
	}

	LM_DBG("running Lua string: [[%s]]\n", script);
	LM_DBG("lua top index is: %d\n", lua_gettop(_sr_L_env.LL));
	bmsg = _sr_L_env.msg;
	_sr_L_env.msg = msg;
	ret = luaL_dostring(_sr_L_env.LL, script);
	if(ret != 0) {
		txt = (char *)lua_tostring(_sr_L_env.LL, -1);
		LM_ERR("error from Lua: %s\n", (txt) ? txt : "unknown");
		lua_pop(_sr_L_env.LL, 1);
	}
	_sr_L_env.msg = bmsg;
	return (ret == 0) ? 1 : -1;
}

/**
 *
 */
static str _sr_kemi_lua_exit_string = str_init("~~ksr~exit~~");

/**
 *
 */
str *sr_kemi_lua_exit_string_get(void)
{
	return &_sr_kemi_lua_exit_string;
}

/**
 *
 */
int app_lua_run_ex(
		sip_msg_t *msg, char *func, char *p1, char *p2, char *p3, int emode)
{
	int n;
	int ret;
	str txt;
	sip_msg_t *bmsg;
	int ltop;

	if(_sr_L_env.LL == NULL) {
		LM_ERR("lua loading state not initialized (call: %s)\n", func);
		return -1;
	}
	if(_app_lua_sr_reload != 0) {
		/* check the script version loaded */
		if(!sr_lua_reload_script()) {
			LM_ERR("lua reload failed\n");
			return -1;
		}
	} else
		LM_DBG("reload deactivated\n");
	LM_DBG("executing Lua function: [[%s]]\n", func);
	ltop = lua_gettop(_sr_L_env.LL);
	LM_DBG("lua top index is: %d\n", ltop);
	lua_getglobal(_sr_L_env.LL, func);
	if(!lua_isfunction(_sr_L_env.LL, -1)) {
		if(emode) {
			LM_ERR("no such function [%s] in lua scripts\n", func);
			LM_ERR("top stack type [%d - %s]\n", lua_type(_sr_L_env.LL, -1),
					lua_typename(_sr_L_env.LL, lua_type(_sr_L_env.LL, -1)));
			txt.s = (char *)lua_tostring(_sr_L_env.LL, -1);
			LM_ERR("error from Lua: %s\n", (txt.s) ? txt.s : "unknown");
			/* restores the original stack size */
			lua_settop(_sr_L_env.LL, ltop);
			return -1;
		} else {
			/* restores the original stack size */
			lua_settop(_sr_L_env.LL, ltop);
			return 1;
		}
	}
	n = 0;
	if(p1 != NULL) {
		lua_pushstring(_sr_L_env.LL, p1);
		n++;
		if(p2 != NULL) {
			lua_pushstring(_sr_L_env.LL, p2);
			n++;
			if(p3 != NULL) {
				lua_pushstring(_sr_L_env.LL, p3);
				n++;
			}
		}
	}
	bmsg = _sr_L_env.msg;
	_sr_L_env.msg = msg;
	ret = lua_pcall(_sr_L_env.LL, n, 0, 0);
	_sr_L_env.msg = bmsg;
	if(ret != 0) {
		txt.s = (char *)lua_tostring(_sr_L_env.LL, -1);
		n = 0;
		if(txt.s != NULL) {
			for(n = 0;
					txt.s[n] != '\0' && _sr_kemi_lua_exit_string.s[n] != '\0';
					n++) {
				if(txt.s[n] != _sr_kemi_lua_exit_string.s[n])
					break;
			}
			if(txt.s[n] != '\0' || _sr_kemi_lua_exit_string.s[n] != '\0') {
				LM_ERR("error from Lua: %s\n", txt.s);
				n = 0;
			} else {
				LM_DBG("ksr error call from Lua: %s\n", txt.s);
				n = 1;
			}
		} else {
			LM_ERR("error from Lua: unknown\n");
		}
		lua_pop(_sr_L_env.LL, 1);
		if(n == 1) {
			/* restores the original stack size */
			lua_settop(_sr_L_env.LL, ltop);
			return 1;
		} else {
			LM_ERR("error executing: %s (err: %d)\n", func, ret);
			/* restores the original stack size */
			lua_settop(_sr_L_env.LL, ltop);
			return -1;
		}
	}

	/* restores the original stack size */
	lua_settop(_sr_L_env.LL, ltop);

	return 1;
}

/**
 *
 */
int app_lua_run(sip_msg_t *msg, char *func, char *p1, char *p2, char *p3)
{
	return app_lua_run_ex(msg, func, p1, p2, p3, 1);
}

void app_lua_dump_stack(lua_State *L)
{
	int i;
	int t;
	int top;

	top = lua_gettop(L);

	LM_DBG("lua stack top index: %d\n", top);
	for(i = 1; i <= top; i++) {
		t = lua_type(L, i);
		switch(t) {
			case LUA_TSTRING: /* strings */
				LM_DBG("[%i:s> %s\n", i, lua_tostring(L, i));
				break;
			case LUA_TBOOLEAN: /* booleans */
				LM_DBG("[%i:b> %s\n", i,
						lua_toboolean(L, i) ? "true" : "false");
				break;
			case LUA_TNUMBER: /* numbers */
				LM_DBG("[%i:n> %g\n", i, lua_tonumber(L, i));
				break;
			default: /* other values */
				LM_DBG("[%i:t> %s\n", i, lua_typename(L, t));
				break;
		}
	}
}

/**
 *
 */
int sr_kemi_lua_return_int(lua_State *L, sr_kemi_t *ket, int rc)
{
	if(ket->rtype == SR_KEMIP_INT) {
		lua_pushinteger(L, rc);
		return 1;
	}
	if(ket->rtype == SR_KEMIP_BOOL && rc != SR_KEMI_FALSE) {
		return app_lua_return_true(L);
	}
	return app_lua_return_false(L);
}

void sr_kemi_lua_push_dict_item(lua_State *L, sr_kemi_dict_item_t *item);

/**
 * creates and push a table to the lua stack with
 * the elements of the list
 */
void sr_kemi_lua_push_array(lua_State *L, sr_kemi_dict_item_t *item)
{
	int i = 1;
	sr_kemi_dict_item_t *k;
	if(!item) {
		LM_CRIT("BUG: dict field empty\n");
		return;
	}
	if(item->vtype == SR_KEMIP_ARRAY) {
		k = item->v.dict;
	} else {
		k = item;
	}
	if(k) {
		lua_newtable(L);
	}
	while(k) {
		lua_pushnumber(L, i++);
		sr_kemi_lua_push_dict_item(L, k);
		lua_settable(L, -3);
		k = k->next;
	}
}

void sr_kemi_lua_push_dict(lua_State *L, sr_kemi_dict_item_t *item)
{
	sr_kemi_dict_item_t *k = item;
	if(!item) {
		LM_CRIT("BUG: dict field empty\n");
		return;
	}
	lua_newtable(L);
	while(k) {
		sr_kemi_lua_push_dict_item(L, k->v.dict);
		lua_setfield(L, -2, k->name.s);
		k = k->next;
	}
}

void sr_kemi_lua_push_dict_item(lua_State *L, sr_kemi_dict_item_t *item)
{
	if(!item) {
		LM_CRIT("BUG: dict field empty\n");
		return;
	}
	switch(item->vtype) {
		case SR_KEMIP_NONE:
			LM_CRIT("BUG: vtype is NONE\n");
			lua_pushnil(L);
			break;
		case SR_KEMIP_INT:
			lua_pushinteger(L, item->v.n);
			break;
		case SR_KEMIP_STR:
			lua_pushlstring(L, item->v.s.s, item->v.s.len);
			break;
		case SR_KEMIP_BOOL:
			if(item->v.n != SR_KEMI_FALSE) {
				lua_pushboolean(L, SRLUA_TRUE);
			} else {
				lua_pushboolean(L, SRLUA_FALSE);
			}
			break;
		case SR_KEMIP_NULL:
			lua_pushnil(L);
			break;
		case SR_KEMIP_ARRAY:
			sr_kemi_lua_push_array(L, item);
			break;
		case SR_KEMIP_DICT:
			sr_kemi_lua_push_dict(L, item);
			break;
		default:
			LM_DBG("unknown type:%d\n", item->vtype);
			/* unknown type - return false */
			lua_pushboolean(L, SRLUA_FALSE);
	}
}

/**
 *
 */
int sr_kemi_lua_return_xval(lua_State *L, sr_kemi_t *ket, sr_kemi_xval_t *rx)
{
	if(!rx) {
		LM_CRIT("BUG: xval field empty\n");
		return 0;
	}
	switch(rx->vtype) {
		case SR_KEMIP_NONE:
			return 0;
		case SR_KEMIP_INT:
			lua_pushinteger(L, rx->v.n);
			return 1;
		case SR_KEMIP_LONG:
			lua_pushnumber(L, rx->v.l);
			return 1;
		case SR_KEMIP_STR:
			lua_pushlstring(L, rx->v.s.s, rx->v.s.len);
			return 1;
		case SR_KEMIP_BOOL:
			if(rx->v.n != SR_KEMI_FALSE) {
				lua_pushboolean(L, SRLUA_TRUE);
			} else {
				lua_pushboolean(L, SRLUA_FALSE);
			}
			return 1;
		case SR_KEMIP_XVAL:
			/* unknown content - return false */
			lua_pushboolean(L, SRLUA_FALSE);
			return 1;
		case SR_KEMIP_NULL:
			lua_pushnil(L);
			return 1;
		case SR_KEMIP_ARRAY:
			sr_kemi_lua_push_array(L, rx->v.dict);
			sr_kemi_xval_free(rx);
			return 1;
		case SR_KEMIP_DICT:
			sr_kemi_lua_push_dict_item(L, rx->v.dict);
			sr_kemi_xval_free(rx);
			return 1;
		default:
			/* unknown type - return false */
			lua_pushboolean(L, SRLUA_FALSE);
			return 1;
	}
}

/**
 *
 */
int sr_kemi_lua_exec_func_ex(lua_State *L, sr_kemi_t *ket, int pdelta)
{
	int i;
	int argc;
	int ret;
	str *fname;
	str *mname;
	sr_kemi_xval_t vps[SR_KEMI_PARAMS_MAX];
	sr_lua_env_t *env_L;
	sr_kemi_xval_t *xret;

	env_L = sr_lua_env_get();

	if(env_L == NULL || env_L->msg == NULL || ket == NULL) {
		LM_ERR("invalid Lua environment attributes or parameters\n");
		return app_lua_return_false(L);
	}

	fname = &ket->fname;
	mname = &ket->mname;

	argc = lua_gettop(L);
	if(argc == pdelta && ket->ptypes[0] == SR_KEMIP_NONE) {
		if(ket->rtype == SR_KEMIP_XVAL) {
			xret = ((sr_kemi_xfm_f)(ket->func))(env_L->msg);
			return sr_kemi_lua_return_xval(L, ket, xret);
		} else {
			ret = ((sr_kemi_fm_f)(ket->func))(env_L->msg);
			return sr_kemi_lua_return_int(L, ket, ret);
		}
	}
	if(argc == pdelta && ket->ptypes[0] != SR_KEMIP_NONE) {
		LM_ERR("invalid number of parameters for: %.*s.%.*s\n", mname->len,
				mname->s, fname->len, fname->s);
		return app_lua_return_false(L);
	}

	if(argc > SR_KEMI_PARAMS_MAX + pdelta) {
		LM_ERR("too many parameters for: %.*s.%.*s\n", mname->len, mname->s,
				fname->len, fname->s);
		return app_lua_return_false(L);
	}

	memset(vps, 0, SR_KEMI_PARAMS_MAX * sizeof(sr_kemi_xval_t));
	for(i = 0; i < SR_KEMI_PARAMS_MAX; i++) {
		if(ket->ptypes[i] == SR_KEMIP_NONE) {
			break;
		}
		if(argc < i + pdelta + 1) {
			LM_ERR("not enough parameters for: %.*s.%.*s\n", mname->len,
					mname->s, fname->len, fname->s);
			return app_lua_return_false(L);
		}
		if(ket->ptypes[i] == SR_KEMIP_STR) {
			vps[i].vtype = SR_KEMIP_STR;
			vps[i].v.s.s = (char *)lua_tostring(L, i + pdelta + 1);
			if(vps[i].v.s.s != NULL) {
				if(lua_isstring(L, i + pdelta + 1)) {
#if LUA_VERSION_NUM > 501
					vps[i].v.s.len = lua_rawlen(L, i + pdelta + 1);
#else
					vps[i].v.s.len = lua_strlen(L, i + pdelta + 1);
#endif
				} else {
					vps[i].v.s.len = strlen(vps[i].v.s.s);
				}
			} else {
				vps[i].v.s.len = 0;
			}
			LM_DBG("param[%d] for: %.*s is str: %.*s\n", i, fname->len,
					fname->s, vps[i].v.s.len, vps[i].v.s.s);
		} else if(ket->ptypes[i] == SR_KEMIP_INT) {
			vps[i].vtype = SR_KEMIP_INT;
			vps[i].v.n = lua_tointeger(L, i + pdelta + 1);
			LM_DBG("param[%d] for: %.*s is int: %d\n", i, fname->len, fname->s,
					vps[i].v.n);
		} else if(ket->ptypes[i] == SR_KEMIP_LONG) {
			vps[i].vtype = SR_KEMIP_LONG;
			vps[i].v.l = (long)lua_tonumber(L, i + pdelta + 1);
			LM_DBG("param[%d] for: %.*s is long: %ld\n", i, fname->len,
					fname->s, vps[i].v.l);
		} else {
			LM_ERR("unknown parameter type %d (%d)\n", ket->ptypes[i], i);
			return app_lua_return_false(L);
		}
	}

	xret = sr_kemi_exec_func(ket, env_L->msg, i, vps);
	return sr_kemi_lua_return_xval(L, ket, xret);
}

/**
 *
 */
int sr_kemi_lua_exec_midx_func(lua_State *L, str *mname, int midx, str *fname)
{
	int pdelta;
	sr_kemi_t *ket = NULL;
	sr_lua_env_t *env_L;

	env_L = sr_lua_env_get();

	if(env_L == NULL || env_L->msg == NULL) {
		LM_ERR("invalid Lua environment attributes\n");
		return app_lua_return_false(L);
	}

	ket = sr_kemi_lookup(mname, midx, fname);
	if(ket == NULL) {
		LM_ERR("cannot find function (%d): %.*s.%.*s\n", midx,
				(mname && mname->len > 0) ? mname->len : 0,
				(mname && mname->len > 0) ? mname->s : "", fname->len,
				fname->s);
		return app_lua_return_false(L);
	}
	if(mname->len <= 0) {
		pdelta = 1;
	} else {
		pdelta = 3;
	}
	return sr_kemi_lua_exec_func_ex(L, ket, pdelta);
}

/**
 *
 */
int sr_kemi_lua_exec_func(lua_State *L, int eidx)
{
	sr_kemi_t *ket;
	int ret;
	struct timeval tvb = {0}, tve = {0};
	struct timezone tz;
	unsigned int tdiff;
	lua_Debug dinfo;

	ket = sr_kemi_lua_export_get(eidx);
	if(unlikely(cfg_get(core, core_cfg, latency_limit_action) > 0)
			&& is_printable(cfg_get(core, core_cfg, latency_log))) {
		gettimeofday(&tvb, &tz);
	}

	ret = sr_kemi_lua_exec_func_ex(L, ket, 0);

	if(unlikely(cfg_get(core, core_cfg, latency_limit_action) > 0)
			&& is_printable(cfg_get(core, core_cfg, latency_log))) {
		gettimeofday(&tve, &tz);
		tdiff = (tve.tv_sec - tvb.tv_sec) * 1000000
				+ (tve.tv_usec - tvb.tv_usec);
		if(tdiff >= cfg_get(core, core_cfg, latency_limit_action)) {
			memset(&dinfo, 0, sizeof(lua_Debug));
			if(lua_getstack(L, 1, &dinfo) > 0
					&& lua_getinfo(L, "nSl", &dinfo) > 0) {
				LOG(cfg_get(core, core_cfg, latency_log),
						"alert - action KSR.%s%s%s(...)"
						" took too long [%u us] (%s:%d - %s [%s])\n",
						(ket->mname.len > 0) ? ket->mname.s : "",
						(ket->mname.len > 0) ? "." : "", ket->fname.s, tdiff,
						(dinfo.short_src[0]) ? dinfo.short_src : "<unknown>",
						dinfo.currentline,
						(dinfo.name) ? dinfo.name : "<unknown>",
						(dinfo.what) ? dinfo.what : "<unknown>");
			} else {
				LOG(cfg_get(core, core_cfg, latency_log),
						"alert - action KSR.%s%s%s(...)"
						" took too long [%u us]\n",
						(ket->mname.len > 0) ? ket->mname.s : "",
						(ket->mname.len > 0) ? "." : "", ket->fname.s, tdiff);
			}
		}
	}

	return ret;
}

/**
 *
 */
static int sr_kemi_lua_modf(lua_State *L)
{
	int ret;
	char *luav[MAX_ACTIONS];
	char *argv[MAX_ACTIONS];
	int argc;
	int i;
	int mod_type;
	struct run_act_ctx ra_ctx;
	struct action *act;
	ksr_cmd_export_t *expf;
	sr_lua_env_t *env_L;

	ret = 1;
	act = NULL;
	argc = 0;
	memset(luav, 0, MAX_ACTIONS * sizeof(char *));
	memset(argv, 0, MAX_ACTIONS * sizeof(char *));
	env_L = sr_lua_env_get();
	if(env_L->msg == NULL)
		goto error;

#if 0
	app_lua_dump_stack(L);
#endif
	argc = lua_gettop(L);
	if(argc == 0) {
		LM_ERR("name of module function not provided\n");
		goto error;
	}
	if(argc >= MAX_ACTIONS) {
		LM_ERR("too many parameters\n");
		goto error;
	}
	/* first is function name, then parameters */
	for(i = 1; i <= argc; i++) {
		if(!lua_isstring(L, i)) {
			LM_ERR("invalid parameter type (%d)\n", i);
			goto error;
		}
		luav[i - 1] = (char *)lua_tostring(L, i);
	}
	/* pkg copy only parameters */
	for(i = 1; i < MAX_ACTIONS; i++) {
		if(luav[i] != NULL) {
			argv[i] = (char *)pkg_malloc(strlen(luav[i]) + 1);
			if(argv[i] == NULL) {
				PKG_MEM_ERROR;
				goto error;
			}
			strcpy(argv[i], luav[i]);
		}
	}

	expf = find_export_record(luav[0], argc - 1, 0);
	if(expf == NULL) {
		LM_ERR("function '%s' is not available\n", luav[0]);
		goto error;
	}
	/* check fixups */
	if(expf->fixup != NULL && expf->free_fixup == NULL) {
		LM_ERR("function '%s' has fixup - cannot be used\n", luav[0]);
		goto error;
	}
	switch(expf->param_no) {
		case 0:
			mod_type = MODULE0_T;
			break;
		case 1:
			mod_type = MODULE1_T;
			break;
		case 2:
			mod_type = MODULE2_T;
			break;
		case 3:
			mod_type = MODULE3_T;
			break;
		case 4:
			mod_type = MODULE4_T;
			break;
		case 5:
			mod_type = MODULE5_T;
			break;
		case 6:
			mod_type = MODULE6_T;
			break;
		case VAR_PARAM_NO:
			mod_type = MODULEX_T;
			break;
		default:
			LM_ERR("unknown/bad definition for function '%s' (%d params)\n",
					luav[0], expf->param_no);
			goto error;
	}

	act = mk_action(mod_type, argc + 1 /* number of (type, value) pairs */,
			MODEXP_ST, expf,	 /* function */
			NUMBER_ST, argc - 1, /* parameter number */
			STRING_ST, argv[1],	 /* param. 1 */
			STRING_ST, argv[2],	 /* param. 2 */
			STRING_ST, argv[3],	 /* param. 3 */
			STRING_ST, argv[4],	 /* param. 4 */
			STRING_ST, argv[5],	 /* param. 5 */
			STRING_ST, argv[6]	 /* param. 6 */
	);

	if(act == NULL) {
		LM_ERR("action structure could not be created for '%s'\n", luav[0]);
		goto error;
	}

	/* handle fixups */
	if(expf->fixup) {
		if(argc == 1) { /* no parameters */
			if(expf->fixup(0, 0) < 0) {
				LM_ERR("Error in fixup (0) for '%s'\n", luav[0]);
				goto error;
			}
		} else {
			for(i = 1; i < argc; i++) {
				if(expf->fixup(&(act->val[i + 1].u.data), i) < 0) {
					LM_ERR("Error in fixup (%d) for '%s'\n", i, luav[0]);
					goto error;
				}
				act->val[i + 1].type = MODFIXUP_ST;
			}
		}
	}
	init_run_actions_ctx(&ra_ctx);
	ret = do_action(&ra_ctx, act, env_L->msg);

	/* free fixups */
	if(expf->fixup) {
		for(i = 1; i < argc; i++) {
			if((act->val[i + 1].type == MODFIXUP_ST)
					&& (act->val[i + 1].u.data)) {
				expf->free_fixup(&(act->val[i + 1].u.data), i);
			}
		}
	}
	pkg_free(act);
	for(i = 0; i < MAX_ACTIONS; i++) {
		if(argv[i] != NULL)
			pkg_free(argv[i]);
		argv[i] = 0;
	}
	lua_pushinteger(L, ret);
	return 1;

error:
	if(act != NULL)
		pkg_free(act);
	for(i = 0; i < MAX_ACTIONS; i++) {
		if(argv[i] != NULL)
			pkg_free(argv[i]);
		argv[i] = 0;
	}
	lua_pushinteger(L, -1);
	return 1;
}

/**
 *
 */
static int sr_kemi_lua_exit(lua_State *L)
{
	str *s;

	LM_DBG("script exit call\n");
	s = sr_kemi_lua_exit_string_get();
	lua_getglobal(L, "error");
	lua_pushstring(L, s->s);
	lua_call(L, 1, 0);
	return 0;
}

/**
 *
 */
static int sr_kemi_lua_drop(lua_State *L)
{
	str *s;

	LM_DBG("script drop call\n");
	sr_kemi_core_set_drop(NULL);
	s = sr_kemi_lua_exit_string_get();
	lua_getglobal(L, "error");
	lua_pushstring(L, s->s);
	lua_call(L, 1, 0);
	return 0;
}

/**
 *
 */
static int sr_kemi_lua_probe(lua_State *L)
{
	LM_DBG("someone probing from lua\n");
	return 0;
}

/**
 *
 */
static const luaL_Reg _sr_kemi_x_Map[] = {{"modf", sr_kemi_lua_modf},
		{"exit", sr_kemi_lua_exit}, {"drop", sr_kemi_lua_drop},
		{"probe", sr_kemi_lua_probe}, {NULL, NULL}};


/**
 *
 */
luaL_Reg *_sr_KSRMethods = NULL;

#define SR_LUA_KSR_MODULES_SIZE 256
#define SR_LUA_KSR_METHODS_SIZE \
	(SR_KEMI_LUA_EXPORT_SIZE + SR_LUA_KSR_MODULES_SIZE)

/**
 *
 */
void lua_sr_kemi_register_libs(lua_State *L)
{
	luaL_Reg *_sr_crt_KSRMethods = NULL;
	sr_kemi_module_t *emods = NULL;
	int emods_size = 0;
	int i;
	int k;
	int n;
	char mname[128];

#if 0
	/* dynamic lookup on function name */
	lua_sr_kemi_register_core(L);
	lua_sr_kemi_register_modules(L);
#endif

	_sr_KSRMethods = malloc(SR_LUA_KSR_METHODS_SIZE * sizeof(luaL_Reg));
	if(_sr_KSRMethods == NULL) {
		LM_ERR("no more pkg memory\n");
		return;
	}
	memset(_sr_KSRMethods, 0, SR_LUA_KSR_METHODS_SIZE * sizeof(luaL_Reg));

	emods_size = sr_kemi_modules_size_get();
	emods = sr_kemi_modules_get();

	n = 0;
	_sr_crt_KSRMethods = _sr_KSRMethods;
	if(emods_size == 0 || emods[0].kexp == NULL) {
		LM_ERR("no kemi exports registered\n");
		return;
	}

	for(i = 0; emods[0].kexp[i].func != NULL; i++) {
		if(_ksr_app_lua_log_mode & KSR_APP_LUA_LOG_EXPORTS) {
			LM_DBG("exporting KSR.%s(...)\n", emods[0].kexp[i].fname.s);
		}
		_sr_crt_KSRMethods[i].name = emods[0].kexp[i].fname.s;
		_sr_crt_KSRMethods[i].func =
				sr_kemi_lua_export_associate(&emods[0].kexp[i]);
		if(_sr_crt_KSRMethods[i].func == NULL) {
			LM_ERR("failed to associate kemi function with lua export\n");
			free(_sr_KSRMethods);
			_sr_KSRMethods = NULL;
			return;
		}
		n++;
	}

	luaL_openlib(L, "KSR", _sr_crt_KSRMethods, 0);

	luaL_openlib(L, "KSR.x", _sr_kemi_x_Map, 0);

	/* registered kemi modules */
	if(emods_size > 1) {
		for(k = 1; k < emods_size; k++) {
			n++;
			_sr_crt_KSRMethods = _sr_KSRMethods + n;
			snprintf(mname, 128, "KSR.%s", emods[k].kexp[0].mname.s);
			for(i = 0; emods[k].kexp[i].func != NULL; i++) {
				if(_ksr_app_lua_log_mode & KSR_APP_LUA_LOG_EXPORTS) {
					LM_DBG("exporting %s.%s(...)\n", mname,
							emods[k].kexp[i].fname.s);
				}
				_sr_crt_KSRMethods[i].name = emods[k].kexp[i].fname.s;
				_sr_crt_KSRMethods[i].func =
						sr_kemi_lua_export_associate(&emods[k].kexp[i]);
				if(_sr_crt_KSRMethods[i].func == NULL) {
					LM_ERR("failed to associate kemi function with func "
						   "export\n");
					free(_sr_KSRMethods);
					_sr_KSRMethods = NULL;
					return;
				}
				n++;
			}
			if(!lua_checkstack(L, i + 8)) {
				LM_ERR("not enough Lua stack capacity\n");
				exit(-1);
			}
			luaL_openlib(L, mname, _sr_crt_KSRMethods, 0);
			if(_ksr_app_lua_log_mode & KSR_APP_LUA_LOG_EXPORTS) {
				LM_DBG("initializing kemi sub-module: %s (%s) (%d/%d/%d)\n",
						mname, emods[k].kexp[0].mname.s, i, k, n);
			}
		}
	}
	LM_DBG("module 'KSR' has been initialized (%d/%d)\n", emods_size, n);
}

static const char *app_lua_rpc_api_list_doc[2] = {
		"list kemi exports to lua", 0};

static void app_lua_rpc_api_list(rpc_t *rpc, void *ctx)
{
	int i;
	int n;
	sr_kemi_t *ket;
	void *th;
	void *sh;
	void *ih;

	if(rpc->add(ctx, "{", &th) < 0) {
		rpc->fault(ctx, 500, "Internal error root reply");
		return;
	}

	/* count the number of exported functions */
	n = 0;
	for(i = 0; i < SR_KEMI_LUA_EXPORT_SIZE; i++) {
		ket = sr_kemi_lua_export_get(i);
		if(ket == NULL)
			continue;
		n++;
	}

	if(rpc->struct_add(th, "d[", "msize", n, "methods", &ih) < 0) {
		rpc->fault(ctx, 500, "Internal error array structure");
		return;
	}
	for(i = 0; i < SR_KEMI_LUA_EXPORT_SIZE; i++) {
		ket = sr_kemi_lua_export_get(i);
		if(ket == NULL)
			continue;
		if(rpc->struct_add(ih, "{", "func", &sh) < 0) {
			rpc->fault(ctx, 500, "Internal error internal structure");
			return;
		}
		if(rpc->struct_add(sh, "SSSS", "ret",
				   sr_kemi_param_map_get_name(ket->rtype), "module",
				   &ket->mname, "name", &ket->fname, "params",
				   sr_kemi_param_map_get_params(ket->ptypes))
				< 0) {
			LM_ERR("failed to add the structure with attributes (%d)\n", i);
			rpc->fault(ctx, 500, "Internal error creating dest struct");
			return;
		}
	}
}

/*** RPC implementation ***/

static const char *app_lua_rpc_reload_doc[2] = {"Reload lua script", 0};

static const char *app_lua_rpc_list_doc[2] = {"list lua scripts", 0};

static void app_lua_rpc_reload(rpc_t *rpc, void *ctx)
{
	int pos = -1;

	rpc->scan(ctx, "*d", &pos);
	LM_DBG("selected index: %d\n", pos);
	if(lua_sr_reload_script(pos) < 0)
		rpc->fault(ctx, 500, "Reload Failed");
	return;
}

static void app_lua_rpc_list(rpc_t *rpc, void *ctx)
{
	int i;
	sr_lua_load_t *list = NULL, *li;
	if(lua_sr_list_script(&list) < 0) {
		LM_ERR("Can't get loaded scripts\n");
		return;
	}
	if(list) {
		li = list;
		i = 0;
		while(li) {
			rpc->rpl_printf(ctx, "%d: [%s]", i, li->script);
			li = li->next;
			i += 1;
		}
	} else {
		rpc->rpl_printf(ctx, "No scripts loaded");
	}
	return;
}

rpc_export_t app_lua_rpc_cmds[] = {
		{"app_lua.reload", app_lua_rpc_reload, app_lua_rpc_reload_doc, 0},
		{"app_lua.list", app_lua_rpc_list, app_lua_rpc_list_doc, 0},
		{"app_lua.api_list", app_lua_rpc_api_list, app_lua_rpc_api_list_doc, 0},
		{0, 0, 0, 0}};

/**
 * register RPC commands
 */
int app_lua_init_rpc(void)
{
	if(rpc_register_array(app_lua_rpc_cmds) != 0) {
		LM_ERR("failed to register RPC commands\n");
		return -1;
	}
	return 0;
}

/**
 *
 */
int bind_app_lua(app_lua_api_t *api)
{
	if(!api) {
		ERR("Invalid parameter value\n");
		return -1;
	}
	api->env_get_f = sr_lua_env_get;
	api->openlibs_register_f = app_lua_openlibs_register;
	return 0;
}
