/** @file messageproxy.cpp
 *  EAMessage proxy for Functions
 *	@headerfile messageproxy.h "EABaseTypesMessage/messageproxy.h"
 *	@author Chris Burns (cburns@ea.com)
 *
 *	Core Technology UI
 *	(c) 2012 Electronic Arts Inc. 
 */

#include "EABaseTypesMessage/messageproxy.h"
#include "EABaseTypesMessage/message.h"

#include "eaassert/eaassert.h"

#include "EABaseTypes/iencoder.h"
#include "EABaseTypes/basetypeencoder.h"
#include "EABaseTypes/datatypes.h"
#include "EABaseTypes/functor.h"
#include "EABaseTypes/factory.h"
#include "EABaseTypes/functioncallwrappers.h"
#include "EABaseTypes/ptruserdata.h"

#include "EABaseTypesFuture/future.h"

#include "EABaseTypesPlus/errorencoder.h"

namespace EA
{
	namespace Types
	{
		const unsigned int kAnonymousProxyCall =	0x23000000;
		const unsigned int kAnonymousProxyAddRef =	0x23000001;
		const unsigned int kAnonymousProxyRelease =	0x23000002;
		const unsigned int kNamedProxyCall =		0x23000003;
		const unsigned int kUnsafeProxyCall =		0x23000004;

		BaseTypeReturn ProxyBaseType(BaseTypeIn baseType, MessageProxyIn src, MessageProxyIn dst)
		{
			if(baseType != NULL)
			{
				Factory* factory = baseType->GetFactory();

				BaseTypeEncoder encoder(factory);
                
                UnproxyEncoder<BaseTypeEncoder> unproxyEncoder(dst.GetUnsafePointer(), &encoder);
                
				ProxyEncoder<UnproxyEncoder<BaseTypeEncoder> > proxyEncoder(src.GetUnsafePointer(), dst.GetUnsafePointer(), &unproxyEncoder);

				BaseTypeDecoder<ProxyEncoder<UnproxyEncoder<BaseTypeEncoder> > >::Decode(&proxyEncoder, baseType.GetUnsafePointer());

				return encoder.AcquireRoot();
			}
			else
			{
				return NULL;
			}
		}

		static void Send(EA::Messaging::Server* server, EA::Messaging::MessageId messageId, EA::Messaging::IMessageRC* message)
		{
			server->Lock(true);
			server->MessagePost(messageId, message);
			message->Release();
			server->Lock(false);
		}

		class AnonymousProxyFunction : public Function
		{
		public:
			
			AnonymousProxyFunction(Factory* factory,
								   MessageProxyIn localProxy,
								   MessageProxyIn remoteProxy,
								   int id
#if !defined(EA_FINAL)
								   , BaseTypeIn debugInfo
#endif
								   ):
				Function(factory),
				mLocalProxy(localProxy),
				mRemoteProxy(remoteProxy),
				mId(id)
			{
#if !defined(EA_FINAL)
				SetDebugInfo(debugInfo);
#endif
			}

			virtual ~AnonymousProxyFunction()
			{
				ObjectRef metadata = new(mFactory) Object(mFactory);

				metadata->insert("id", mId);

				EA::Allocator::ICoreAllocator* allocator = mFactory->GetAllocator();

				Message* message = CORE_NEW(allocator, "Message", EA::Allocator::MEM_TEMP) Message(allocator, metadata);
				metadata = NULL;

				// we don't really care if this actually succeeds, if the proxy is gone it has released the original function anyway
				Send(mRemoteProxy->mLocalServer, kAnonymousProxyRelease, message);
				// Send() calls Message::Release()
			}

			virtual BaseTypeReturn Clone()
			{
				AddRef();

				return this;
			}

			FunctionReturn Proxy(MessageProxyIn localProxy)
			{
				FunctionRef clone = new (mFactory) AnonymousProxyFunction(mFactory,
																		  localProxy, 
																		  mRemoteProxy, 
																		  mId
#if !defined(EA_FINAL)
																		  , mDebugInfo->Clone()
#endif
																		  );

				// we need to send a message to the original owner of the function so that it keeps an extra reference
				// to the function since we're effectively duplicating it
				ObjectRef metadata = new(mFactory) Object(mFactory);

				metadata->insert("id", mId);

				EA::Allocator::ICoreAllocator* allocator = mFactory->GetAllocator();

				Message* message = CORE_NEW(allocator, "Message", EA::Allocator::MEM_TEMP) Message(allocator, metadata);
				metadata = NULL;

				// we don't really care if this actually succeeds, if the proxy is gone it has released the original function anyway
				Send(mRemoteProxy->mLocalServer, kAnonymousProxyAddRef, message);
				// Send() calls Message::Release()

				return clone;
			}

			virtual void Call(unsigned int id, 
							  void* decoderParams,
							  void* encoder,
							  DecodeFunction defaultDecode,
							  IEncoder* defaultEncoder)
			{
				CheckData(decoderParams, defaultDecode);

				const eastl::pair<DecodeFunction, DecodeFunction>* decode = GetFactory()->GetCallerMap()->Get(PROXY_CALLER_TYPE, id);

				ObjectRef metadata = new(mFactory) Object(mFactory);

				metadata->insert("id", mId);

				EA::Allocator::ICoreAllocator* allocator = mFactory->GetAllocator();

				Message* message = CORE_NEW(allocator, "Message", EA::Allocator::MEM_TEMP) Message(allocator, metadata);
				metadata = NULL;
				
				ByteStreamEncoder byteStreamEncoder(&message->mData);
				ProxyEncoder<ByteStreamEncoder> argumentEncoder(mLocalProxy.GetUnsafePointer(), mRemoteProxy.GetUnsafePointer(), &byteStreamEncoder);

				if(decode != NULL)
				{
					decode->first(&argumentEncoder, decoderParams);
				}
				else
				{
					IEncoderImpl<ProxyEncoder<ByteStreamEncoder> > defaultArgumentEncoder(&argumentEncoder);
					defaultDecode(&defaultArgumentEncoder, decoderParams);
				}

				Send(mRemoteProxy->mLocalServer, kAnonymousProxyCall, message);
				// Send() calls Message::Release()
			}

			//lint -efunc(931, AnonymousProxyFunction::Equals)
			virtual bool Equals(FunctionIn pCompare)
			{
				bool result = false;
				if(pCompare->GetFunctionType() == EA::Type::TypeOf<AnonymousProxyFunction>())
				{
					AnonymousProxyFunction* other = static_cast<AnonymousProxyFunction*>(pCompare.GetUnsafePointer());

					EA_ASSERT_MESSAGE(mLocalProxy == other->mLocalProxy, "It is unsafe to compare functions that don't belong to the same proxy");

					result = mRemoteProxy == other->mRemoteProxy && mId == other->mId;
				}
				
				return result;
			}

			virtual EA::Type::TypeID GetFunctionType()
			{
				return EA::Type::TypeOf(*this);
			}
            
            const MessageProxyRef& GetRemoteProxy() const
            {
                return mRemoteProxy;
            }
            
            int GetId() const
            {
                return mId;
            }

		private:

			MessageProxyRef	mLocalProxy;
			MessageProxyRef	mRemoteProxy;
			int				mId;
		};

		// PcLint doesn't get what we're doing with our AddRef()/Release() stuff
		//lint -efunc(429, NamedProxyFunction::Call)
		class NamedProxyFunction : public Function
		{
		public:

			NamedProxyFunction(Factory* factory,
							   MessageProxy* localProxy,
							   const EA::String& proxyName,
							   const char* path):
				Function(factory),
				mLocalProxy(localProxy),
				mProxyName(proxyName),
				mPath(factory->GetAllocator(), path)
			{
				//can't addref here, would cause a circular reference
				//mLocalProxy->AddRef();

#if !defined(EA_FINAL)
				EA::String debugInfo(factory);
				debugInfo.sprintf("Named proxy function from %s%s to %s", proxyName.c_str(), path, localProxy->GetName().c_str());
				SetDebugInfo(factory->String(debugInfo));
#endif
			}

			virtual ~NamedProxyFunction()
			{
				//mLocalProxy->Release();
				mLocalProxy = NULL;
			}

			virtual BaseTypeReturn Clone()
			{
				AddRef();

				return this;
			}

			virtual void Call(unsigned int id, 
							  void* decoderParams,
							  void* encoder,
							  DecodeFunction defaultDecode,
							  IEncoder* defaultEncoder)
			{
				CheckData(decoderParams, defaultDecode);

				const eastl::pair<DecodeFunction, DecodeFunction>* decode = GetFactory()->GetCallerMap()->Get(PROXY_CALLER_TYPE, id);

				MessageProxyRef remoteProxy = mLocalProxy->GetRemoteProxy(mProxyName);

				EA_ASSERT_FORMATTED(remoteProxy != NULL, ("Can't send message to proxy named %s\n", mProxyName.c_str()));

				ObjectRef metadata = new(mFactory) Object(mFactory);

				metadata->insert("path", mPath);

				EA::Allocator::ICoreAllocator *allocator = mFactory->GetAllocator();

				Message* message = CORE_NEW(allocator, "Message", EA::Allocator::MEM_TEMP) Message(allocator, metadata);
				metadata = NULL;
				
				ByteStreamEncoder byteStreamEncoder(&message->mData);
				ProxyEncoder<ByteStreamEncoder> argumentEncoder(mLocalProxy, remoteProxy.GetUnsafePointer(), &byteStreamEncoder);

				if(decode != NULL)
				{
					decode->first(&argumentEncoder, decoderParams);
				}
				else
				{
					IEncoderImpl<ProxyEncoder<ByteStreamEncoder> > defaultArgumentEncoder(&argumentEncoder);
					defaultDecode(&defaultArgumentEncoder, decoderParams);
				}

				Send(remoteProxy->GetLocalServer(), kNamedProxyCall, message);
			}

			virtual bool Equals(FunctionIn pCompare)
			{
				bool result = false;
				if(pCompare->GetFunctionType() == EA::Type::TypeOf<NamedProxyFunction>())
				{
					NamedProxyFunction* other = static_cast<NamedProxyFunction*>(pCompare.GetUnsafePointer());

					EA_ASSERT_MESSAGE(mLocalProxy == other->mLocalProxy, "It is unsafe to compare functions that don't belong to the same proxy");

					result = mProxyName == other->mProxyName && mPath == other->mPath;
				}
				
				return result;
			}

			virtual EA::Type::TypeID GetFunctionType()
			{
				return EA::Type::TypeOf(*this);
			}

		private:

			MessageProxy*	mLocalProxy;
			EA::String		mProxyName;
			EA::RawString	mPath;
		};

		class UnsafeProxyFunction : public Function
		{
		public:
			
			UnsafeProxyFunction(Factory* factory,
								MessageProxyIn localProxy,
								MessageProxyIn remoteProxy,
								Function* unsafeFunction):
				Function(factory),
				mLocalProxy(localProxy),
				mRemoteProxy(remoteProxy),
				mUnsafeFunction(unsafeFunction)
			{
#if !defined(EA_FINAL)
				EA::String debugInfo(factory);
				debugInfo.sprintf("Unsafe proxy function of %p from %s to %s", unsafeFunction, remoteProxy->GetName().c_str(), localProxy->GetName().c_str());
				SetDebugInfo(factory->String(debugInfo));
#endif
			}

			virtual ~UnsafeProxyFunction()
			{
				// we don't own the remote pointer, so we won't be Releasing it
			}

			virtual BaseTypeReturn Clone()
			{
				AddRef();

				return this;
			}

			FunctionReturn Proxy(MessageProxyIn localProxy)
			{
				FunctionRef clone = new (mFactory) UnsafeProxyFunction(mFactory,
																	   localProxy, 
																	   mRemoteProxy, 
																	   mUnsafeFunction);

				// we don't own the remote pointer, so we won't be AddRefing it

				return clone;
			}

			virtual void Call(unsigned int id, 
							  void* decoderParams,
							  void* encoder,
							  DecodeFunction defaultDecode,
							  IEncoder* defaultEncoder)
			{
				CheckData(decoderParams, defaultDecode);

				const eastl::pair<DecodeFunction, DecodeFunction>* decode = GetFactory()->GetCallerMap()->Get(PROXY_CALLER_TYPE, id);

				ObjectRef metadata = new(mFactory) Object(mFactory);

				metadata->insert("unsafeFunction", PtrUserData<Function>::Create(mFactory, mUnsafeFunction));

				EA::Allocator::ICoreAllocator* allocator = mFactory->GetAllocator();

				Message* message = CORE_NEW(allocator, "Message", EA::Allocator::MEM_TEMP) Message(allocator, metadata);
				metadata = NULL;
				
				ByteStreamEncoder byteStreamEncoder(&message->mData);
				ProxyEncoder<ByteStreamEncoder> argumentEncoder(mLocalProxy.GetUnsafePointer(), mRemoteProxy.GetUnsafePointer(), &byteStreamEncoder);

				if(decode != NULL)
				{
					decode->first(&argumentEncoder, decoderParams);
				}
				else
				{
					IEncoderImpl<ProxyEncoder<ByteStreamEncoder> > defaultArgumentEncoder(&argumentEncoder);
					defaultDecode(&defaultArgumentEncoder, decoderParams);
				}

				Send(mRemoteProxy->mLocalServer, kUnsafeProxyCall, message);
				// Send() calls Message::Release()
			}

			virtual bool Equals(FunctionIn pCompare)
			{
				bool result = false;
				if(pCompare->GetFunctionType() == EA::Type::TypeOf<UnsafeProxyFunction>())
				{
					UnsafeProxyFunction* other = static_cast<UnsafeProxyFunction*>(pCompare.GetUnsafePointer());

					EA_ASSERT_MESSAGE(mLocalProxy == other->mLocalProxy, "It is unsafe to compare functions that don't belong to the same proxy");

					result = mRemoteProxy == other->mRemoteProxy && mUnsafeFunction == other->mUnsafeFunction;
				}
				
				return result;
			}

			virtual EA::Type::TypeID GetFunctionType()
			{
				return EA::Type::TypeOf(*this);
			}

			const MessageProxyRef& GetRemoteProxy() const
            {
                return mRemoteProxy;
            }
            
            Function* GetUnsafeFunction() const
            {
                return mUnsafeFunction;
            }

		private:

			MessageProxyRef	mLocalProxy;
			MessageProxyRef	mRemoteProxy;
			Function*		mUnsafeFunction;
		};

		MessageProxy::MessageProxy(Factory* factory, const char* name, bool remoteList):
			ObjectProxy(factory),
			mName(factory, name),
			mMutex(),
			mRefCount(1),
			mLocalServer(CORE_NEW(factory->GetAllocator(), "MessageProxy::mLocalServer", EA::Allocator::MEM_TEMP) EA::Messaging::Server(factory->GetAllocator())),
			mRemoteProxies(factory->GetAllocator()),
			mAnonymousFunctionId(0),
			mAnonymousFunctions(factory->GetAllocator()),
			mLocalList()
		{
			if(remoteList)
			{
				mLocalList = new (mFactory) EA::Types::Functor2<void, FunctionIn, ArrayIn>(mFactory, this, &MessageProxy::LocalList);
			}
        
			mLocalServer->Init();

			mLocalServer->AddHandler(this, kAnonymousProxyCall, false);
			mLocalServer->AddHandler(this, kAnonymousProxyAddRef, false);
			mLocalServer->AddHandler(this, kAnonymousProxyRelease, false);
			mLocalServer->AddHandler(this, kNamedProxyCall, false);
			mLocalServer->AddHandler(this, kUnsafeProxyCall, false);
		}

		MessageProxy::~MessageProxy()
		{
			Shutdown();

			mLocalServer->Shutdown();
			CORE_DELETE(mFactory->GetAllocator(), mLocalServer);
			mLocalServer = NULL;
		}

		/* virtual */ int MessageProxy::AddRef()
		{
			// we must use post-increment with AtomicInt32
			int value = mRefCount++;

			return value + 1;
		}

		/* virtual */ int MessageProxy::Release()
		{
			// we must use post-decrement with AtomicInt32
			int value = (int)(mRefCount--);
            if(value == 1)
            {
                mRefCount = 1;
                DeleteThis();

                return 0;
            }
            return value - 1;
		}

		/* virtual */ int MessageProxy::GetRefCount() const
		{
			return mRefCount;
		}

		void MessageProxy::DeleteThis()
		{
			CORE_DELETE(mFactory->GetAllocator(), this);
		}

		void MessageProxy::Shutdown()
		{
			mLocalServer->RemoveHandler(this, kUnsafeProxyCall);
			mLocalServer->RemoveHandler(this, kNamedProxyCall);
			mLocalServer->RemoveHandler(this, kAnonymousProxyAddRef);
			mLocalServer->RemoveHandler(this, kAnonymousProxyRelease);
			mLocalServer->RemoveHandler(this, kAnonymousProxyCall);

			mRemoteProxies.clear();
			mAnonymousFunctions.clear();
		}

		EA::Messaging::Server* MessageProxy::GetLocalServer()
		{
			return mLocalServer;
		}

		void MessageProxy::AddRemoteProxy(MessageProxyIn proxy)
		{
			EA::Thread::AutoFutex mutex(mMutex);
			// check to see if it's already there?

			mRemoteProxies.insert(proxy->mName).first->second = proxy;
		}

		void MessageProxy::RemoveRemoteProxy(MessageProxyIn proxy)
		{
			EA::Thread::AutoFutex mutex(mMutex);

			mRemoteProxies.erase(proxy->mName);
		}

		MessageProxyReturn MessageProxy::GetRemoteProxy(const EA::String& name)
		{
			EA::Thread::AutoFutex mutex(mMutex);

			RemoteProxyMap::iterator it = mRemoteProxies.find(name);

			if(it != mRemoteProxies.end())
			{
				return (*it).second;
			}
			else
			{
				return NULL;
			}
		}

		void MessageProxy::LocalList(FunctionIn callback, ArrayIn visited)
		{
			return ObjectProxy::List(callback, visited);
		}

		/* virtual */ ObjectReturn MessageProxy::ToObject()
		{
			ObjectRef that = ObjectProxy::ToObject();

			that->insert("LocalList", new (mFactory) EA::Types::Functor2<void, FunctionIn, ArrayIn>(mFactory, this, &MessageProxy::LocalList));

			return that;
		}
        
        /* virtual */ void MessageProxy::List(FunctionIn callback, ArrayIn visited)
        {
			ArrayRef v;

			if(visited != NULL)
			{
				Array::const_iterator it = visited->begin();
				Array::const_iterator end = visited->end();
            
				for(; it != end; ++it)
				{
					if(PtrUserData<IProxy>::AsPtr(*it) == this)
					{
						EA::RawString  error(mFactory->GetAllocator());
						error.sprintf("MessageProxy %p", this);
						callback->Call<void>(error);
						return;
					}
				}
            
				v = visited->Clone()->AsArray();
			}
			else
			{
				v = mFactory->Array();
			}

            v->push_back(PtrUserData<IProxy>::Create(mFactory, this));
            
            ObjectRef object = mFactory->Object();
            
            object->insert(mName, ListBaseType(mBaseType, v));
            
            RemoteProxyMap::iterator pIt = mRemoteProxies.begin();
            RemoteProxyMap::iterator pEnd = mRemoteProxies.end();
            
            for(; pIt != pEnd; ++pIt)
            {
                const EA::String& name = (*pIt).first;
				MessageProxyRef& messageProxy = (*pIt).second;

				Function* unsafeFunction = messageProxy->GetLocalList();

				BaseTypeRef list;
                
				// some message proxies are not listable remotely
				if(unsafeFunction != NULL)
				{
					FutureFunctionRef future = Future(mFactory);
					FunctionRef function = new (mFactory) UnsafeProxyFunction(mFactory, MessageProxyIn(this), messageProxy, unsafeFunction);
					function->Call<void>(future->GetResolver(), v);
					list = future;
				}

				object->insert(name, list);
            }
            
            FutureFunctionRef result = AsFuture(mFactory, object);
            result->AddCallback(callback);
        }

		/* virtual */ FunctionReturn MessageProxy::GetFunction(const char* path)
		{
			EA::String name(mFactory);
			if(!ProxyPathNext(path, name))
			{
				return NULL;
			}
			else
			{
				if(name == mName)
				{
					return ObjectProxy::GetFunction(path);
				}
				else
				{
					return new(mFactory) NamedProxyFunction(mFactory, this, name, path);
				}
			}
		}

		/* virtual */ ObjectReturn MessageProxy::GetInterface(const char* path, ArrayIn schema)
		{

			EA::String name(mFactory);
			if(!ProxyPathNext(path, name))
			{
				return NULL;
			}
			else
			{
				if(name == mName)
				{
					return ObjectProxy::GetInterface(path, schema);
				}
				else
				{
					ObjectRef object = mFactory->Object();

					Array::const_iterator sIt = schema->begin();
					Array::const_iterator sEnd = schema->end();

					for(; sIt != sEnd; ++sIt)
					{
						const EA::String fName = (*sIt)->AsEAString();

						EA::RawString fPath(mFactory->GetAllocator(), path);
						fPath.append("/");
						fPath.append(fName.c_str());

						object->insert(fName, new(mFactory) NamedProxyFunction(mFactory, this, name, fPath.c_str()));
					}

					return object;
				}
			}
		}

		/* virtual */ ObjectReturn MessageProxy::GetInterface(const char* path, const char** schema)
		{
			return IProxy::GetInterface(mFactory, path, schema);
		}

		FunctionReturn MessageProxy::EncodeFunction(MessageProxyIn remoteProxy, FunctionIn function)
		{
			if(function->GetFunctionType() == EA::Type::TypeOf<AnonymousProxyFunction>())
			{
				AnonymousProxyFunction* anonymous = static_cast<AnonymousProxyFunction*>(function.GetUnsafePointer());

				// re-proxy the function with the remote proxy as the new local proxy
				return anonymous->Proxy(remoteProxy);
			}
			else if(function->GetFunctionType() == EA::Type::TypeOf<UnsafeProxyFunction>())
			{
				UnsafeProxyFunction* unsafe = static_cast<UnsafeProxyFunction*>(function.GetUnsafePointer());

				// re-proxy the function with the remote proxy as the new local proxy
				return unsafe->Proxy(remoteProxy);
			}
			else
			{
				mAnonymousFunctions[mAnonymousFunctionId] = eastl::pair<FunctionRef, int>(function, 1);

#if !defined(EA_FINAL)
				ArrayRef debugInfo = mFactory->Array();
				EA::String proxyDebugInfo(mFactory);
				proxyDebugInfo.sprintf("Anonymous proxy function from %s to %s", mName.c_str(), remoteProxy->mName.c_str());
				debugInfo->push_back(proxyDebugInfo);

				BaseTypeRef proxiedDebugInfo = function->GetDebugInfo();
				debugInfo->push_back(proxiedDebugInfo != NULL ? proxiedDebugInfo->Clone() : BaseTypeRef(NULL));
#endif

				// when we proxy a function, the local proxy becomes the remote proxy for the function, and the remote proxy becomes the local proxy
				return new (mFactory) AnonymousProxyFunction(
						mFactory, 
						remoteProxy, 
						MessageProxyIn(this), 
						mAnonymousFunctionId++
#if !defined(EA_FINAL)
						, debugInfo
#endif
					);
			}
		}
        
        FunctionReturn MessageProxy::DecodeFunction(FunctionIn function)
        {
			FunctionRef result = function;

			if(function->GetFunctionType() == EA::Type::TypeOf<AnonymousProxyFunction>())
			{
				AnonymousProxyFunction* anonymous = static_cast<AnonymousProxyFunction*>(function.GetUnsafePointer());

				if(anonymous->GetRemoteProxy().GetUnsafePointer() == this)
				{
					result = mAnonymousFunctions[anonymous->GetId()].first;
				}
			}
			else if(function->GetFunctionType() == EA::Type::TypeOf<UnsafeProxyFunction>())
			{
				UnsafeProxyFunction* unsafe = static_cast<UnsafeProxyFunction*>(function.GetUnsafePointer());

				if(unsafe->GetRemoteProxy().GetUnsafePointer() == this)
				{
					result = FunctionIn(unsafe->GetUnsafeFunction());
				}
			}

			return result;
        }

		/* private */ Function* MessageProxy::GetLocalList()
		{
			return mLocalList.GetUnsafePointer();
		}

		/* private virtual */ bool MessageProxy::HandleMessage(EA::Messaging::MessageId messageId, 
															   void* message)
		{
			switch(messageId)
			{
				case kAnonymousProxyCall:
					{
						Message* data = static_cast<Message*>(message);

						HandleAnonymousProxyCall((*data->mMetadata->AsObject())["id"]->AsInt(), &data->mData);
					}
					break;
				case kAnonymousProxyAddRef:
					{
						Message* data = static_cast<Message*>(message);

						HandleAnonymousProxyAddRef((*data->mMetadata->AsObject())["id"]->AsInt());
					}
					break;
				case kAnonymousProxyRelease:
					{
						Message* data = static_cast<Message*>(message);

						HandleAnonymousProxyRelease((*data->mMetadata->AsObject())["id"]->AsInt());
					}
					break;
				case kNamedProxyCall:
					{
						Message* data = static_cast<Message*>(message);

						ObjectRef metadata = data->mMetadata->AsObject();

						HandleNamedProxyCall((*metadata)["path"]->AsCStr(), &data->mData);
					}
					break;
				case kUnsafeProxyCall:
					{
						Message* data = static_cast<Message*>(message);

						ObjectRef metadata = data->mMetadata->AsObject();

						HandleUnsafeProxyCall(PtrUserData<Function>::AsPtr((*metadata)["unsafeFunction"]), &data->mData);
					}
					break;
				default:
					break;
			}

			return true;
		}

		/* private */ void MessageProxy::HandleAnonymousProxyCall(int id, const ByteStream* in)
		{
			AnonymousFunctionMap::const_iterator it = mAnonymousFunctions.find(id);

			if(it != mAnonymousFunctions.end())
			{
				Call((*it).second.first, in);
			}
			else
			{
				EA_FAIL_FORMATTED(("No such anonymous proxy function with id %d!", id));
			}
		}
		
		/* private */ void MessageProxy::HandleAnonymousProxyAddRef(int id)
		{
			AnonymousFunctionMap::iterator it = mAnonymousFunctions.find(id);

			if(it != mAnonymousFunctions.end())
			{
				++(*it).second.second;
			}
			else
			{
				EA_FAIL_FORMATTED(("No such anonymous proxy with id %d!", id));
			}
		}

		/* private */ void MessageProxy::HandleAnonymousProxyRelease(int id)
		{
			AnonymousFunctionMap::iterator it = mAnonymousFunctions.find(id);

			if(it != mAnonymousFunctions.end())
			{
				if(--(*it).second.second <= 0)
				{
					mAnonymousFunctions.erase(it);
				}
			}
			else
			{
				EA_FAIL_FORMATTED(("No such anonymous proxy with id %d!", id));
			}
		}

		/* private */ void MessageProxy::HandleNamedProxyCall(const char* path, const ByteStream* in)
		{
			FunctionRef function = ObjectProxy::GetFunction(path);

			if(function != NULL)
			{
				Call(function, in);
			}
		}

		/* private */ void MessageProxy::HandleUnsafeProxyCall(Function* function, const ByteStream* in)
		{
			Call(FunctionIn(function), in);
		}

		/* private */ void MessageProxy::Call(FunctionIn function, const ByteStream* in)
		{
			ProxyDecoderParams decoderParams(this, in);

			ErrorEncoder encoder;
				
			IEncoderImpl<ErrorEncoder> defaultEncoder(&encoder);

			function->Call(PROXY_CALLER_TYPE, &decoderParams, &encoder, ProxyDecoder<IEncoder>::Decode, &defaultEncoder);
		}
	}
}
