/*
	Lan Wars.
	Copyright (c) 2009, Darragh Coy.
	All rights reserved.
	Original work(s), source code & author(s) details available at: http://www.darraghcoy.com

	Redistribution and use in source and binary forms, with or without modification, are permitted 
	provided that the following conditions are met:

		* 	Redistributions of source code must retain the above copyright notice, this list of 
			conditions and the following disclaimer.
		* 	Redistributions in binary form must reproduce the above copyright notice, this list of 
			conditions and the following disclaimer in the documentation.
		* 	Neither the name of the author nor the names of contributors may be used to endorse or 
			promote products derived from this software without specific prior written permission.
		* 	The use of any third party libraries, modules or data files provided with the source 
			form of the program must follow and respect the individual licensing terms of these 
			components. You do not have permission to modify or circumvent the licensing terms for 
			provided third party components unless the third party licenses specifically allow this. 
			If you do not agree to the terms of provided third party components then you do not have 
			permission to use them in your derived work(s).
		* 	The terms of this license do not apply to separate (but related) distributions if supplied 
			by the author(s), only to the contents of this source distribution, derived works, and the 
			binaries produced by either modified or unmodified versions of the source code. The terms 
			of this license do not also apply to any third party libraries or components provided with 
			the source distribution. Such third party elements are licensed under their own separate 
			agreements.

	THIS SOFTWARE IS PROVIDED BY DARRAGH COY ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 
	BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
	ARE DISCLAIMED. IN NO EVENT SHALL DARRAGH COY BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
	EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 
	OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 
	OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 
	IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#ifndef RESOURCE_MANAGER_H
#define RESOURCE_MANAGER_H

//=====================================================================================================================
//=====================================================================================================================

#include "Resources\Resource.h"

//=====================================================================================================================
//=====================================================================================================================

namespace Game
{
	//=================================================================================================================
	/// @brief		Base class for all resource managers, textures and so forth etc. This class is templatized so that 
	///				the core management code, which is common to all resource managers, can be reused again and again.
	//=================================================================================================================
	
	template <class ResourceType> class ResourceManager : public ReferencedObject
	{
		public:
	    
			//=========================================================================================================
			// Friends
			//=========================================================================================================
			
				/// ManagedResource is the only class that can load and free actual resource instances, it needs private access 	    
	        
				friend class ManagedResource;    
	    
			//=========================================================================================================
			// Classes
			//=========================================================================================================
	        
				//=====================================================================================================
				///	@brief		This class holds a managed reference to an actual resource instance. These references 
				///				can be freely passed around and copied etc. All access to resources has to be done 
				///				through these managed objects. Get a managed resource by calling 
				///				ResourceManager::GetResource(). 
				/// @remarks	This class is not used directly, even though it is possible to do so. Wrapper classes 
				///				such as Texture, PixelShader and VertexShader encapsulate this object for 
				///				their particular resource type and add extra functionality for easier use. 
				//=====================================================================================================
				
				class ManagedResource
				{
					public:
					
						//=============================================================================================
						///	@brief		Default constructor. Creates a default null resource that is invalid.
						//=============================================================================================
					
						ManagedResource()
						{
							m_manager	= 0;
							m_resource	= 0;
						}
					
						//=============================================================================================
						///	@brief		Constructor.
						///	@param		name - name of the managed resource
						///	@param		manager - manager managing the resources
						/// @pre		Given manager must not be null
						/// @pre		Given name must not be zero length 
						//=============================================================================================
						
						ManagedResource( const std::string & name , ResourceManager * manager ) : m_manager(manager)
						{
							// Make sure ok:
							
							ENSURE( name.length() > 0 , "Given name must not be zero length" );
							
							// Make sure ok:
							
							ENSURE( manager , "Given manager must not be null" );
						
							// Load this resource
							
							m_resource = manager->Load(name);
							
							// Add a reference to our manager:
							
							manager->AddReference();											
						}
						
						//=============================================================================================
						///	@brief	Destructor. Frees the managed resource.
						//=============================================================================================
						
						~ManagedResource()
						{
							if ( m_manager ) 
							{
								// Free this resource
								
								if ( m_resource ) m_manager->Free( m_resource->GetName() );
							
								// Release reference to the resource manager
							
								m_manager->RemoveReference();
							}
						}					
						
						//=============================================================================================
						///	@brief	Copy constructor. Creates this resource from another resource.
						//=============================================================================================
						
						ManagedResource( const ManagedResource & other )
						{
							// Save the resource manager:
							
							m_manager = other.m_manager;
							
							// Make sure not null:														
							
							if ( other.IsNull() == false )
							{
								// Add a reference to our manager:
								
								m_manager->AddReference();										
						
								// Load the resource name given in the other resource:
								
								m_resource = other.m_manager->Load( other.m_resource->GetName() );
							}
							else
							{
								m_resource = 0;
							}
						}
						
						//=============================================================================================
						///	@brief	Assignment operator. Assigns the other resource to this resource.
						//=============================================================================================
						
						ManagedResource & operator = ( const ManagedResource & other )
						{
							// Only if it is not the same:
							
							if ( &other != this )						
							{
								// Save the previous resource
								
								Resource<ResourceType> * prev_resource = m_resource;
								
								// Save the previous manager
								
								ResourceManager * prev_manager = m_manager;
								
								// Use the new manager
								
								m_manager = other.m_manager;
								
								// Make sure other object has a manager
								
								if ( other.m_manager )
								{								
									// Add a reference to the new manager:
								
									other.m_manager->AddReference();
									
									// Load the new resource with the new manager
									
									if ( other.m_resource )
									{
										m_resource = m_manager->Load( other.m_resource->GetName() );
									}
									else
									{
										m_resource = 0;
									}
								}								
								
								// Free up old resource
								
								if ( prev_resource ) 
								{
									if ( prev_manager )
									{
										// Now free the previous resource with the previous manager
									
										prev_manager->Free( prev_resource->GetName() );
								
										// Free reference to the previous resource manager:
										
										if ( prev_manager ) prev_manager->RemoveReference();
									}
								}
								else
								{
									if ( prev_manager ) prev_manager->RemoveReference();
								}
							}	
							
							// Return this object
							
							return * this;										
						}
						
						//=============================================================================================
						///	@brief		Returns the actual resource itself. Do not delete this pointer! 
						///	@returns	Pointer to the actual resource.
						/// @pre		This must not be null resource to be deferenced
						//=============================================================================================
						
						inline Resource<ResourceType> operator * () const 
						{ 
							// Make sure ok:
							
							ENSURE( m_resource != 0 , "This must not be null resource to be deferenced" );
							
							// Return:
							
							return m_resource->GetData(); 
						}
						
						//=============================================================================================
						///	@brief		Returns the name of the resource
						/// @returns	Name of the resource
						/// @remarks	Will return a blank string if this is a null resource
						//=============================================================================================
						
						inline std::string GetName() const 
						{ 
							if ( IsNull() )
							{
								return string();
							}
							else
							{
								return m_resource->GetName(); 
							}
						}
						
						//=================================================================================================
						///	@brief		Returns the manager of the resource.
						/// @returns	Manager of the resource
						/// @remarks	Will return null if this is a null resource
						//=================================================================================================
						
						inline ResourceManager * GetManager(){ return m_manager; }
						
						//=============================================================================================
						///	@brief		Returns the resource instance. There will only ever be one of this
						/// @returns	The resource itself
						/// @remarks	Will return null if this is a null resource
						//=============================================================================================
						
						inline Resource<ResourceType> * GetResource() const { return m_resource; }
						
						//=============================================================================================
						///	@brief		Returns the user data of the resource.
						/// @returns	The user data of the resource
						/// @remarks	Returns 0 if this is a default null resource
						//=============================================================================================
						
						inline long GetResourceUserData() const 
						{ 
							if ( m_resource )
							{
								return m_resource->GetUserData(); 
							}
							else
							{
								return 0;
							}
						}
						
						//=================================================================================================
						///	@brief		Returns the user data of the resource manager itself.
						/// @returns	The user data of the resource manager
						/// @remarks	Returns 0 if this is a default null resource
						//=================================================================================================
						
						inline long GetManagerUserData() const 
						{ 
							if ( m_resource )
							{
								return m_resource->GetManagerUserData(); 
							}
							else
							{
								return 0;
							}
						}
						
						//=============================================================================================
						///	@brief		Tells if this is a null resource. A null resource is created by the default
						///				constructor and has no pointer to a resource instance or a manager. Null 
						///				resources are not valid.
						/// @returns	True if the resource is a null resource
						//=============================================================================================
						
						inline bool IsNull() const 
						{
							if ( m_resource == 0 || m_manager == 0 )
							{
								return true;
							}
							else
							{
								return false;
							}					
						}
						
						//=============================================================================================
						///	@brief		Tells if the resource is valid
						/// @returns	True if the resource is valid
						/// @remarks	Null resources are never valid, so this can be used to check for that also
						//=============================================================================================
						
						inline bool IsValid() const 
						{ 
							if ( IsNull() == false )
							{
								return m_resource->IsValid(); 
							}
							else
							{
								return false;
							}
						}		
					
					private:
					
						//=============================================================================================
						// Variables
						//=============================================================================================
						
							/// Pointer to the actual resource instance itself: not to be deleted directly!
							
							Resource<ResourceType> * m_resource;
							
							/// Pointer to the resource manager
							
							ResourceManager * m_manager;
				};

			//=========================================================================================================
			// Functions
			//=========================================================================================================
	        
				//=====================================================================================================
				///	@brief		Retrieves the named resource. If the resource is not loaded then the manager loads it.
				/// @param		name - name of the resource to get
				///	@returns	Managed resource of the given name.
				//=====================================================================================================        
				
				ManagedResource GetResource( const std::string & name )
				{
					return ManagedResource(name,this);
				}
				
				//=====================================================================================================
				///	@brief		Retrieves the named resource. If the resource is not loaded then the manager loads it.
				/// @param		name - name of the resource to get
				///	@returns	Managed resource of the given name.
				/// @pre		Given name must not be null
				//=====================================================================================================        
				
				ManagedResource GetResource( const char * name )
				{				
					// Make sure ok:
					
					ENSURE( name != 0 , "Given name must not be null" );

					// Return the resource
					
					return ManagedResource(name,this);
				}					
				
				//=====================================================================================================
				///	@brief		Constructor. Creates the resource manager.
				///	@param		user_data - manager specific data for this manager that is to be passed down to all
				///				constructed resources.
				//=====================================================================================================
	        
				ResourceManager( long user_data )
				{
					m_user_data = user_data;
				}  			
	        
				//=====================================================================================================
				///	@brief		Destructor. Destroys all resources.
				/// @pre		All managed resources should have had their references cleared.
				/// @remarks	In debug mode if there are resources left in the manager, this function will fail
				///				spectucularly. In release, the manager will release any resources that are in use and 
				///				hope for the best that no attempt is made to use them.
				//=====================================================================================================
	        
				virtual ~ResourceManager()
				{
					// Make sure ok:
				
					ENSURE( m_resources.size() == 0 , "All managed resources should have had their references cleared." )
				
					// Run through the resource list
					
					std::map<std::string,ResourceInstance>::iterator i = m_resources.begin();
					std::map<std::string,ResourceInstance>::iterator e = m_resources.end();
					
					// Delete all resources:
					
					while ( i != e )
					{
						// Delete this resource:
						
						delete (*i).second.m_resource;
						
						// Move along people, nothing to see here..
						
						++i;
					}
				}								

		protected:   
	    
			//=========================================================================================================
			// Structs
			//=========================================================================================================
			
				//=====================================================================================================
				/// @brief		This simple data structure is used by each resource manager to keep track of the 
				///				usage count for each loaded resource. 
				//=====================================================================================================
				
				struct ResourceInstance
				{
					/// Number of references there are to this resource
				
					unsigned m_reference_count;
					
					/// Actual resource itself
					
					Resource<ResourceType> * m_resource;
				};
				
			//=========================================================================================================
			// Functions
			//=========================================================================================================                    		
				
				//=====================================================================================================
				///	@brief		Loads a given resource. Names are case insensitive. If the resource is already loaded 
				///				then the reference count to the resource is simply incremented and the resource is 
				///				not reloaded.
				///	@param		name - file that contains the resource
				/// @returns	The loaded resource
				/// @remarks	This is called by ManagedResource instances to add to the reference count for a loaded
				///				resource, or to load a resource if it does not yet exist.
				//=====================================================================================================
				
				Resource<ResourceType> * Load( const std::string & name )
				{
					// Make the name lower case:
					
					std::string name_lower = name; String::ToLower(name_lower);
					
					// Get an iterator to the resource in the map:
					
					std::map<std::string,ResourceInstance>::iterator iter = m_resources.find(name_lower);
				
					// See if it's in the map already:
					
					if ( iter != m_resources.end() )
					{
						// In the map: increment the resource reference count
						
						(*iter).second.m_reference_count++;
					
						// Return the resource:
						
						return (*iter).second.m_resource;
					}
					else
					{
						// Not in the map: create the resource
						
						Resource<ResourceType> * res = new Resource<ResourceType>(name_lower,m_user_data,this);					
						
						// Make an actual resource instance:
						
						ResourceInstance res_instance;
						
						// Fill it in:
						
						res_instance.m_resource = res; 
						res_instance.m_reference_count = 1;
						
						// Add into the map:
						
						m_resources[name_lower] = res_instance;
						
						// Return the resource
						
						return res;
					}
				}
				
				//=====================================================================================================
				///	@brief		Frees a given resource. Names are case insensitive. If the resource is referenced by 
				///				another source then the reference count is simply decremented, and the resource data 
				///				remains untouched.
				///	@param		name - name of the file used by the resource
				/// @remarks	This is called by ManagedResource instances to decrement to the reference count for a 
				///				loaded resource, or to free a resource if no references to it are left.			
				//=====================================================================================================			
				
				void Free( const std::string & name )
				{
					// Make the name lower case:
					
					std::string name_lower = name; String::ToLower(name_lower);
					
					// Get an iterator to the resource in the map:
					
					std::map<std::string,ResourceInstance>::iterator iter = m_resources.find(name_lower);
					
					// See if in the map:			
				
					if ( iter != m_resources.end() )
					{
						// Good stuff: decrement the reference count
						
						(*iter).second.m_reference_count--;
						
						// If there are no more references to the resource then remove it
						
						if ( (*iter).second.m_reference_count == 0 )
						{
							// Delete the resource:
						
							delete (*iter).second.m_resource;
							
							// Remove it from the map:
							
							m_resources.erase(iter);
						}
					}
				}				
	    
			//=========================================================================================================
			// Variables
			//=========================================================================================================

				/// Map from resource names to actual resource instances themselves. All names are stored in lower-case

				std::map<std::string,ResourceInstance> m_resources;
				
				/// Specific data for each type of manager. For a texture manager this might be a handle to a graphics device etc.
				
				long m_user_data;
	};	
}

#endif
