/* vifm
 * Copyright (C) 2023 filterfalse <filterfalse@gmail.com>.
 *
 * This program 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.
 *
 * This program 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 <stddef.h> /* NULL */
#include <stdlib.h> /* free() */
#include <wchar.h> /* wcslen() */

#include "vifm_abbrevs.h"

#include "../engine/abbrevs.h"
#include "../ui/statusbar.h"
#include "../bracket_notation.h"
#include "../status.h"
#include "lua/lauxlib.h"
#include "lua/lua.h"
#include "api.h"
#include "common.h"
#include "vlua_state.h"

static int VLUA_API(abbrevs_add)(lua_State *lua);
static wchar_t * lua_abbrev_handler(void *user_data);

VLUA_DECLARE_UNSAFE(abbrevs_add);

/* Functions of `vifm.abbrevs` table. */
static const luaL_Reg vifm_abbrevs_methods[] = {
	{ "add", VLUA_REF(abbrevs_add) },
	{ NULL,  NULL                  }
};

void
vifm_abbrevs_init(lua_State *lua)
{
	luaL_newlib(lua, vifm_abbrevs_methods);
}

/* Member of `vifm.abbrevs` that registers a new abbreviation or raises an
 * error.  Returns boolean, which is true on success. */
static int
VLUA_API(abbrevs_add)(lua_State *lua)
{
	vlua_t *vlua = vlua_state_get(lua);

	luaL_checktype(lua, 1, LUA_TTABLE);

	wchar_t wlhs[32];
	const size_t max_wlhs_len = ARRAY_LEN(wlhs) - 1U;
	vlua_cmn_check_field(lua, 1, "lhs", LUA_TSTRING);
	const char *lhs = lua_tostring(lua, -1);
	wchar_t *tmp_wlhs = substitute_specs(lhs);
	if(wcslen(tmp_wlhs) == 0 || wcslen(tmp_wlhs) > max_wlhs_len)
	{
		free(tmp_wlhs);
		return luaL_error(lua, "`lhs` can't be empty or longer than %d",
				(int)max_wlhs_len);
	}

	wcscpy(wlhs, tmp_wlhs);
	free(tmp_wlhs);

	lua_newtable(lua);
	vlua_cmn_check_field(lua, 1, "handler", LUA_TFUNCTION);
	lua_setfield(lua, -2, "handler");
	lua_pushstring(lua, lhs);
	lua_setfield(lua, -2, "lhs");
	void *handler = vlua_cmn_to_pointer(lua);

	void *user_data = vlua_state_store_pointer(vlua, handler);
	if(user_data == NULL)
	{
		return luaL_error(lua, "%s", "Failed to store handler data");
	}

	const char *descr = "";
	if(vlua_cmn_check_opt_field(vlua->lua, 1, "description", LUA_TSTRING))
	{
		descr = lua_tostring(lua, -1);
	}

	int no_remap = 1;
	if(vlua_cmn_check_opt_field(lua, 1, "noremap", LUA_TBOOLEAN))
	{
		no_remap = lua_toboolean(lua, -1);
	}

	lua_pushboolean(lua, (vle_abbr_add_foreign(wlhs, descr, no_remap,
					&lua_abbrev_handler, user_data) == 0));
	return 1;
}

/* Handler of all foreign abbreviations registered from Lua.  Returns newly
 * allocated wide string. */
static wchar_t *
lua_abbrev_handler(void *user_data)
{
	state_ptr_t *p = user_data;
	lua_State *lua = p->vlua->lua;

	vlua_cmn_from_pointer(lua, p->ptr);
	lua_getfield(lua, -1, "lhs");
	const char *lhs = lua_tostring(lua, -1);
	lua_getfield(lua, -2, "handler");

	lua_createtable(lua, /*narr=*/0, /*nrec=*/1);
	lua_pushstring(lua, lhs);
	lua_setfield(lua, -2, "lhs");

	int sm_cookie = vlua_state_safe_mode_on(lua);
	int result = lua_pcall(lua, /*nargs=*/1, /*nresults=*/1, /*msgh=*/0);
	vlua_state_safe_mode_off(lua, sm_cookie);

	if(result != LUA_OK)
	{
		const char *error = lua_tostring(lua, -1);
		ui_sb_err(error);
		lua_pop(lua, 3);
		curr_stats.save_msg = 1;
		return NULL;
	}

	wchar_t *rhs = NULL;
	if(lua_istable(lua, -1))
	{
		lua_getfield(lua, -1, "rhs");
		const char *str = lua_tostring(lua, -1);
		if(str != NULL)
		{
			rhs = substitute_specs(str);
		}
		lua_pop(lua, 1);
	}
	lua_pop(lua, 3);
	return rhs;
}

/* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
/* vim: set cinoptions+=t0 : */
