#include <iostream>

extern "C"
{
	#include "lua.h"
	#include "lauxlib.h"
	#include "lualib.h"
}

bool dostring(lua_State* L, const char* str)
{
	if (luaL_loadbuffer(L, str, std::strlen(str), str) || lua_pcall(L, 0, 0, 0))
	{
		const char* a = lua_tostring(L, -1);
		std::cout << a << "\n";
		lua_pop(L, 1);
		return true;
	}
	return false;
}

#include <luabind/luabind.hpp>
#include <boost/intrusive_ptr.hpp>

namespace luabind
{
	namespace converters
	{
		// tell luabind that there is a converter for boost::intrusive_ptr<T>
		template<class T>
		yes_t is_user_defined(by_value<boost::intrusive_ptr<T> >);

		template<class T>
		yes_t is_user_defined(by_const_reference<boost::intrusive_ptr<T> >);

		// function used to destruct the object in lua
		template<class T>
		struct decrement_ref_count
		{
			static void apply(void* ptr)
			{
				T* p = static_cast<T*>(ptr);
				intrusive_ptr_release(p);
			}
		};

		template<class T>
		void convert_cpp_to_lua(lua_State* L, const boost::intrusive_ptr<T>& ptr)
		{
			if (!ptr)
			{
				lua_pushnil(L);
				return;
			}

			T* raw_ptr = ptr.get();
			// add reference to object, this will be released when the object is collected
			intrusive_ptr_add_ref(raw_ptr);

			detail::class_registry* registry = luabind::detail::class_registry::get_registry(L);
			detail::class_rep* crep = registry->find_class(LUABIND_TYPEID(T));

			assert(crep != 0 && "You are trying to convert an unregistered type");

			// create the struct to hold the object
			void* obj = lua_newuserdata(L, sizeof(detail::object_rep));
			// we send 0 as destructor since we know it will never be called
			new(obj) luabind::detail::object_rep(raw_ptr, crep, detail::object_rep::owner, decrement_ref_count<T>::apply);

			// set the meta table
			detail::getref(L, crep->metatable_ref());
			lua_setmetatable(L, -2);
		}

		template<class T>
		boost::intrusive_ptr<T>
		convert_lua_to_cpp(lua_State* L, by_const_reference<boost::intrusive_ptr<T> >, int index)
		{
			typename detail::default_policy::template generate_converter<T*, detail::lua_to_cpp>::type converter;
			T* ptr = converter.apply(L, LUABIND_DECORATE_TYPE(T*), index);
			return boost::intrusive_ptr<T>(ptr);
		}

		template<class T>
		boost::intrusive_ptr<T> convert_lua_to_cpp(lua_State* L, by_value<boost::intrusive_ptr<T> >, int index)
		{
			return convert_lua_to_cpp(L, by_const_reference<boost::intrusive_ptr<T> >(), index);
		}

		template<class T>
		int match_lua_to_cpp(lua_State* L, by_value<boost::intrusive_ptr<T> >, int index)
		{
			typedef typename detail::default_policy::template generate_converter<T*, detail::lua_to_cpp>::type converter_t;
			converter_t::match(L, LUABIND_DECORATE_TYPE(T*), index);
		}

		template<class T>
		int match_lua_to_cpp(lua_State* L, by_const_reference<boost::intrusive_ptr<T> >, int index)
		{
			typedef typename detail::default_policy::template generate_converter<T*, detail::lua_to_cpp>::type converter_t;
			converter_t::match(L, LUABIND_DECORATE_TYPE(T*), index);
		}

	}
}

struct A
{
	A()
	: cnt(0) 
	{}

	~A() { std::cout << "free memory\n"; }
	
	int cnt;
};

void intrusive_ptr_add_ref(A* ptr)
{
	++ptr->cnt;
	std::cout << "add ref\n";
}

void intrusive_ptr_release(A* ptr)
{
	--ptr->cnt;
	std::cout << "release\n";
	if (ptr->cnt == 0) delete ptr;
}

void f(boost::intrusive_ptr<A> ptr)
{
	std::cout << "count: " << ptr->cnt << "\n";
}

boost::intrusive_ptr<A> factory()
{
	return boost::intrusive_ptr<A>(new A());
}

int main()
{
	lua_State* L = lua_open();
	lua_baselibopen(L);

	luabind::open(L);
	
	using namespace luabind;

	module(L)
	[
		class_<A>("A")
			.def_readonly("cnt", &A::cnt),

		def("factory", &factory),
		def("f", &f)
	];

	dostring(L, "a = factory()");
	dostring(L, "print('lua count: ' .. a.cnt)");
	dostring(L, "f(a)");

	lua_close(L);
	return 0;
}

