///////////////////////////////////////////////////////////////////////////////
/// @file deWorld.cpp
///
/// @brief Destiny "World" storage
///
/// @author Assassin
///
/// This file is the intellectual property of Novus Delta, LLC.. Usage of the
/// contents of this file is subject to the Destiny3D Member License which
/// can be found at http://www.destiny3d.com.  Any other usage is prohibited.
///
/// This file is distributed "AS IS" without warranty of any kind.  Novus
/// Delta, LLC. does not guarantee the fitness of the contents of this file
/// for any particular purpose.
///
/// Copyright (C) 2001-2003 Novus Delta, LLC. All Rights Reserved.
///
/// <hr>
///                                 Change History
/// <hr>
///
/// @date Dec 2001
/// @author Assassin
/// @remarks Creation
///
/// @date Feb 2002
/// @author Assassin
/// @remarks Rendering functionality moved to deRender
///
/// @date Sep 2002
/// @author Assassin
/// @remarks Tear-down and re-write
///
///////////////////////////////////////////////////////////////////////////////

#include "deWorld_priv.hpp"
#include "deDriver.hpp"
#include "deMath.hpp"
#include "deCamera.hpp"
#include "deFileSystem.hpp"
#include "deFile.hpp"
#include <cstdio>

long deWorldManager::s_WorldRscInterfaceID = 0;
static deWorldManager* s_TheManager = NULL;
IdeWorldManager* IdeWorld_GetManager()
{
	if (!s_TheManager)
	{
		s_TheManager = new deWorldManager;
	}
	return s_TheManager;
}

void IdeWorld_ShutDown()
{
	delete s_TheManager;
	s_TheManager = NULL;
}

static IdeFile * LoadFile(const char * filename, IdeFileSystem* BaseFS, deBoolean Read, deBoolean Write)
{
	IdeFile *		NewFile;
	long			OpenFlags;

	NewFile = NULL;
	if (BaseFS == NULL)
	{
		IdeFSReal *		FSReal;

		//create the base
		FSReal = IdeFileSystem_CreateFSReal();
		if (!FSReal)
			return NULL;
		BaseFS = FSReal;

	}
	else
	{
//#pragma todo (handle loading of files from inside another filesystem)
		BaseFS->Claim();
	}
	
	//figure out the flags to use
	OpenFlags = 0;
	if (Read && Write)
		OpenFlags = deFile_Open_Always | deFile_Open_Read | deFile_Open_Write;
	else if (Read)
		OpenFlags = deFile_Open_Existing | deFile_Open_Read;
	else if (Write)
		OpenFlags = deFile_Open_CreateAlways | deFile_Open_Write;

	//open up the file
	NewFile = BaseFS->Open(filename, OpenFlags);
	BaseFS->Release();
	if (!NewFile)
	{
		return NULL;
	}
	
	return NewFile;
}

IdeResourceBase * LoadWorldFile(const char * filename, IdeFile * worldfile, deBoolean /*isThreaded*/)
{
	if (!worldfile)
		return NULL;
	deWorld * pWorld = new deWorld(filename);
	if (!pWorld)
		return NULL;
	((deWorldManager*)s_TheManager)->AddWorld(pWorld);
	deBoolean RetVal = pWorld->LoadFromFile(worldfile);
	if (!RetVal)
	{
		pWorld->Release();
		return NULL;
	}
	return pWorld;
}

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

deWorldManager::deWorldManager()
: m_LatestWOID(0), m_TypeHash(23)
{
	m_WorldIDServer = IdeIDServer_CreateServer(deTRUE, 2);
	if (!m_WorldIDServer)
		throw;
	if (!Initialize())
		throw;
	if (!s_TheManager)
		s_TheManager = this;
}

deWorldManager::~deWorldManager()
{
	ShutDown();
	if (s_TheManager == this)
		s_TheManager = NULL;
	if (m_WorldIDServer)
		m_WorldIDServer->Release();
}

deBoolean deWorldManager::Initialize()
{
	IdeResourceManager * Manager = IdeResourceManager_GetManager();
	s_WorldRscInterfaceID = Manager->GetUniqueInterfaceID();
	Manager->RegisterResourceType("dworld", LoadWorldFile);
	
	return deTRUE;
}

deBoolean deWorldManager::ShutDown()
{
	IdeWorld *	pWorld;
	deID_t		ID = 0;

	//
	pWorld = (IdeWorld*)m_WorldIDServer->WalkData(ID);
	while (pWorld)
	{
		// force the world to release all the way to deletion
		while (pWorld->Release());
		// it should remove itself from the list through RemoveWorld
		// get the next one
		pWorld = (IdeWorld*)m_WorldIDServer->WalkData(ID);
	}

	return deTRUE;
}

deWorldID deWorldManager::CreateWorld()
{
	deWorld * pWorld;

	pWorld = new deWorld;
	if (!pWorld)
		return NULL;

	deWorldID WID = AddWorld(pWorld);
	if (WID == 0)
	{
		delete pWorld;
		return 0;
	}

	return WID;
}

deWorldID deWorldManager::AddWorld(deWorld* pWorld)
{
#pragma todo(possible problem - perhaps put in something to disallow high ID values)
	u32 tempval = m_WorldIDServer->AddData((void*)((IdeWorld*)pWorld));
	DE_ASSERT(tempval < (1<<16));
	deWorldID ID = (deWorldID)(tempval);
	if (ID == 0)
		return 0;

	pWorld->SetWorldID(ID);
	
	return ID;
}

deBoolean deWorldManager::RemoveWorld(deWorldID WorldID)
{
	void * pWorld = m_WorldIDServer->LookupData(WorldID);
	if (!pWorld)
		return deFALSE;
	return m_WorldIDServer->RemoveData(WorldID, pWorld);
}

IdeWorld* deWorldManager::GetWorld(deWorldID ID)
{
	IdeWorld * world;
	world = (IdeWorld*)(m_WorldIDServer->LookupData(ID));
	return world;
}

IdeWorldObject* deWorldManager::GetWorldObject(deWorldID WID, deObjectID OID)
{
	IdeWorld*		pWorld;
	pWorld = GetWorld(WID);
	if (!pWorld)
		return NULL;
	return pWorld->GetWorldObject(OID);
}

long deWorldManager::GetRscInterfaceID()
{
	return s_WorldRscInterfaceID;
}

long deWorldManager::GetUniqueWOInterfaceID()
{
	m_LatestWOID++;
	return m_LatestWOID;
}

deBoolean deWorldManager::RegisterWOClass(const char* ClassName, IdeWorldObject_Creator creator)
{
	if (ClassName == NULL || creator == NULL)
		DEBUGRETURN(deFALSE, "RegisterWOClass: bad parameters");
	
	if (NULL == m_TypeHash.AddElement(creator, ClassName))
		return deFALSE;

	return deTRUE;
}

IdeWorldObject*	deWorldManager::CreateWorldObject(const char* ClassName)
{
	if (ClassName == NULL)
		return NULL;

	IdeWorldObject_Creator * pCreator, Creator;
	IdeWorldObject* pObject;
	pCreator = m_TypeHash.FindElement(ClassName);
	if (!pCreator)
		return NULL;
	Creator = *pCreator;
	if (!Creator)
		return NULL;
	
	pObject = Creator();

	return pObject;
}


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

deWorld::deWorld()
: m_LatestItemID(0), m_WorldID(0), m_LockDown(deFALSE), m_ObjectListHash(23)
{
	m_ObjectIDServer = IdeIDServer_CreateServer(deTRUE, 8);
}

deWorld::deWorld(const char* filename)
: deResourceBase(filename), m_LatestItemID(0), m_WorldID(0), 
  m_LockDown(deFALSE), m_ObjectListHash(23)
{
	m_ObjectIDServer = IdeIDServer_CreateServer(deTRUE, 8);
}

deWorld::~deWorld()
{
	((deWorldManager*)s_TheManager)->RemoveWorld(m_WorldID);

	IdeWorldObject*	pObject;
	deID_t			ID;
	
	ID = 0;
	
	pObject = (IdeWorldObject*)m_ObjectIDServer->WalkData(ID);
	while (pObject)
	{
		// by setting the world ID to 0, the object should remove itself from the world
		pObject->SetWorldID(0);
		
		pObject = (IdeWorldObject*)m_ObjectIDServer->WalkData(ID);
	}

	m_ObjectIDServer->Release();
}

//#pragma todo (test this) // done
deBoolean deWorld::LoadFromFile(IdeFile * file)
{
	if (file == NULL)
		return deFALSE;

	deBoolean RetVal = deFALSE;
	RetVal = this->DeSerialize(file);
	return RetVal;
}

IdeResourceBase * deWorld::MakeCopy()
{
	return NULL;
}

void * deWorld::GetRscInterface(long interface_id)
{
	if (interface_id == deWorldManager::s_WorldRscInterfaceID)
	{
		return (IdeWorld*)(this);
	}
	if (interface_id == 0)
	{
		return (IdeResourceBase*)(this);
	}
	return NULL;
}

deWorldID deWorld::GetWorldID()
{
	return m_WorldID;
}

deWorldID deWorld::GetWorldIDUsingLocalID(deWorldID LocalID, deBoolean /*ForceLoad*/)
{
#pragma todo (finish me)
	if (LocalID == 1)
		return m_WorldID;

	return 0;
}

deWorldID deWorld::GetLocalIDFromLoadedWorld(deWorldID WorldID)
{
#pragma todo (finish me)
	if (WorldID == m_WorldID)
		return 1;

	return 0;
}

IdeWorldObject* deWorld::GetWorldObject(deObjectID ObjectID)
{
	IdeWorldObject * Object = (IdeWorldObject*)(m_ObjectIDServer->LookupData(ObjectID));
	return Object;
}

deObjectID deWorld::AddWorldObject(IdeWorldObject * Object)
{
	if (m_LockDown)
		DEBUGRETURN(deFALSE, "AddWorldObject - world is on lockdown");
	if ( Object == NULL)
		DEBUGRETURN(deFALSE, "AddWorldObject - invalid parameter");

	deObjectID ID;

	if (Object->GetWorldID() != 0)
		DEBUGRETURN(0, "AddWorldObject - already in a world");
	ID = (deObjectID)(m_ObjectIDServer->AddData(Object));

	if (!RecordObjectWithTypeName(ID, Object->GetWOTypeName()))
	{
		RemoveWorldObject(ID);
		return 0;
	}
	
	Object->Claim();
	
	return ID;
}

deBoolean deWorld::AddWorldObjectWithID(IdeWorldObject * Object)
{
	// allow this operation in a lock-down, and hope it isn't abused
	if (Object == NULL)
		DEBUGRETURN(deFALSE, "AddWorldObjectWithID - invalid parameter");

	deObjectID ID = Object->GetObjectID();
	
	if (!m_ObjectIDServer->AddDataWithID(Object, (deID_t)(ID)))
		return deFALSE;

	if (!RecordObjectWithTypeName(ID, Object->GetWOTypeName()))
		return deFALSE;

//	Object->Claim();

	return deTRUE;
}

deBoolean deWorld::RemoveWorldObject(deObjectID ObjectID)
{
	if (m_LockDown)
		return deFALSE;
	IdeWorldObject* object;
	object = (IdeWorldObject*)m_ObjectIDServer->LookupData(ObjectID);
	if (!object)
		return deFALSE;
	object->Release();
	return m_ObjectIDServer->RemoveData(ObjectID, object);
}

deBoolean deWorld::SetWorldID(deWorldID ID)
{
	if (m_LockDown)
		return deFALSE;
	if (m_WorldID)
		return deFALSE;
	m_WorldID = ID;
	return deTRUE;
}

const char* deWorld::GetNextObjectTypeName(void* &PrevEntry)
{
	deString* pStr;
	PrevEntry = m_ObjectTypesUsed.GetNext(PrevEntry, pStr);
	return pStr->const_str();
}

deObjectID deWorld::GetNextObjectOfType(const char* TypeName, void* &PrevEntry)
{
	deTList<deObjectID> * pList;
	pList = m_ObjectListHash[TypeName];
	if (!pList)
		return 0;

	deObjectID ObjectID;
	PrevEntry = pList->GetNext(PrevEntry, ObjectID);
	return ObjectID;
}

deBoolean deWorld::RecordObjectWithTypeName(deObjectID ObjectID, const char* TypeName)
{
	if (TypeName == NULL || ObjectID == 0)
		return deFALSE;

	deTList<deObjectID> * pList;
	pList = m_ObjectListHash[TypeName];
	if (pList == NULL)
	{
		deTList<deObjectID> NewList;
		pList = m_ObjectListHash.AddElement(NewList, TypeName);
		if (pList == NULL)
			return deFALSE;
	}
	if (NULL == pList->AddElement(ObjectID))
		return deFALSE;
	return deTRUE;
}

/*
World File format:

header:
{
char	MagicValues[8]
short	VersionMajor
short	VersionMinor
}

chunk:
{
uchar	string_length
char	typename[string_length]
ulong	obj_id
long	data_length
byte	data[data_length]
}

file format:
header
chunk
chunk
chunk
...
*/

static const struct deWorld_Header_t
{
	char	MagicValues[8];
	short	VersionMajor;
	short	VersionMinor;
} deWorld_Header = {{0x52,0x65,0x64,0x42,0x65,0x61,0x72,0x64}, 0,1};

deBoolean deWorld::Serialize(IdeFile* File)
{
	m_LockDown = deTRUE;

	if (!WriteHeader(File))
		return deFALSE;
	
	// DO STUFF HERE
	void * ptr;
	IdeWorldObject* object;
	char *TypeName;
	long StartPos;
	deObjectID ID;

	ID = 0;
	ptr = m_ObjectIDServer->WalkData(ID);
	while (ptr)
	{
		object = (IdeWorldObject*)ptr;

		TypeName = (char*)object->GetWOTypeName();
		if (TypeName == NULL)
			goto Serialize_fail;

		if (!WriteObjectInfo(File, ID, TypeName))
			goto Serialize_fail;

		// get position relative to start
		StartPos = File->GetPosition();
		
		if (!object->Serialize(File))
			goto Serialize_fail;

		if (!WriteObjectDataLength(File, StartPos))
			goto Serialize_fail;

		ptr = m_ObjectIDServer->WalkData(ID);
	}

	m_LockDown = deFALSE;
	
	return deTRUE;
	
Serialize_fail:
	m_LockDown = deFALSE;
	return deFALSE;
}

deBoolean deWorld::DeSerialize(IdeFile* File)
{
	// make sure this file is the right kind of file
	if (!CheckHeader(File))
		return deFALSE;

	m_LockDown = deTRUE;
	
	IdeWorldObject*	pObject;
	char			TypeName[256];
	deObjectID		ObjectID;
	long			DataLength;
	long			FilePos;
	long			FileSize;
	
	FilePos = 0;
	FileSize = File->GetSize();
	// grab each object's header
	while (ReadObjectInfo(File, ObjectID, TypeName, DataLength))
	{
		if (FilePos + DataLength > FileSize)
			return deFALSE;
		pObject = ((deWorldManager*)s_TheManager)->CreateWorldObject(TypeName);
		FilePos = File->GetPosition();
		if (pObject != NULL && ObjectID != 0)
		{
			if (pObject->SetWorldID(m_WorldID, ObjectID))
			{
				if (!pObject->DeSerialize(File, DataLength))
				{
					DEBUGMSG2("Failed to load world object ID=%d TypeName='%s'", ObjectID, TypeName);
					pObject->Release();
				}
			}
			else
			{
				pObject->Release();
			}
		}
		File->SetPosition(FilePos+DataLength, deFile_Seek_Start);
	}

	// after everything is loaded, the objects can go and resolve their pointers
	deID_t			ID;
	ID = 0;
	pObject = (IdeWorldObject*)m_ObjectIDServer->WalkData(ID);
	while (pObject)
	{
		pObject->DeSerializeLoad();
		
		pObject = (IdeWorldObject*)m_ObjectIDServer->WalkData(ID);
	}

#pragma todo(implement releasing of implicit claims with deferment to top-level pointers)

	m_LockDown = deFALSE;
	
	return deTRUE;
}

deBoolean deWorld::WriteHeader(IdeFile* File)
{
	if (File == NULL)
		return deFALSE;
	
	char	Buffer[256];
	int		Length;

	// 8-byte magic number, 2-byte major version, 2-byte minor version
	Length = 0;
	memcpy(Buffer+Length, deWorld_Header.MagicValues, 8);
	Length += 8;
	memcpy(Buffer+Length, &(deWorld_Header.VersionMajor), 2);
	Length += 2;
	memcpy(Buffer+Length, &(deWorld_Header.VersionMinor), 2);
	Length += 2;

	if (Length != File->Write(Buffer, Length))
		return deFALSE;

	return deTRUE;
}

deBoolean deWorld::CheckHeader(IdeFile* File)
{
	if (File == NULL)
		return deFALSE;
	
	char Magics[9], Refs[9];
	short Major, Minor;

	// 8-byte magic number, 2-byte major version, 2-byte minor version
	if (8 != File->Read(Magics, 8))
		return deFALSE;
	if (2 != File->Read(&Major, 2))
		return deFALSE;
	if (2 != File->Read(&Minor, 2))
		return deFALSE;

	// test the file's magic numbers to make sure they match
	memcpy(Refs, &(deWorld_Header.MagicValues), 8);
	Magics[8] = '\0';
	Refs[8] = '\0';
	if (strcmp(Magics, Refs) != 0)
		return deFALSE;

	// fail if this file is newer than this code can handle
	if (Major > deWorld_Header.VersionMajor)
		return deFALSE;
	if (Major == deWorld_Header.VersionMajor && Minor > deWorld_Header.VersionMinor)
		return deFALSE;

	// success
	return deTRUE;
}


deBoolean deWorld::WriteObjectInfo(IdeFile* File, deObjectID ObjectID, char * TypeName)
{
	if (ObjectID == 0 || TypeName == NULL || File == NULL)
		return deFALSE;

	int				Length;
	unsigned char	StrLength;
	
	// write typename length, including null terminator
	Length = strlen(TypeName) + 1;
	if (Length > 255)
		return deFALSE;
	StrLength = (unsigned char)Length;
	if (1 != File->Write(&StrLength, 1))
		return deFALSE;
	
	// write typename
	if (Length != File->Write(TypeName, StrLength))
		return deFALSE;

	// write object ID
	if (4 != File->Write(&ObjectID, 4))
		return deFALSE;
	
	// write blank 4 bytes to be used for data length
	Length = 0;
	if (4 != File->Write(&Length, 4))
		return deFALSE;
	
	return deTRUE;
}

deBoolean deWorld::WriteObjectDataLength(IdeFile* File, long ObjectStartPos)
{
	if (File == NULL)
		return deFALSE;

	long		ObjectEndPos, DataLength;
	deBoolean	RetVal = deTRUE;
	
	// grab end position
	ObjectEndPos = File->GetPosition();

	DataLength = ObjectEndPos - ObjectStartPos;
	if (DataLength < 0)
		return deFALSE;
	
	// go back to the start and write the datalength
	File->SetPosition(ObjectStartPos-4, deFile_Seek_Start);
	if (4 != File->Write(&DataLength, 4))
		RetVal = deFALSE;

	// reset position back to end again, for writing next object
	File->SetPosition(ObjectEndPos, deFile_Seek_Start);
	
	return RetVal;
}

deBoolean deWorld::ReadObjectInfo(IdeFile* File, deObjectID &ObjectID, char TypeNameBuffer[256], long &DataLength)
{
	/*
	-----------------------
	| uchar string_length
	| char typename[string_length]
	| u32 obj_id
	| s32 data_length
	-----------------------
	byte data[data_length]
	*/
	
	if (File == NULL || TypeNameBuffer == NULL)
		return deFALSE;

	unsigned char StrLength;

	// read typename length
	if (1 != File->Read(&StrLength, 1))
		return deFALSE;
	DataLength = StrLength;
	// read typename
	if (DataLength != File->Read(TypeNameBuffer, DataLength))
		return deFALSE;
	TypeNameBuffer[255] = '\0';

	// read object ID
	if (4 != File->Read(&ObjectID, 4))
		return deFALSE;

	// read datalength
	if (4 != File->Read(&DataLength, 4))
		return deFALSE;

	return deTRUE;
}

