// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 2012-2016 by John "JTE" Muniz.
// Copyright (C) 2012-2018 by Sonic Team Junior.
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
/// \file  lua_thinkerlib.c
/// \brief thinker library for Lua scripting

#include "doomdef.h"
#ifdef HAVE_BLUA
#include "p_local.h"
#include "lua_script.h"
#include "lua_libs.h"

#define META_ITERATIONSTATE "iteration state"

static const char *const iter_opt[] = {
	"all",
	"mobj",
	NULL};

static const actionf_p1 iter_funcs[] = {
	NULL,
	(actionf_p1)P_MobjThinker
};

struct iterationState {
	actionf_p1 filter;
	int next;
};

static int iterationState_gc(lua_State *L)
{
	struct iterationState *it = luaL_checkudata(L, -1, META_ITERATIONSTATE);
	if (it->next != LUA_REFNIL)
	{
		luaL_unref(L, LUA_REGISTRYINDEX, it->next);
		it->next = LUA_REFNIL;
	}
	return 0;
}

#define push_thinker(th) {\
	if ((th)->function.acp1 == (actionf_p1)P_MobjThinker) \
		LUA_PushUserdata(L, (th), META_MOBJ); \
	else \
		lua_pushlightuserdata(L, (th)); \
}

static int lib_iterateThinkers(lua_State *L)
{
	thinker_t *th = NULL, *next = NULL;
	struct iterationState *it = luaL_checkudata(L, 1, META_ITERATIONSTATE);
	lua_settop(L, 2);

	if (lua_isnil(L, 2))
		th = &thinkercap;
	else if (lua_isuserdata(L, 2))
	{
		if (lua_islightuserdata(L, 2))
			th = lua_touserdata(L, 2);
		else
		{
			th = *(thinker_t **)lua_touserdata(L, -1);
			if (!th)
			{
				if (it->next == LUA_REFNIL)
					return 0;

				lua_rawgeti(L, LUA_REGISTRYINDEX, it->next);
				if (lua_islightuserdata(L, -1))
					next = lua_touserdata(L, -1);
				else
					next = *(thinker_t **)lua_touserdata(L, -1);
			}
		}
	}

	luaL_unref(L, LUA_REGISTRYINDEX, it->next);
	it->next = LUA_REFNIL;

	if (th && !next)
		next = th->next;
	if (!next)
		return luaL_error(L, "next thinker invalidated during iteration");

	for (; next != &thinkercap; next = next->next)
		if (!it->filter || next->function.acp1 == it->filter)
		{
			push_thinker(next);
			if (next->next != &thinkercap)
			{
				push_thinker(next->next);
				it->next = luaL_ref(L, LUA_REGISTRYINDEX);
			}
			return 1;
		}
	return 0;
}

static int lib_startIterate(lua_State *L)
{
	struct iterationState *it;

	lua_pushvalue(L, lua_upvalueindex(1));
	it = lua_newuserdata(L, sizeof(struct iterationState));
	luaL_getmetatable(L, META_ITERATIONSTATE);
	lua_setmetatable(L, -2);

	it->filter = iter_funcs[luaL_checkoption(L, 1, "mobj", iter_opt)];
	it->next = LUA_REFNIL;
	return 2;
}

#undef push_thinker

int LUA_ThinkerLib(lua_State *L)
{
	luaL_newmetatable(L, META_ITERATIONSTATE);
	lua_pushcfunction(L, iterationState_gc);
	lua_setfield(L, -2, "__gc");
	lua_pop(L, 1);

	lua_createtable(L, 0, 1);
		lua_pushcfunction(L, lib_iterateThinkers);
		lua_pushcclosure(L, lib_startIterate, 1);
		lua_setfield(L, -2, "iterate");
	lua_setglobal(L, "thinkers");
	return 0;
}

#endif
