/** @file objectproxy.cpp
*	@headerfile objectproxy.h "EABaseTypesPlus/objectproxy.h"
*	Proxy implemented using an Object
*	@author Chris Burns (cburns@ea.com)
*
*	(c) 2013 Electronic Arts Inc.
*/

#include "EABaseTypesPlus/objectproxy.h"

#include "EAAssert/eaassert.h"

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

#include "EABaseTypesFuture/future.h"

namespace EA
{
	namespace Types
	{
		ObjectProxy::ObjectProxy(Factory* factory):
			IProxy(),
			mFactory(factory),
			mBaseType()
		{
		}

		/* virtual */ ObjectProxy::~ObjectProxy()
		{
		}

		/* virtual */ ObjectReturn ObjectProxy::ToObject()
		{
			ObjectRef that = mFactory->Object();

			that->insert("Add", new (mFactory) Functor2<void, const char*, BaseTypeIn>(mFactory, this, &ObjectProxy::Add));
			that->insert("Remove", new (mFactory) Functor1<void, const char*>(mFactory, this, &ObjectProxy::Remove));
            that->insert("List", new (mFactory) Functor2<void, FunctionIn, ArrayIn>(mFactory, this, &ObjectProxy::List));
			that->insert("GetFunction", new (mFactory) Functor1<FunctionReturn, const char*>(mFactory, this, &ObjectProxy::GetFunction));
			that->insert("GetInterface", new (mFactory) Functor2<ObjectReturn, const char*, ArrayIn>(mFactory, this, &ObjectProxy::GetInterface));

			return that;
		}

		/* virtual */ void ObjectProxy::Add(const char* path, BaseTypeIn baseType)
		{
			if(ProxyPathHasNext(path))
			{
				if(mBaseType == NULL) mBaseType = mFactory->Object();

				EA::String name(mFactory);

				BaseTypeRef current = mBaseType;

				while(ProxyPathNext(path, name))
				{
					ObjectRef object = current->AsObject();

					if(ProxyPathHasNext(path))
					{
						Object::const_iterator oIt = object->find(name);

						if(oIt == object->end())
						{
							current = object->insertObject(name);
						}
						else
						{
							current = (*oIt).second;
						}
					}
					else
					{
						object->insert(name, baseType);
					}
				}
			}
			else
			{
				EA_ASSERT_MESSAGE(mBaseType == NULL, ("The root has already been populated"));
				mBaseType = baseType;
			}
		}

		/* virtual */ void ObjectProxy::Remove(const char* path)
		{
			if(ProxyPathHasNext(path))
			{
				EA::String name(mFactory);

				BaseTypeRef current = mBaseType;

				while(ProxyPathNext(path, name))
				{
					ObjectRef object = current->AsObject();

					if(ProxyPathHasNext(path))
					{
						Object::const_iterator oIt = object->find(name);
						if(oIt != object->end())
						{
							current = (*oIt).second;
						}
						else
						{
							break;
						}
					}
					else
					{
						object->erase(name);
					}
				}
			}
			else
			{
				mBaseType = NULL;
			}
		}
        
        /* virtual */ void ObjectProxy::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));
            
            FutureFunctionRef future = AsFuture(mFactory, ListBaseType(mBaseType, v));
            
            future->AddCallback(callback);
        }

		/* virtual */ FunctionReturn ObjectProxy::GetFunction(const char* path)
		{
			EA::String name(mFactory);

			BaseTypeRef current = mBaseType;

			for(;;)
			{
				if(current->GetType() == USERDATA_TYPE)
				{
					return IProxy::AsIProxyRef(current)->GetFunction(path);
				}
				else if(ProxyPathNext(path, name))
				{
					ObjectRef object = current->AsObject();

					Object::const_iterator oIt = object->find(name);
					if(oIt != object->end())
					{
						current = (*oIt).second;
					}
					else
					{
						return NULL;
					}
				}
				else
				{
					return current->AsFunction();
				}
			}
		}

		/* virtual */ ObjectReturn ObjectProxy::GetInterface(const char* path, ArrayIn schema)
		{
			EA::String name(mFactory);

			BaseTypeRef current = mBaseType;

			for(;;)
			{
				if(current->GetType() == USERDATA_TYPE)
				{
					return IProxy::AsIProxyRef(current)->GetInterface(path, schema);
				}
				else 
				{
					ObjectRef object = current->AsObject();

					if(ProxyPathNext(path, name))
					{
						Object::const_iterator oIt = object->find(name);
						if(oIt != object->end())
						{
							current = (*oIt).second;
						}
						else
						{
							return NULL;
						}
					}
					else
					{
						ObjectRef that = mFactory->Object();

						Array::const_iterator sIt = schema->begin();
						Array::const_iterator sEnd = schema->end();
						for(; sIt != sEnd; ++sIt)
						{
							const EA::String& name = (*sIt)->AsEAString();
							that->insert(name, (*object)[name]);
						}

						return that;
					}
				}
			}
		}

		/* virtual */ ObjectReturn ObjectProxy::GetInterface(const char* path, const char** schema)
		{
			return IProxy::GetInterface(mFactory, path, schema);
		}
        
        /* protected*/ BaseTypeReturn ObjectProxy::ListBaseType(BaseTypeIn baseType, ArrayIn visited)
        {
			BaseTypeRef result;

			if(baseType != NULL)
			{
				DataType type = baseType->GetType();
                
				if(type == USERDATA_TYPE)
				{
					FutureFunctionRef future = Future(mFactory);
					IProxy::AsIProxyRef(baseType)->List(future->GetResolver(), visited);
					result = future;
				}
				else if(type == OBJECT_TYPE)
				{
					ObjectRef object = baseType->AsObject();

					Object::const_iterator it = object->begin();
					Object::const_iterator end = object->end();
                
					ObjectRef o = mFactory->Object();
                
					for(; it != end; ++it)
					{
						o->insert((*it).first, ListBaseType((*it).second, visited));
					}
                
					result = o;
				}
				else if(type == FUNCTION_TYPE)
				{
					result = mFactory->String("Function");
				}
				else
				{
					result = mFactory->String("Unexpected type");
				}
			}

			return result;
        }
	}
}
