// Copyright (c) 2003 Daniel Wallin and Arvid Norberg

// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
// ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
// OR OTHER DEALINGS IN THE SOFTWARE.


#ifndef LUABIND_REF_HPP_INCLUDED
#define LUABIND_REF_HPP_INCLUDED

#include <cassert>
#include <algorithm>

#include <luabind/config.hpp>
#include <luabind/lua_include.hpp>

struct lua_State;

namespace luabind
{

#if LUA_VERSION_NUM >= 501
#define LUA_REFNIL (-1)
#undef luaL_setn
#undef luaL_getn
#endif

namespace detail
{

	int LUABIND_API ref(lua_State *L);
	void LUABIND_API unref(lua_State *L, int ref);

	inline void getref(lua_State* L, int r)
	{
		lua_rawgeti(L, LUA_REGISTRYINDEX, r);
	}

	struct lua_reference
	{
		lua_reference(lua_State* L_ = 0)
			: L(L_)
			, m_ref(LUA_NOREF)
		{}
		lua_reference(lua_reference const& r)
			: L(r.L)
			, m_ref(LUA_NOREF)
		{
			if (!r.is_valid()) return;
			r.get(L);
			set(L);
		}
		~lua_reference() { reset(); }

		lua_State* state() const { return L; }

		void operator=(lua_reference const& r)
		{
			// TODO: self assignment problems
			reset();
			if (!r.is_valid()) return;
			r.get(r.state());
			set(r.state());
		}

		bool is_valid() const
		{ return m_ref != LUA_NOREF; }

		void set(lua_State* L_)
		{
			reset();
			L = L_;
			m_ref = ref(L);
		}

		void replace(lua_State* L_)
		{
			lua_rawseti(L_, LUA_REGISTRYINDEX, m_ref);
		}

		// L may not be the same pointer as
		// was used when creating this reference
		// since it may be a thread that shares
		// the same globals table.
		void get(lua_State* L_) const
		{
			assert(m_ref != LUA_NOREF);
			assert(L_);
			getref(L_, m_ref);
		}

		void reset()
		{
			if (L && m_ref != LUA_NOREF) unref(L, m_ref);
			m_ref = LUA_NOREF;
		}

		void swap(lua_reference& r)
		{
			assert(r.L == L);
			std::swap(r.m_ref, m_ref);
		}

	private:
		lua_State* L;
		int m_ref;
	};

}}

#endif // LUABIND_REF_HPP_INCLUDED

