/*==LICENSE==*

CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011  Cyan Worlds, Inc.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

You can contact Cyan Worlds, Inc. by email legal@cyan.com
 or by snail mail at:
      Cyan Worlds, Inc.
      14617 N Newport Hwy
      Mead, WA   99021

*==LICENSE==*/
#include "plKeyImp.h"
#include "hsStream.h"
#include "hsKeyedObject.h"
#include "hsResMgr.h"
#include "hsTypes.h"
#include "../pnMessage/plRefMsg.h"
#include "../pnMessage/plSelfDestructMsg.h"
#include "hsTimer.h"
#include "plProfile.h"
#include "plgDispatch.h"

plProfile_CreateMemCounter("Keys", "Memory", KeyMem);

static UInt32 CalcKeySize(plKeyImp* key)
{
	UInt32 nameLen = 0;
	if (key->GetUoid().GetObjectName())
		nameLen = strlen(key->GetUoid().GetObjectName()) + 1;
	return sizeof(plKeyImp) + nameLen;
}

//#define LOG_ACTIVE_REFS
#ifdef LOG_ACTIVE_REFS
#include "plCreatableIndex.h"
static const char* kObjName = "GUI_District_OptionsMenuGUI";
static UInt16 kClassType = CLASS_INDEX_SCOPED(plSceneNode);
static UInt32 kCloneID = 0;
hsBool IsTrackedKey(const plKeyImp* key)
{
	return hsStrEQ(key->GetName(), kObjName) && key->GetUoid().GetClassType() == kClassType && key->GetUoid().GetCloneID() == kCloneID;
}
#endif

plKeyImp::plKeyImp() :
	fObjectPtr(nil), 
	fStartPos(-1),
	fDataLen(-1),
	fNumActiveRefs(0),
	fPendingRefs(1),
	fCloneOwner(nil)
{
#ifdef HS_DEBUGGING
	fIDName = nil;
	fClassType = nil;
#endif
}

plKeyImp::plKeyImp(plUoid u, UInt32 pos,UInt32 len):
	fUoid(u),
	fObjectPtr(nil), 
	fStartPos(pos),
	fDataLen(len),
	fNumActiveRefs(0),
	fPendingRefs(1),
	fCloneOwner(nil)
{
	plProfile_NewMem(KeyMem, CalcKeySize(this));

#ifdef HS_DEBUGGING
	fIDName = fUoid.GetObjectName();
	fClassType = plFactory::GetNameOfClass( fUoid.GetClassType() );
#endif
}

plKeyImp::~plKeyImp() 
{
	plProfile_DelMem(KeyMem, CalcKeySize(this));

#if defined(HS_DEBUGGING) && 0
	// Colin debugging
	char buf[512];
	sprintf(buf, "0x%x %s %s\n", this, fIDName, fClassType);
	hsStatusMessage(buf);
#endif

	hsAssert(fObjectPtr == nil, "Deleting non-nil key!  Bad idea!");

	if (fCloneOwner != nil)
	{
		// Must be a clone, remove us from our parent list
		((plKeyImp*)fCloneOwner)->RemoveClone(this);
	}

	for (int i = 0; i < fClones.GetCount(); i++)
	{
		if (fClones[i])
			fClones[i]->UnRegister();
	}
	fClones.Reset();

	// This is normally empty by now, but if we never got loaded,
	// there will be unsent ref messages in the NotifyCreated list
	ClearNotifyCreated();
}

void plKeyImp::SetUoid(const plUoid& uoid)
{
	fUoid = uoid; 
#ifdef HS_DEBUGGING
	fIDName = fUoid.GetObjectName();
	fClassType = plFactory::GetNameOfClass(fUoid.GetClassType());
#endif
}

const char* plKeyImp::GetName() const	
{ 
	return fUoid.GetObjectName(); 
}

hsKeyedObject* plKeyImp::GetObjectPtr()
{	
	return ObjectIsLoaded();
}

hsKeyedObject* plKeyImp::ObjectIsLoaded() const
{
	return this ? fObjectPtr : nil;
}

// Copy the contents of p for cloning process
void plKeyImp::CopyForClone(const plKeyImp *p, UInt32 playerID, UInt32 cloneID)
{
	fObjectPtr = nil;				// the clone object start as nil
	fUoid = p->GetUoid();			// we will set the UOID the same to start

#ifdef HS_DEBUGGING
	fIDName = fUoid.GetObjectName();
	fClassType = plFactory::GetNameOfClass( fUoid.GetClassType() );
#endif

	fStartPos = p->GetStartPos();	
	fDataLen = p->GetDataLen();		
	fUoid.SetClone(playerID, cloneID);
}

hsKeyedObject* plKeyImp::VerifyLoaded()
{
	if (!fObjectPtr)
		hsgResMgr::ResMgr()->ReadObject(this);

	return fObjectPtr;
}

//// Read/Write //////////////////////////////////////////////////////////////
//	The actual key read/writes for the index file, the only time the whole
//	key is ever actually stored.

void plKeyImp::Read(hsStream* s)
{
	fUoid.Read(s);
	s->ReadSwap(&fStartPos);
	s->ReadSwap(&fDataLen);

	plProfile_NewMem(KeyMem, CalcKeySize(this));

#ifdef HS_DEBUGGING
	fIDName = fUoid.GetObjectName();
	fClassType = plFactory::GetNameOfClass(fUoid.GetClassType());
#endif
}

void plKeyImp::SkipRead(hsStream* s)
{
	plUoid tempUoid;
	tempUoid.Read(s);
	s->ReadSwap32();
	s->ReadSwap32();
}

void plKeyImp::Write(hsStream* s)
{
	fUoid.Write(s);
	s->WriteSwap(fStartPos);
	s->WriteSwap(fDataLen);
	if (fStartPos == (UInt32)-1)
		int foo = 0;
}

//// WriteObject /////////////////////////////////////////////////////////////
//	Writes the key's object to the already opened stream

void plKeyImp::WriteObject(hsStream* stream)
{
	hsKeyedObject* ko = ObjectIsLoaded();
	if (ko == nil)
	{
		// Mark the key as not written
		fStartPos = (UInt32)-1;
		fDataLen = (UInt32)-1;
		return;
	}

	fStartPos = stream->GetPosition();
	hsgResMgr::ResMgr()->WriteCreatable(stream, ko);
	fDataLen = stream->GetPosition() - fStartPos;
}

void plKeyImp::UnRegister()		// called from plRegistry
{
	plKey safeRefUntilWereDone = plKey::Make(this);

	hsKeyedObject* ko = ObjectIsLoaded();
	if (ko)
	{
		INotifyDestroyed();
		fObjectPtr = nil;
		fNumActiveRefs = 0;

		hsRefCnt_SafeUnRef(ko);
	}
	IClearRefs();
	ClearNotifyCreated();
};

hsKeyedObject* plKeyImp::RefObject(plRefFlags::Type flags)
{
	if ((flags == plRefFlags::kPassiveRef) && !ObjectIsLoaded())
		return nil;

#ifdef LOG_ACTIVE_REFS
	if (IsTrackedKey(this))
		hsStatusMessageF("@@@ RefObject adding active ref to %s (%d total)", kObjName, fNumActiveRefs+1);
#endif // LOG_ACTIVE_REFS

	IncActiveRefs();

	return VerifyLoaded();	// load object on demand
}

void plKeyImp::UnRefObject(plRefFlags::Type flags)		
{
	// Rather than using hsRefCnt's, make Ref and 
	// UnRef work with ActiveRef system
	if ( (flags == plRefFlags::kPassiveRef) && !ObjectIsLoaded())
		return;

#ifdef LOG_ACTIVE_REFS
	if (IsTrackedKey(this))
		hsStatusMessageF("@@@ UnRefObject releasing active ref to %s (%d total)", kObjName, fNumActiveRefs-1);
#endif // LOG_ACTIVE_REFS
	DecActiveRefs();

	if( !GetActiveRefs() )
	{
		INotifyDestroyed();

		IClearRefs();
		ClearNotifyCreated();
		
		plKey key=plKey::Make( this );	// for linux build
		plSelfDestructMsg* nuke = TRACKED_NEW plSelfDestructMsg( key );
		plgDispatch::Dispatch()->MsgSend(nuke);
	}
}

hsKeyedObject* plKeyImp::SetObjectPtr(hsKeyedObject* p)	
{ 
	hsKeyedObject* retVal = nil;

	// If our object is the only one with a ref to us, this function will crash, so we 
	// make sure we have an extra ref, just like in UnRegister().
	plKey safeRefUntilWereDone = plKey::Make(this);

	if (p)
	{
#ifdef HS_DEBUGGING
		if (fClassType)
		{
			char str[2048];
			sprintf(str, "Mismatch of class (we are a %s, given a %s)", fClassType, p->ClassName());
			hsAssert(fClassType == p->ClassName() || strcmp(fClassType, p->ClassName()) == 0, str); // points to static
		}
		else
			fClassType = p->ClassName();
#endif

		hsAssert(!fObjectPtr, "Setting an ObjectPtr thats already Set!");
		
		retVal = fObjectPtr = p;
	}
	else
	{
		if (fObjectPtr)
			UnRegister();

		fObjectPtr = nil;
		retVal = nil;
	}

	return retVal; 
}

void plKeyImp::ClearNotifyCreated() 
{ 
	for (int i = 0; i < fNotifyCreated.GetCount(); i++)
		hsRefCnt_SafeUnRef(fNotifyCreated[i]);
	fNotifyCreated.Reset(); 
	fNotified.Reset();
	fActiveRefs.Reset();
}

void plKeyImp::AddNotifyCreated(plRefMsg* msg, plRefFlags::Type flags) 
{ 
	if (!(flags == plRefFlags::kPassiveRef))
	{
#ifdef LOG_ACTIVE_REFS
		if (IsTrackedKey(this))
		{
			hsStatusMessageF("@@@ %s(%s) adding active ref to %s (%d total)", msg->GetReceiver(0)->GetName(),
				plFactory::GetNameOfClass(msg->GetReceiver(0)->GetUoid().GetClassType()), kObjName, fNumActiveRefs+1);
		}
#endif // LOG_ACTIVE_REFS

		IncActiveRefs();
		SetActiveRef(GetNumNotifyCreated());
	}

	hsRefCnt_SafeRef(msg);
	fNotifyCreated.Append(msg); 
}

void plKeyImp::RemoveNotifyCreated(int i) 
{ 
	hsRefCnt_SafeUnRef(fNotifyCreated[i]);
	fNotifyCreated.Remove(i); 

	fNotified.RemoveBit(i);
	fActiveRefs.RemoveBit(i);
}

void plKeyImp::AddRef(plKeyImp* key) const
{
	fPendingRefs++;
	fRefs.Append(key);
}


void plKeyImp::RemoveRef(plKeyImp* key) const
{
	int idx = fRefs.Find(key);
	if (fRefs.kMissingIndex != idx)
		fRefs.Remove(idx);
}

void plKeyImp::AddClone(plKeyImp* key)
{
	hsAssert(!GetClone(key->GetUoid().GetClonePlayerID(), key->GetUoid().GetCloneID()),
				"Adding a clone which is already there?");

	key->fCloneOwner = plKey::Make(this);
	fClones.Append(key);
}

void plKeyImp::RemoveClone(plKeyImp* key) const
{
	if (key->GetUoid().IsClone())
	{
		int idx = fClones.Find(key);
		if (idx != -1)
		{
			fClones.Remove(idx);
			key->fCloneOwner = nil;
		}
	}
}

plKey plKeyImp::GetClone(UInt32 playerID, UInt32 cloneID) const
{
	for (int i = 0; i < fClones.GetCount(); i++)
	{
		plKeyImp* cloneKey = fClones[i];
		if (cloneKey
			&& cloneKey->GetUoid().GetCloneID() == cloneID
			&& cloneKey->GetUoid().GetClonePlayerID() == playerID)
			return plKey::Make(cloneKey);
	}

	return plKey();
}

UInt32 plKeyImp::GetNumClones()
{
	return fClones.GetCount();
}

plKey plKeyImp::GetCloneByIdx(UInt32 idx)
{
	if (idx < fClones.GetCount())
		return plKey::Make(fClones[idx]);

	return nil;
}

void plKeyImp::SatisfyPending(plRefMsg* msg) const
{
	for (int i = 0; i < msg->GetNumReceivers(); i++)
		((plKeyImp*)msg->GetReceiver(i))->SatisfyPending();
}

void plKeyImp::SatisfyPending() const
{
	hsAssert(fPendingRefs > 0, "Have more requests satisfied than we made");
	if (!--fPendingRefs)
	{
#ifdef PL_SEND_SATISFIED
		plSatisfiedMsg* msg = TRACKED_NEW plSatisfiedMsg(this);
		plgDispatch::MsgSend(msg);
#endif // PL_SEND_SATISFIED
	}
}

void plKeyImp::ISetupNotify(plRefMsg* msg, plRefFlags::Type flags)
{
	msg->SetSender(nil);

	AddNotifyCreated(msg, flags);

	hsAssert(msg->GetNumReceivers(), "nil object getting a reference");
	for (int i = 0; i < msg->GetNumReceivers(); i++)
		((plKeyImp*)msg->GetReceiver(i))->AddRef(plKey::Make(this));
}

void plKeyImp::SetupNotify(plRefMsg* msg, plRefFlags::Type flags)
{
	hsKeyedObject* ko = ObjectIsLoaded();

	ISetupNotify(msg, flags);

	// the KeyedObject is already Loaded, so we better go ahead and send the notify message just added.
	if (ko)
	{
		hsRefCnt_SafeRef(ko);
		msg->SetRef(ko);
		msg->SetTimeStamp(hsTimer::GetSysSeconds());

		// Add ref, since Dispatch will unref this msg but we want to keep using it.
		hsRefCnt_SafeRef(msg);

		SetNotified(GetNumNotifyCreated()-1);
		SatisfyPending(msg);

#ifdef LOAD_IN_THREAD		// test for resLoader
		plgDispatch::Dispatch()->MsgQueue(msg);
#else
		plgDispatch::Dispatch()->MsgSend(msg);
#endif

		hsRefCnt_SafeUnRef(ko);
	}
	hsRefCnt_SafeUnRef(msg);
}

// We could just call NotifyCreated() on all our fRefs, and then fix
// up fNotified to only get set when the message actually was delivered (i.e.
// refMsg->GetReceiver(0)->GetObjectPtr() != nil. But that only really works
// if we guarantee the refMsg->GetNumReceivers() == 1.
// This looks like it'll take forever to run, but this is only called right
// when our object has just been loaded, at which time normally fRefs.GetCount() == 0.
void plKeyImp::INotifySelf(hsKeyedObject* ko)
{
	for (int i = 0; i < fRefs.GetCount(); i++)
	{
		hsKeyedObject* rcv = fRefs[i]->GetObjectPtr();
		if (rcv)
		{
			for (int j = 0; j < fRefs[i]->fNotifyCreated.GetCount(); j++)
			{
				plRefMsg* refMsg = fRefs[i]->fNotifyCreated[j];
				if (refMsg && refMsg->GetRef() && !fRefs[i]->IsNotified(j))
				{
					hsAssert(refMsg->GetRef() == rcv, "Ref message out of sync with its ref");

					// GetNumReceivers() should always be 1 for a refMsg.
					for (int k = 0; k < refMsg->GetNumReceivers(); k++)
					{
						if (&(*refMsg->GetReceiver(k)) == (plKeyData*)this)
						{
							fRefs[i]->SetNotified(j);
							fRefs[i]->SatisfyPending(refMsg);

							hsRefCnt_SafeRef(refMsg);
							plgDispatch::MsgSend(refMsg);
							break;
						}
					}
				}
			}
		}
	}
}

void plKeyImp::NotifyCreated()
{
	hsKeyedObject* ko = GetObjectPtr();
	hsRefCnt_SafeRef(ko);
	hsAssert(ko, "Notifying of created before on nil object");
	
	INotifySelf(ko);
	
	for (int i = 0; i < GetNumNotifyCreated(); i++)
	{
		if (!IsNotified(i) && GetNotifyCreated(i)->GetReceiver(0)->GetObjectPtr())
		{
			plRefMsg* msg = GetNotifyCreated(i);
			msg->SetRef(ko);
			msg->SetTimeStamp(hsTimer::GetSysSeconds());
			msg->SetContext(plRefMsg::kOnCreate);
			hsRefCnt_SafeRef(msg);

			SetNotified(i);
			SatisfyPending(msg);

			plgDispatch::MsgSend(msg);		
		}
	}
	hsRefCnt_SafeUnRef(ko);
}

void plKeyImp::INotifyDestroyed()
{
	hsKeyedObject* ko = GetObjectPtr();
	hsAssert(ko, "Notifying of destroy on already destroyed");
	int i;
	for( i = 0; i < GetNumNotifyCreated(); i++ )
	{
		hsAssert(ko, "Notifying of destroy on already destroyed");
		plRefMsg* msg = GetNotifyCreated(i);
		msg->SetRef(ko);
		msg->SetTimeStamp(hsTimer::GetSysSeconds());
		msg->SetContext(plRefMsg::kOnDestroy);
		hsRefCnt_SafeRef(msg);
		msg->Send();
	}
	fNotified.Clear();
}

void plKeyImp::IClearRefs()
{
	while (GetNumRefs())
		IRelease(GetRef(0));
	fRefs.Reset();

	for (int i = 0; i < GetNumNotifyCreated(); i++)
	{
		plRefMsg* msg = GetNotifyCreated(i);
		for (int j = 0; j < msg->GetNumReceivers(); j++)
			((plKeyImp*)msg->GetReceiver(j))->RemoveRef(this);
	}
}

void plKeyImp::Release(plKey targetKey)
{
	IRelease((plKeyImp*)targetKey);
}

void plKeyImp::IRelease(plKeyImp* iTargetKey)
{
	// Remove the target key from my ref list
	RemoveRef(iTargetKey);

	// Inspect the target key to find whether it is supposed to send a message
	// to me on destruction, and to find out if I have an active of passive 
	// ref on this key.  Not sure why I don't track my own active/passive ref states
	hsBool isActive = false;
	int iTarg = -1;
	for (int i = 0; (iTarg < 0) && (i < iTargetKey->GetNumNotifyCreated()); i++)
	{
		plMessage* rcvMsg = iTargetKey->GetNotifyCreated(i);
		for (int j = 0; j < rcvMsg->GetNumReceivers(); j++)
		{
			if (&(*rcvMsg->GetReceiver(j)) == (plKeyData*)this)
			{
				isActive = iTargetKey->IsActiveRef(iTarg = i);
				break;
			}
		}
	}

	if (iTarg < 0)
	{
		// If it doesn't send me a message on destruction,  I am assuming I don't have an
		// active ref on the key (seems to be always true, but should we depend on it?)
		// Since it doesn't send me a message, and won't be destroyed by the release, no 
		// need to do anything more here
		return;
	}

	// If releasing this target causes its destruction, we'll notify
	// everyone that target is dead. Otherwise, we'll remove
	// releaser from target's notify list and notify releaser
	// it's been removed. Object doesn't actually get deleted until
	// it receives the SelfDestruct msg, by which time everyone referencing
	// it has been notified it is going away.
#ifdef LOG_ACTIVE_REFS
	if (isActive && IsTrackedKey(iTargetKey))
		hsStatusMessageF("@@@ %s(%s) releasing active ref on %s (%d total)", GetName(), plFactory::GetNameOfClass(GetUoid().GetClassType()), kObjName, iTargetKey->fNumActiveRefs-1);
#endif // LOG_ACTIVE_REFS

	if (isActive && iTargetKey->GetActiveRefs() && !iTargetKey->DecActiveRefs())
	{
		iTargetKey->INotifyDestroyed();

		iTargetKey->IClearRefs();
		iTargetKey->ClearNotifyCreated();
		
		plKey key = plKey::Make(iTargetKey);
		plSelfDestructMsg* nuke = TRACKED_NEW plSelfDestructMsg(key);
		plgDispatch::Dispatch()->MsgSend(nuke);
	}
	else
	{
		plRefMsg* refMsg = iTargetKey->GetNotifyCreated(iTarg);
		hsRefCnt_SafeRef(refMsg);
		iTargetKey->RemoveNotifyCreated(iTarg);
		if (refMsg)
		{
			refMsg->SetRef(iTargetKey->ObjectIsLoaded());
			refMsg->SetTimeStamp(hsTimer::GetSysSeconds());
			refMsg->SetContext(plRefMsg::kOnRemove);
			hsRefCnt_SafeRef(refMsg);
			plgDispatch::MsgSend(refMsg);
		}
		hsRefCnt_SafeUnRef(refMsg);
	}
}