/** @file AsyncFileHandler.cpp
 *	Generic interface for file handling
 *	@headerfile AsyncFileHandler.h "EABaseTypesPlus/AsyncFileHandler.h"
 *	@author Chris Burns (cburns@ea.com)
 *
 *	UX
 *	(c) 2012 Electronic Arts Inc.
 */

#include "EABaseTypesPlus/asyncfilehandler.h"
#include "EABaseTypes/factory.h"
#include "EASTLICA/vector.h"
#include "asyncfileloader.h"
#include "coreallocator/icoreallocatormacros.h"
#include "eathread/eathread_futex.h"

namespace EA
{
namespace Types
{

	/*!
	*	@struct AsyncFileHandlerImpl
	*	@brief Private data belonging to AsyncFileHandler
	*/
	struct AsyncFileHandler::AsyncFileHandlerImpl
	{
		AsyncFileHandlerImpl(EA::Allocator::ICoreAllocator* allocator)
			: mFileLoadsFutex()
			, mCompleteLoads(allocator, "AsyncFileHandlerImpl::mCompleteLoads")
			, mPendingLoads(allocator, "AsyncFileHandlerImpl::mPendingLoads")
		{
		}

		~AsyncFileHandlerImpl()
		{
			AsyncFileLoaderArray::iterator it = mPendingLoads.begin();
			AsyncFileLoaderArray::iterator end = mPendingLoads.end();
			for(; it != end; ++it)
			{
				AsyncFileLoader* loader = (*it);
				CORE_DELETE(loader->GetAllocator(), (*it));
			}
			mPendingLoads.clear();
			
			it = mCompleteLoads.begin();
			end = mCompleteLoads.end();
			for(; it != end; ++it)
			{
				AsyncFileLoader* loader = (*it);
				CORE_DELETE(loader->GetAllocator(), (*it));
			}
			mCompleteLoads.clear();
		}

		typedef EASTLICA::Vector<AsyncFileLoader*> AsyncFileLoaderArray;
		EA::Thread::Futex				mFileLoadsFutex;
		AsyncFileLoaderArray			mCompleteLoads;
		AsyncFileLoaderArray			mPendingLoads;

	private:

		AsyncFileHandlerImpl(const AsyncFileHandlerImpl& other);
		const AsyncFileHandlerImpl& operator=(const AsyncFileHandlerImpl& rhs);
	};

	AsyncFileHandler::AsyncFileHandler(EA::Allocator::ICoreAllocator* allocator, const char* root)
		: mRoot(allocator, root)
		, mImpl(NULL)
		, mAllocator(allocator)
	{
		mImpl = CORE_NEW(mAllocator, "AsyncFileHandlerImpl", EA::Allocator::MEM_PERM) AsyncFileHandlerImpl(mAllocator);
	}

	AsyncFileHandler::~AsyncFileHandler()
	{
		CORE_DELETE(mAllocator, mImpl);
	}

	//LINT Custodial pointer 'loader' has not been freed or returned. We free it when then load completes in AsyncFileHandler::Update
	//lint -efunc(429, EA::Types::AsyncFileHandler::Load)
	void AsyncFileHandler::Load(EA::Types::IFileHandlerCallbackIn callback, EA::Allocator::ICoreAllocator *allocator, const char* fileName)
	{
		AsyncFileLoader* loader = CORE_NEW(allocator, "AsyncFileLoader", EA::Allocator::MEM_TEMP) AsyncFileLoader(allocator, 
																												  this,
																												  callback, 
																												  fileName);
		mImpl->mFileLoadsFutex.Lock();
		mImpl->mPendingLoads.push_back(loader);
		mImpl->mFileLoadsFutex.Unlock();

		loader->StartLoad();
	}

	void AsyncFileHandler::Update()
	{
		//copy the complete loads so we don't tie up the futex
		//Load complete callbacks may add queue more files to load so we don't want to be locked while calling those
		AsyncFileHandlerImpl::AsyncFileLoaderArray tmpList(mAllocator);
		mImpl->mFileLoadsFutex.Lock();
		for(AsyncFileHandlerImpl::AsyncFileLoaderArray::const_iterator it = mImpl->mCompleteLoads.begin(), end = mImpl->mCompleteLoads.end(); it != end; ++it)
		{
			tmpList.push_back(*it);
		}
		mImpl->mCompleteLoads.clear();
		mImpl->mFileLoadsFutex.Unlock();

		for(AsyncFileHandlerImpl::AsyncFileLoaderArray::iterator it = tmpList.begin(), end = tmpList.end(); it != end; ++it)
		{
			AsyncFileLoader* loader = (*it);
			loader->GetCallback()->LoadComplete(loader->GetResource());
			CORE_DELETE(loader->GetAllocator(), loader);
		}
		tmpList.clear();
	}

	void AsyncFileHandler::AsyncLoadComplete(AsyncFileLoader* loader)
	{
		EA::Thread::AutoFutex af(mImpl->mFileLoadsFutex);
		mImpl->mCompleteLoads.push_back(loader);

		AsyncFileHandlerImpl::AsyncFileLoaderArray::iterator it = mImpl->mPendingLoads.begin();
		AsyncFileHandlerImpl::AsyncFileLoaderArray::iterator end = mImpl->mPendingLoads.end();
		for(; it != end; ++it)
		{
			AsyncFileLoader* pending = (*it);
			if(pending == loader)
			{
				mImpl->mPendingLoads.erase(it);
				break;
			}
		}
	}
}
}
