//-------------------------------------------------------------------------------------------------
// <copyright file="Luna2.h" company="LiquidRobotics">
//  Copyright (c) Liquid Robotics Corporation, confidential and proprietary.  All rights reserved. 
// </copyright>
//
// <summary>
//   Declaration and implementation of the Luna2 template class, which wraps another
//   class to make its lua-ready methods callable by name as well as making it able
//   to be instantiated and destroyed from a lua script.
//
//	 Luna2 is a variant of the freely-available Luna template class, which was
//   documented as Technical Note #5 on at www.lua.org.  This version was modified
//   to use the SMC LuaInstance class and to use method calls on the parameterized
//   class.  Please note that the following methods are required for the
//	 parameterized class T:
//
//		T(LuaInstance& L)				- class constructor that can take parameters
//										  from the lua instantiation call
//
//		const string& GetClassName()	- returns the name of the class
//
//		int GetRegistrations(Luna2<T>::RegType*& regs)
//
//										- sets "regs" to point to the first element
//										  of the method registration array and
//										  returns the number of elements to register
// </summary>
//
// <owner>Mike Cookson</owner>
//
//-------------------------------------------------------------------------------------------------
//
// Example usage:
//
// class TestClass
// {
// public:
//
// 	TestClass(LuaInstance& _L)
// 	: number(0)
// 	{
// 	}
//
// 	static string GetClassName()
// 	{
// 		return "TestClass";
// 	}
//
// 	static int GetRegistrations(const Luna2<TestClass>::RegType*& regBase)
// 	{
// 		regBase = registrations;
// 		return registrationCount;
// 	}
//
// private:
//
// 	static const Luna2<TestClass>::RegType registrations[];
// 	static const size_t registrationCount;
//
// 	int GetNumber(LuaInstance& L)
// 	{
// 		L.pushnumber(number);
// 		return 1;
// 	}
//
// 	int SetNumber(LuaInstance& L)
// 	{
// 		number = L.tonumber(-1);
// 		return 0;
// 	}
//
// 	LuaInstance::Number number;
// };
//
// const Luna2<TestClass>::RegType TestClass::registrations[] =
// {
// 	{ "SetNumber", &TestClass::SetNumber },
// 	{ "GetNumber", &TestClass::GetNumber }
// };
//
// const size_t TestClass::registrationCount = sizeof(TestClass::registrations)/sizeof(TestClass::registrations[0]);
//
// int main(int argc, char* argv[])
// {
// 	LuaInstance lua;
//
// 	Luna2<TestClass>::Register(lua);
//
// 	lua.L_dofile("test1.lua");
//	return 0;
// }
//
//
// In lua:
//
//		local x = TestClass()
//		x.SetNumber(1)
//		print(x.GetNumber())
//
//----------------------------------------------------------------------------------

#ifndef __Luna2_h__
#define __Luna2_h__

#include "LqrSMACLuaInstance.h"
#include <sstream>
#include <string>

template<class T>
class Luna2
{
public:

	// Luna2 uses LuaInstance in preference to lua_State*
	typedef LiquidRobotics::SMACFramework::LuaInstance LuaInstance;

	// RegType is used in an array when registering type methods
	struct RegType
	{
	  const char *name;						// name of the method
	  int (T::*mfunc)(LuaInstance& L);		// mfunc is a pointer to a class method
	};

	// This method should be called to register the type to be instantiated under
	// lua; withConstructor determines if the constructor is available to the lua
	// interpreter
    static void Register(LuaInstance& L, bool withConstructor = true)
    {
    	// call event before registration
    	T::PreRegistration(L);

    	// registers Constructor under the class name
    	if (withConstructor)
    	{
			L.pushcfunction(&Luna2<T>::Constructor);
			L.setglobal(T::GetClassName());
    	}

		// creates a new metatable under the class name and sets up GarbageCollect
		// to be called when an object registered with the metatable is deleted
		L.L_newmetatable(T::GetClassName());
		L.pushstring("__gc");
		L.pushcfunction(&Luna2<T>::GarbageCollect);
		L.settable(-3);
    }

    // This method can be called as though from lua to construct an instance; it is
    // used when no constructor has been registered
    static int Constructor(LuaInstance& L, T* obj = NULL)
    {
    	// allocate an instance of T and call the constructor or use the one passed
    	// in
    	if (!obj)
    		obj = new T(L);

		L.newtable();					// creates a table
		L.pushnumber(0);				// planning to affect table[0]

		// creates user data large enough to store a T* (obj)
		T** a = (T**) L.newuserdata(sizeof(T*));
		*a = obj;

		// gets the metatable associated with the class name and stores it
		// as the metatable for the user data
		L.L_getmetatable(T::GetClassName());
		L.setmetatable(-2);

		L.settable(-3); 				// table[0] = user data (obj)

		// get method registration parameters
		const RegType* regs = NULL;
		const int numRegs = T::GetRegistrations(regs);

		// create a closure for method and store its index as the closure data, then
		// store the closure as an entry in the table indexed by function name
		for (int i = 0; i < numRegs; i++)
		{
			L.pushstring(regs[i].name);
			L.pushnumber(i);
			L.pushcclosure(&Luna2<T>::Thunk, 1);
			L.settable(-3);
		}

		return 1;						// returning the table
    }

private:

	// This method is called to create a new instance of T
    static int Constructor(lua_State* pState)
    {
    	LuaInstance L(pState);			// alias pState using LuaInstance
    	return Constructor(L);
    }

    // This method is called when the user data is deleted
    static int GarbageCollect(lua_State* pState)
    {
    	LuaInstance L(pState);			// alias pState using LuaInstance

    	// alias the user data to get the object pointer
		T** obj = static_cast<T**>(L.L_checkudata(-1, T::GetClassName()));

		// delete the T object instance
		try { delete *obj; }
		catch(...) { }

		return 0;						// nothing returned
    }

    // This method is called by each closure; errors thrown from the invoked
    // function are caught and re-thrown with the lua_error function
    static int Thunk(lua_State* pState)
    {
    	using namespace std;
    	bool err = false;
		int result = 0;					// value to return
		int i = -1;						// index number

    	{
			LuaInstance L(pState);			// alias pState using LuaInstance

			// reads the index from the closure's upvalue
			i = (int) L.tonumber(L.upvalueindex(1));

			// get registration data
			const RegType* regs = NULL;
			const int numRegs = T::GetRegistrations(regs);

				if (i >= numRegs)
			{
				stringstream msg;
				msg << T::GetClassName() << " invalid invocation on function " << i;
				string m;
				L.pushlstring(m);
				err = true;
			}
			else
			{
				L.pushnumber(0);			// table element 0
				L.gettable(1);				// table to reference

				// pull up "this" for invocation
				T** obj = static_cast<T**>(L.L_checkudata(-1, T::GetClassName()));

				L.remove(-1);				// cleans up stack

				// invoke the method
				try
				{
					result = ((*obj)->*(regs[i].mfunc))(L);
				}
				catch(exception& ex)
				{
					stringstream msg;
					msg << T::GetClassName() << "[" << i << "] caught exception: " << ex.what();
					string m = msg.str();
					L.pushlstring(m);
					err = true;
				}
				catch(...)
				{
					stringstream msg;
					msg << T::GetClassName() << "[" << i << "] caught generic exception";
					string m = msg.str();
					L.pushlstring(m);
					err = true;
				}
			}
    	}

    	if (err)
    		lua_error(pState);

		return result;
    }
};

#endif
