/* Copyright (c) 2018 Dovecot authors, see the included COPYING file */

#include "lib.h"
#include "module-dir.h"
#include "mail-lua-plugin.h"
#include "mail-storage-lua.h"
#include "mail-storage-private.h"
#include "mail-storage-hooks.h"
#include "dlua-script-private.h"

#define MAIL_LUA_SCRIPT "mail_lua_script"
#define MAIL_LUA_USER_CREATED_FN "mail_user_created"
#define MAIL_LUA_USER_DEINIT_FN "mail_user_deinit"
#define MAIL_LUA_USER_DEINIT_PRE_FN "mail_user_deinit_pre"
#define MAIL_LUA_USER_CONTEXT(obj) \
	MODULE_CONTEXT(obj, mail_lua_user_module)

static MODULE_CONTEXT_DEFINE_INIT(mail_lua_user_module,
				  &mail_user_module_register);

struct mail_lua_user_context {
	union mail_user_module_context module_ctx;
	struct dlua_script *script;
};

static int mail_lua_call_hook(struct dlua_script *script,
			      struct mail_user *user,
			      const char *hook,
			      const char **error_r)
{
	const char *error;

	if (!dlua_script_has_function(script, hook))
		return 0;

	if (user->mail_debug)
		e_debug(user->event, "mail-lua: Calling %s(user)", hook);

	dlua_push_mail_user(script->L, user);

	if (dlua_pcall(script->L, hook, 1, 2, &error) < 0) {
		*error_r = t_strdup_printf("%s(user) failed: %s", hook, error);
		return -1;
	}

	int ret = lua_tonumber(script->L, -2);
	const char *errmsg = lua_tostring(script->L, -1);

	if (ret < 0) {
		*error_r = t_strdup_printf("%s(user) failed: %s",
					   hook, errmsg);
	}

	lua_pop(script->L, 2);
	(void)lua_gc(script->L, LUA_GCCOLLECT, 0);

	return ret < 0 ? -1 : 1;
}

static void mail_lua_user_deinit_pre(struct mail_user *user)
{
	struct mail_lua_user_context *luser = MAIL_LUA_USER_CONTEXT(user);
	const char *error;

	if (luser == NULL)
		return;

	if (mail_lua_call_hook(luser->script, user, MAIL_LUA_USER_DEINIT_PRE_FN,
			       &error) < 0) {
		e_error(user->event, "mail-lua: %s", error);
	}

	luser->module_ctx.super.deinit_pre(user);
}

static void mail_lua_user_deinit(struct mail_user *user)
{
	struct mail_lua_user_context *luser = MAIL_LUA_USER_CONTEXT(user);
	const char *error;

	if (luser == NULL)
		return;

	luser->module_ctx.super.deinit(user);

	if (mail_lua_call_hook(luser->script, user, MAIL_LUA_USER_DEINIT_FN,
			       &error) < 0) {
		e_error(user->event, "mail-lua: %s", error);
	}

	dlua_script_unref(&luser->script);
}

static void mail_lua_user_created(struct mail_user *user)
{
	struct mail_lua_user_context *luser;
	struct mail_user_vfuncs *v = user->vlast;
	struct dlua_script *script;
	const char *error;
	const char *script_fn = mail_user_plugin_getenv(user, MAIL_LUA_SCRIPT);
	int ret;

	if (script_fn == NULL)
		return;

	if (dlua_script_create_file(script_fn, &script, user->event, &error) < 0) {
		user->error = p_strdup_printf(user->pool, "dlua_script_create_file(%s) failed: %s",
					      script_fn, error);
		return;
	}

	dlua_dovecot_register(script);
	dlua_register_mail_storage(script);

	/* init */
	if (dlua_script_init(script, &error) < 0) {
		user->error = p_strdup_printf(user->pool, "dlua_script_init(%s) failed: %s",
					      script_fn, error);
		dlua_script_unref(&script);
		return;
	}

	/* call postlogin hook */
	if ((ret = mail_lua_call_hook(script, user, MAIL_LUA_USER_CREATED_FN,
				      &error)) <= 0) {
		if (ret < 0)
			user->error = p_strdup(user->pool, error);
		dlua_script_unref(&script);
		return;
	}

	luser = p_new(user->pool, struct mail_lua_user_context, 1);
	luser->module_ctx.super = *v;
	v->deinit_pre = mail_lua_user_deinit_pre;
	v->deinit = mail_lua_user_deinit;
	luser->script = script;
	user->vlast = &luser->module_ctx.super;

	MODULE_CONTEXT_SET(user, mail_lua_user_module, luser);
}

bool mail_lua_plugin_get_script(struct mail_user *user,
				struct dlua_script **script_r)
{
	struct mail_lua_user_context *luser = MAIL_LUA_USER_CONTEXT(user);
	if (luser != NULL) {
		*script_r = luser->script;
		return TRUE;
	}
	return FALSE;
}

static const struct mail_storage_hooks mail_lua_hooks = {
	.mail_user_created = mail_lua_user_created,
};

void mail_lua_plugin_init(struct module *module)
{
	mail_storage_hooks_add(module, &mail_lua_hooks);
}

void mail_lua_plugin_deinit(void)
{
	mail_storage_hooks_remove(&mail_lua_hooks);
}

const char *mail_lua_plugin_dependencies[] = { NULL };
