/*==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);
    }
}