#include <stdio.h>

extern "C" {
#define LUA_LIB
#include "lua.h"
#include "lauxlib.h"
#include "luajit.h"
}

static int testobj_alloc;

class TestObj {
public:
  TestObj(int x) { foo = x; testobj_alloc = 1; }
  ~TestObj() { testobj_alloc = 0; }
private:
  int foo;
};

static int ct_alloc(lua_State *L)
{
  TestObj foo(1);
  lua_pushlightuserdata(L, (void *)&foo);
  lua_call(L, lua_gettop(L)-1, LUA_MULTRET);
  if (lua_iscfunction(L, -1)) {
    lua_CFunction f = lua_tocfunction(L, -1);
    lua_pop(L, 1);
    f(L);
  }
  return lua_gettop(L);
}

static int ct_isalloc(lua_State *L)
{
  lua_pushboolean(L, testobj_alloc);
  return 1;
}

static int ct_usereg(lua_State *L)
{
  int n = luaL_checkint(L, 1);
  int m = luaL_checkint(L, 2);
  int i;
  int a = 0, b = 0, c = 0, d = 0, e = 0, f = 0;
  for (i = 0; i < n; i++) {
    a = (a + 1) ^ 0x12345678;
    b = (b + 2) ^ 0x12345678;
    c = (c + 3) ^ 0x12345678;
    d = (d + 4) ^ 0x12345678;
    e = (e + 5) ^ 0x12345678;
    f = (f + 5) ^ 0x12345678;
    if (i == m) {
      if (i & 1)
	lua_pcall(L, 1, 0, 0);
      else
	lua_call(L, 1, 0);
    }
  }
  lua_pushinteger(L, a);
  lua_pushinteger(L, b);
  lua_pushinteger(L, c);
  lua_pushinteger(L, d);
  lua_pushinteger(L, e);
  lua_pushinteger(L, f);
  return 6;
}

static int ct_catch(lua_State *L)
{
  try {
    lua_call(L, lua_gettop(L)-1, LUA_MULTRET);
    return lua_gettop(L);
  } catch (const char *s) {
    lua_pushstring(L, s);
  } catch (...) {
    lua_pushliteral(L, "catch ...");
  }
  return 1;
}

static int ct_throw(lua_State *L)
{
  const char *s = lua_tostring(L, 1);
  throw(s);
  return 0;
}

static int ct_wrap(lua_State *L, lua_CFunction f)
{
  try {
    return f(L);
  } catch (const char *s) {
    lua_pushstring(L, s);
  }
  return lua_error(L);
}

static int ct_wrapon(lua_State *L)
{
  lua_pushlightuserdata(L, (void *)ct_wrap);
  luaJIT_setmode(L, -1, LUAJIT_MODE_WRAPCFUNC|LUAJIT_MODE_ON);
  return 0;
}

static int ct_wrapoff(lua_State *L)
{
  luaJIT_setmode(L, 0, LUAJIT_MODE_WRAPCFUNC|LUAJIT_MODE_OFF);
  return 0;
}

static luaL_Reg ct_funcs[] = {
  {"isalloc",	ct_isalloc },
  {"alloc",	ct_alloc },
  {"usereg",	ct_usereg },
  {"catch",	ct_catch },
  {"throw",	ct_throw },
  {"wrapon",	ct_wrapon },
  {"wrapoff",	ct_wrapoff },
  {NULL, NULL}
};

extern "C" {
LUA_API int luaopen_cpptest(lua_State *L)
{
  luaL_register(L, "cpptest", ct_funcs);
  return 1;
}
}