#include "lua_ref_helper.h"

#define FREELIST_REF 0

#if LUA_VERSION_NUM >= 502
#define lua_objlen(L, INDEX) lua_rawlen(L, INDEX)
#endif

/* convert a stack index to positive */
/* stolen from lauxlib.c, converted to a function */

static int abs_index(lua_State *L, int i) {
  return i > 0 || i <= LUA_REGISTRYINDEX ? i : lua_gettop(L) + i + 1;
}

LUALIB_API int luaX_ref(lua_State *L, int t, int l) {
  int ref;
  t = abs_index(L, t);
  l = abs_index(L, l);
  if (lua_isnil(L, -1)) {
    lua_pop(L, 1);     /* remove from stack */
    return LUA_REFNIL; /* 'nil' has a unique fixed reference */
  }
  lua_rawgeti(L, l, FREELIST_REF);   /* get first free element */
  ref = (int)lua_tointeger(L, -1);   /* ref = l[FREELIST_REF] */
  lua_pop(L, 1);                     /* remove it from stack */
  if (ref != 0) {                    /* any free element? */
    lua_rawgeti(L, l, ref);          /* remove it from list */
    lua_rawseti(L, l, FREELIST_REF); /* (l[FREELIST_REF] = l[ref]) */
  } else {                           /* no free elements */
    ref = (int)lua_objlen(L, l);
    ref++; /* create new reference */
  }
  lua_pushboolean(L, 1);
  lua_rawseti(L, l, ref); /* l[ref] = true */
  lua_rawseti(L, t, ref); /* t[ref] = value */
  return ref;
}

LUALIB_API void luaX_unref(lua_State *L, int t, int l, int ref) {
  if (ref >= 0) {
    t = abs_index(L, t);
    l = abs_index(L, l);
    lua_rawgeti(L, l, FREELIST_REF);
    lua_rawseti(L, l, ref); /* l[ref] = l[FREELIST_REF] */
    lua_pushinteger(L, ref);
    lua_rawseti(L, l, FREELIST_REF); /* l[FREELIST_REF] = ref */
    lua_pushnil(L);
    lua_rawseti(L, t, ref); /* t[ref] = nil */
  }
}
