/*-
 * Copyright 2016 Vsevolod Stakhov
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "rspamadm.h"
#include "libutil/util.h"
#include "libserver/logger.h"
#include "lua/lua_common.h"
#include "lua/lua_thread_pool.h"

extern struct rspamadm_command pw_command;
extern struct rspamadm_command configtest_command;
extern struct rspamadm_command configdump_command;
extern struct rspamadm_command control_command;
extern struct rspamadm_command confighelp_command;
extern struct rspamadm_command statconvert_command;
extern struct rspamadm_command fuzzyconvert_command;
extern struct rspamadm_command signtool_command;
extern struct rspamadm_command lua_command;

const struct rspamadm_command *commands[] = {
	&help_command,
	&pw_command,
	&configtest_command,
	&configdump_command,
	&control_command,
	&confighelp_command,
	&statconvert_command,
	&fuzzyconvert_command,
	&signtool_command,
	&lua_command,
	NULL};


const struct rspamadm_command *
rspamadm_search_command(const char *name, GPtrArray *all_commands)
{
	const struct rspamadm_command *ret = NULL, *cmd;
	const char *alias;
	unsigned int i, j;

	if (name == NULL) {
		name = "help";
	}

	PTR_ARRAY_FOREACH(all_commands, i, cmd)
	{
		if (strcmp(name, cmd->name) == 0) {
			ret = cmd;
			break;
		}

		PTR_ARRAY_FOREACH(cmd->aliases, j, alias)
		{
			if (strcmp(name, alias) == 0) {
				ret = cmd;
				break;
			}
		}
	}

	return ret;
}

void rspamadm_fill_internal_commands(GPtrArray *dest)
{
	unsigned int i;

	for (i = 0; i < G_N_ELEMENTS(commands); i++) {
		if (commands[i]) {
			g_ptr_array_add(dest, (gpointer) commands[i]);
		}
	}
}

static void
lua_thread_str_error_cb(struct thread_entry *thread, int ret, const char *msg)
{
	msg_err("call to rspamadm lua script failed (%d): %s",
			ret, msg);
}

static void
rspamadm_lua_command_run(int argc, char **argv,
						 const struct rspamadm_command *cmd)
{
	struct thread_entry *thread = lua_thread_pool_get_for_config(rspamd_main->cfg);

	lua_State *L = thread->lua_state;

	int table_idx = GPOINTER_TO_INT(cmd->command_data);
	int i;

	/* Function */
	lua_rawgeti(L, LUA_REGISTRYINDEX, table_idx);
	lua_pushstring(L, "handler");
	lua_gettable(L, -2);

	/* Args */
	lua_createtable(L, argc + 1, 0);

	for (i = 0; i < argc; i++) {
		lua_pushstring(L, argv[i]);
		lua_rawseti(L, -2, i); /* Starting from zero ! */
	}

	if (lua_repl_thread_call(thread, 1, (void *) cmd, lua_thread_str_error_cb) != 0) {
		exit(EXIT_FAILURE);
	}

	lua_settop(L, 0);
}

static const char *
rspamadm_lua_command_help(gboolean full_help,
						  const struct rspamadm_command *cmd)
{
	int table_idx = GPOINTER_TO_INT(cmd->command_data);

	if (full_help) {
		struct thread_entry *thread = lua_thread_pool_get_for_config(rspamd_main->cfg);

		lua_State *L = thread->lua_state;
		lua_rawgeti(L, LUA_REGISTRYINDEX, table_idx);
		/* Function */
		lua_pushstring(L, "handler");
		lua_gettable(L, -2);

		/* Args */
		lua_createtable(L, 2, 0);
		lua_pushstring(L, cmd->name);
		lua_rawseti(L, -2, 0); /* Starting from zero ! */

		lua_pushstring(L, "--help");
		lua_rawseti(L, -2, 1);

		if (lua_repl_thread_call(thread, 1, (void *) cmd, lua_thread_str_error_cb) != 0) {
			exit(EXIT_FAILURE);
		}

		lua_settop(L, 0);
	}
	else {
		lua_State *L = rspamd_main->cfg->lua_state;
		lua_rawgeti(L, LUA_REGISTRYINDEX, table_idx);
		lua_pushstring(L, "description");
		lua_gettable(L, -2);

		if (lua_isstring(L, -1)) {
			printf("  %-18s %-60s\n", cmd->name, lua_tostring(L, -1));
		}
		else {
			printf("  %-18s %-60s\n", cmd->name, "no description available");
		}

		lua_settop(L, 0);
	}

	return NULL; /* Must be handled in rspamadm itself */
}

void rspamadm_fill_lua_commands(lua_State *L, GPtrArray *dest)
{
	int i;

	GPtrArray *lua_paths;
	GError *err = NULL;
	const char *lualibdir = RSPAMD_LUALIBDIR, *path;
	struct rspamadm_command *lua_cmd;
	char search_dir[PATH_MAX];

	if (g_hash_table_lookup(ucl_vars, "LUALIBDIR")) {
		lualibdir = g_hash_table_lookup(ucl_vars, "LUALIBDIR");
	}

	rspamd_snprintf(search_dir, sizeof(search_dir), "%s%crspamadm%c",
					lualibdir, G_DIR_SEPARATOR, G_DIR_SEPARATOR);

	if ((lua_paths = rspamd_glob_path(search_dir, "*.lua", FALSE, &err)) == NULL) {
		msg_err("cannot glob files in %s/*.lua: %e", search_dir, err);
		g_error_free(err);

		return;
	}

	PTR_ARRAY_FOREACH(lua_paths, i, path)
	{
		if (luaL_dofile(L, path) != 0) {
			msg_err("cannot execute lua script %s: %s",
					path, lua_tostring(L, -1));
			lua_settop(L, 0);
			continue;
		}
		else {
			if (lua_type(L, -1) == LUA_TTABLE) {
				lua_pushstring(L, "handler");
				lua_gettable(L, -2);
			}
			else {
				continue; /* Something goes wrong, huh */
			}

			if (lua_type(L, -1) != LUA_TFUNCTION) {
				msg_err("rspamadm script %s does not have 'handler' field with type "
						"function",
						path);
				continue;
			}

			/* Pop handler */
			lua_pop(L, 1);
			lua_cmd = g_malloc0(sizeof(*lua_cmd));

			lua_pushstring(L, "name");
			lua_gettable(L, -2);

			if (lua_type(L, -1) == LUA_TSTRING) {
				lua_cmd->name = g_strdup(lua_tostring(L, -1));
			}
			else {
				goffset ext_pos;
				char *name;

				name = g_path_get_basename(path);
				/* Remove .lua */
				ext_pos = rspamd_substring_search(path, strlen(path), ".lua", 4);

				if (ext_pos != -1) {
					name[ext_pos] = '\0';
				}

				lua_cmd->name = name;
			}

			lua_pop(L, 1);

			lua_pushstring(L, "aliases");
			lua_gettable(L, -2);

			if (lua_type(L, -1) == LUA_TTABLE) {
				lua_cmd->aliases = g_ptr_array_new_full(
					rspamd_lua_table_size(L, -1),
					g_free);

				for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) {
					if (lua_isstring(L, -1)) {
						g_ptr_array_add(lua_cmd->aliases,
										g_strdup(lua_tostring(L, -1)));
					}
				}
			}

			lua_pop(L, 1);

			lua_pushvalue(L, -1);
			/* Reference table itself */
			lua_cmd->command_data = GINT_TO_POINTER(luaL_ref(L, LUA_REGISTRYINDEX));
			lua_cmd->flags |= RSPAMADM_FLAG_LUA | RSPAMADM_FLAG_DYNAMIC;
			lua_cmd->run = rspamadm_lua_command_run;
			lua_cmd->help = rspamadm_lua_command_help;

			g_ptr_array_add(dest, lua_cmd);
		}

		lua_settop(L, 0);
	}

	g_ptr_array_free(lua_paths, TRUE);
}
