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

#ifndef EABASETYPESMESSAGE_TYPES_MESSAGEPROXY_H_
#define EABASETYPESMESSAGE_TYPES_MESSAGEPROXY_H_

#include "eamessage/eamessage.h"

#include "EAThread/eathread_futex.h"
#include "EAThread/eathread_atomic.h"

#include "EABaseTypes/string.h"
#include "EABaseTypes/autoref.h"

#include "EABaseTypesPlus/objectproxy.h"

#include "EABaseTypesMessage/proxyencoder.h"

#include "EASTLICA/vector.h"
#include "EASTLICA/map.h"


namespace EA
{
	namespace Allocator
	{
		class ICoreAllocator;
	}

	namespace Types
	{
		EABASETYPES_FORWARD_DECLARATION(class, MessageProxy)
		class Factory;

		static const unsigned int PROXY_CALLER_TYPE = 0x89543542;

		/*!
		*	Template function for adding an optimized caller for proxies
		*	@tparam Decoder	The argument decoder type for the caller
		*	@tparam	Encoder The result encoder type for the caller
		*	@param	id		The unique id for the caller
		*/
		template<template<class> class Decoder, class Encoder>
		void AddProxyCaller(CallerMap* callerMap, unsigned int id)
		{
			callerMap->Add(PROXY_CALLER_TYPE, id, Decoder<ProxyEncoder<ByteStreamEncoder> >::Decode, ByteStreamDecoder<Encoder>::Decode);
		}

		/*!
		*	Proxy a BaseType between message proxies
		*	@param	baseType	The data to proxy
		*	@param	src			The source MessageProxy
		*	@param	dst			The destination MessageProxy
		*/
		BaseTypeReturn ProxyBaseType(BaseTypeIn baseType, MessageProxyIn src, MessageProxyIn dst);

		/*! @class MessageProxy messageproxy.h	"EABaseTypesMessage/messageproxy.h"
		*	@brief Message handler that proxies Functions across an EA::Message::Server
		*/
		class MessageProxy : public ObjectProxy,
							 private EA::Messaging::IHandler
		{
		public:

			/*!
			*	MessageProxy constructor
			*	@param	factory			A factory to create EABaseType objects with.
			*	@param	name			The name of the message proxy (must be globally unique)
			*	@param	remoteList		Allow this message proxy to be listed remotely
			*/
			MessageProxy(Factory* factory, const char* name, bool remoteList = true);

			/*!
			*	MessageProxy destructor
			*	@note	This should never be called directly
			*/
			~MessageProxy();

			// modeled after SafeCom, temporary until we have better refcounting templates
			virtual int AddRef();

			virtual int Release();

			virtual int GetRefCount() const;

			const EA::String& GetName() { return mName; }

			/*!
			*	Shutdown the message proxy - needed because it may not get destroyed immediately due to other threads having references
			*/
			void Shutdown();

			EA::Messaging::Server* GetLocalServer();

			/*!
			*	Add a remote proxy to send messages to
			*	@param	proxy		The remote proxy
			*/
			void AddRemoteProxy(MessageProxyIn proxy);

			/*
			*	Remove a remote proxy
			*	@param	proxy		The remote proxy
			*/
			void RemoveRemoteProxy(MessageProxyIn proxy);

			/*
			*	Retrieve a remote proxy
			*	@param	proxyName	The name of the proxy
			*/
			MessageProxyReturn GetRemoteProxy(const EA::String& name);

			/*!
            *   Recursively list the local contents of the proxy
            *   @param  FunctionIn      The function to call with the data
            *   @param  ArrayIn         Previously visited proxies
            */
			void LocalList(FunctionIn callback, ArrayIn visited);

			virtual ObjectReturn ToObject();
            
            virtual void List(FunctionIn callback, ArrayIn visited);

			virtual FunctionReturn GetFunction(const char* path);

			virtual ObjectReturn GetInterface(const char* path, ArrayIn schema);

			virtual ObjectReturn GetInterface(const char* path, const char** schema);

			/*!
			*	Encode a function in the context of this message proxy
			*	@param	function	The function to encode
			*	@return	FunctionReturn	The encoded function
			*/
			FunctionReturn EncodeFunction(MessageProxyIn remoteProxy, FunctionIn function);
            
            /*!
			*	Decode a function in the context of this message proxy
			*	@param	function	The function to decode
			*	@return	FunctionReturn	The decoded function
			*/
            FunctionReturn DecodeFunction(FunctionIn function);

		private:
		
			//no copy, no assignment
			MessageProxy(const MessageProxy& other);
			MessageProxy& operator=(const MessageProxy& rhs);

			void DeleteThis();

			friend class AnonymousProxyFunction;
			friend class NamedProxyFunction;
			friend class UnsafeProxyFunction;

			Function* GetLocalList();

			virtual bool HandleMessage(EA::Messaging::MessageId messageId, 
									   void* message);

			void HandleAnonymousProxyCall(int id, const ByteStream* in);
			void HandleAnonymousProxyAddRef(int id);
			void HandleAnonymousProxyRelease(int id);
			void HandleNamedProxyCall(const char* path, const ByteStream* in);
			void HandleUnsafeProxyCall(Function* function, const ByteStream* in);

			void Call(FunctionIn function, const ByteStream* in);

			EA::String						mName;

			EA::Thread::Futex				mMutex;

			EA::Thread::AtomicInt32			mRefCount;

			EA::Messaging::Server*			mLocalServer;

			typedef EASTLICA::Map<EA::String, MessageProxyRef> RemoteProxyMap;
			RemoteProxyMap					mRemoteProxies;

			int mAnonymousFunctionId;
			typedef EASTLICA::Map<int, eastl::pair<FunctionRef, int> > AnonymousFunctionMap;
			AnonymousFunctionMap			mAnonymousFunctions;

			FunctionRef						mLocalList;
		};
	}
}

#endif /* EABASETYPESMESSAGE_TYPES_MESSAGEPROXY_H_ */
