#pragma once

#include "Adept.hpp"
#include <Stuff\Database.hpp>

namespace Adept {class ResourceID;}

#if !defined(Spew)
	void Spew(
		const char* group,
		const Adept::ResourceID& id
	);
#endif

namespace GetHashFunctions {
	unsigned GetHashValue(const Adept::ResourceID &value);
}

namespace Stuff {
	bool Close_Enough(
		const Adept::ResourceID &id1,
		const Adept::ResourceID &id2, 
		Scalar e = SMALL
	);
}

namespace Adept {

	class ResourceFile;

	//##########################################################################
	//##########################    ResourceID    ##############################
	//##########################################################################

	//
	// The resource ID holds the file ID and record ID together.  It is used 
	// for storing a unique identifier for the resource, and for indexing into
	// the data structures holding the data.
	//

	class ResourceID
	{
	public:
		ResourceID():
			m_fileID(-1),
			m_recordID(0)
				{}
		explicit ResourceID(ResourceFile *file);
		ResourceID(const ResourceID& resource_id):
			m_fileID(resource_id.m_fileID),
			m_recordID(resource_id.m_recordID)
				{}
		ResourceID(
			short file_id,
			WORD record_id
		):
			m_fileID(file_id),
			m_recordID(record_id)
				{}

		bool TestInstance() const
			{return true;}

	//Accessor Functions
	public:
		short GetFileID() const
			{return m_fileID;}
		WORD GetRecordID() const
			{return m_recordID;}

	public:
		ResourceID& operator=(const ResourceID& resource_id)
			{
				Check_Pointer(this); Check_Object(&resource_id);
				m_fileID = resource_id.m_fileID; m_recordID = resource_id.m_recordID;
				return *this;
			}

		bool operator==(const ResourceID& resource_id) const
			{
				return
					m_fileID == resource_id.m_fileID &&
						m_recordID == resource_id.m_recordID;
			}
		bool operator!=(const ResourceID& resource_id) const
			{
				return
					m_fileID != resource_id.m_fileID ||
						m_recordID != resource_id.m_recordID;
			}
		bool operator>(const ResourceID& resource_id) const
			{
				return
					(m_fileID == resource_id.m_fileID) ? 
						(m_recordID > resource_id.m_recordID) :
						(m_fileID > resource_id.m_fileID);
			}
		bool operator!() const
			{return operator==(Null);}

		friend bool Stuff::Close_Enough(
			const Adept::ResourceID &id1,
			const Adept::ResourceID &id2, 
			Scalar e
		);

		#if !defined(Spew)
			friend void ::Spew(
				const char* group,
				const ResourceID& resource_id
			)
				{
					Check_Object(&resource_id);
					SPEW((
						group,
						"%d:%d+",
						resource_id.m_fileID,
						resource_id.m_recordID
					));
				}
		#endif

		friend unsigned GetHashFunctions::GetHashValue(const ResourceID &value)
			{
				//
				// Verify that the unsigned is 32 bits wide
				// Hash value is first 16 bits of m_fileID and first 16 bits of m_recordID
				//
				return ((value.m_fileID) << 16 | value.m_recordID);
			}

		static ResourceID Null;

		enum {
			BuildDependenciesRecordID,
			FirstFreeRecordID
		};

		short m_fileID;
		WORD m_recordID;
	};

	//##########################################################################
	//###########################    Resource    ###############################
	//##########################################################################

	class Resource:
		public Stuff::MemoryStream
	{
		friend class ResourceManager;
		friend class ResourceFile;

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Constructors, Destructors, & Testing
	//
	public:
		//
		// This constructor is used to provide a return mechanism for building
		// resources in another function
		//
		Resource();

		//
		// These constructors will find the resource and load it
		//
		explicit Resource(const ResourceID &resource_id, bool load=true);
		explicit Resource(const char* name, bool load=false);
		explicit Resource(const char* name, ResourceFile *resource_file, bool load=false);

		~Resource();

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Accessors
	//
	public:
		void FindID(const ResourceID &resource_id, bool load=true);
		void FindName(const char* name, bool load=false);

		void LoadData();
		void UnloadData();
		void AbandonData();
		int GetDiskOffset();
		DWORD GetCompressedSize() { return m_handle.GetRealRecordSize();}

		void First(ResourceFile *file);
		bool ReadAndNext();

		const ResourceID& GetResourceID()
			{Check_Object(this); return m_ID;}
		const char* GetName()
			{Check_Object(this); return m_handle.m_name;}

	protected:
		Stuff::RecordHandle m_handle;
		ResourceID m_ID;

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Building functions
	//
	public:
		bool DoesResourceExist()
			{
				Check_Object(this);
				return m_handle.m_record != NULL && streamSize>0;
			}
		bool IsResourceUpToDate();
		bool IsRegistered();

		void Save(
			Stuff::MemoryStream *stream,
			const Stuff::FileDependencies *dependencies
		);
		void ChangeName(const char* new_name);

		void AddFileDependenciesTo(Stuff::FileDependencies *dependencies);
		void GetFileDependencies(Stuff::FileDependencies *dependencies);
		static Stuff::FileDependencies *ParentFileDependencies;
	};

	//##########################################################################
	//#######################    ResourceFile    ###############################
	//##########################################################################

	class ResourceFile:
		public Stuff::Plug
	{
		friend class ResourceManager;
		friend class Resource;
		friend class ResourceID;

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Constructors, Destructors, & Testing
	//
	public:
		ResourceFile(
			Stuff::DatabaseHandle *handle,
			Stuff::DatabaseHandle *dependency_handle,
			short file_id,
			bool does_auto_close
		):
			Plug(DefaultData),
			m_fileHandle(handle),
			m_dependencyHandle(dependency_handle),
			m_fileID(file_id),
			m_lastRegisteredRecordID(-1),
			m_doesCloseOnCloseAll(does_auto_close)
				{Check_Pointer(this);}
		~ResourceFile();

		void Save();
		void SaveAs(const char* filename);
		void ScatterSave();
		void UnloadRecordData();

		void SetLastRegisteredResourceID(const ResourceID &res_id);

		__int64 CalculateCRC(int core_key);

		__int64 CalculateDateCRC()
			{Check_Object(this); Check_Object(m_fileHandle); return m_fileHandle->CalculateDateCRC();};
	
		short GetFileID()
			{Check_Object(this); return m_fileID;}
		HGOSFILE GetFileHandle()
			{Check_Object(this); return m_fileHandle->GetFileHandle();}

		Stuff::MString
			GetFileName()
				{Check_Object(this); return m_fileHandle->GetFileName();}
		bool
			DoesCloseOnCloseAll()
				{Check_Object(this); return m_doesCloseOnCloseAll;}

	protected:
		Stuff::DatabaseHandle
			*m_fileHandle,
			*m_dependencyHandle;
		WORD m_lastRegisteredRecordID;
		short m_fileID;
		bool m_doesCloseOnCloseAll;
	};

	inline ResourceID::ResourceID(ResourceFile *file):
		m_fileID(file->m_fileID),
		m_recordID(0)
	{
	}

	//##########################################################################
	//#######################    ResourceManager    ############################
	//##########################################################################

	//
	// The resource manager maintains the list of open resource files, a cache
	// of recently opened resources, and a file that overrides the static 
	// resources.
	//

	class ResourceManager:
		public Stuff::Plug
	{
		friend class Resource;

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Constructors, Destructors, & Testing
	//
	public:
		explicit ResourceManager();
		~ResourceManager();

		static void TestClass();

		static ResourceManager *Instance;

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Public Interface
	//
	//		OpenResourceFile - Opens a resource file of name file_name and gives
	//			it file ID file_ID.  The file may be opened in read only mode.  If
	//			the file is opened in read/write mode, the current file may be 
	//			overwritten.
	//
	//		Close - Closes a resource file
	//		
	public:
		ResourceFile* OpenResourceFile(
			const char *file_name,
			const char *dep_file,
			short file_ID,
			DWORD content_version,
			bool core,
			bool overwrite,
			bool close_on_close_all = true		//This parameter is for the CloseAllButCore Function
		);
		ResourceFile* OpenResourceFile(
			Stuff::DatabaseHandle *database,
			short file_ID,
			bool core,
			bool close_on_close_all = true		//This parameter is for the CloseAllButCore Function
		);

		ResourceFile* OpenResourceFile(
			Stuff::DatabaseHandle *database,
			const char *dep_file,
			short file_ID,
			DWORD content_version,
			bool core,
			bool overwrite,
			bool close_on_close_all = true		//This parameter is for the CloseAllButCore Function
		);


		ResourceFile* OpenResourceFile(
			Stuff::MemoryStream *stream,
			const char *file_name,
			const char *dep_file,
			short file_ID,
			DWORD content_version,
			bool core,
			bool close_on_close_all = true		//This parameter is for the CloseAllButCore Function
		);

		void SaveAll();

		void CloseAllButCore();
		void SpewLoadedResources();

		ResourceFile* GetResourceFile(short file_id)
			{Check_Object(this); return m_openedFiles.Find(file_id);}
		short AllocateResourceFileID()
			{Check_Object(this); return m_nextResourceFileID++;}


		short FindOpenResourceID();

	protected:
		short m_nextResourceFileID;

		Stuff::TableOf<ResourceFile*, short> m_openedFiles;
		Stuff::ChainOf<ResourceFile*> m_nonCoreFiles;
	};
}

namespace MemoryStreamIO {

	inline Stuff::MemoryStream& Read(
		Stuff::MemoryStream *stream,
		Adept::ResourceID *output
	)
	{
		Check_Object(stream);
		return stream->ReadBytes(output, sizeof(*output));
	}

	inline Stuff::MemoryStream& Write(
		Stuff::MemoryStream *stream,
		const Adept::ResourceID *input
	)
	{
		Check_Object(stream);
		return stream->WriteBytes(input, sizeof(*input));
	}

}

