/*==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/>.

Additional permissions under GNU GPL version 3 section 7

If you modify this Program, or any covered work, by linking or
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
(or a modified version of those libraries),
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
licensors of this Program grant you additional
permission to convey the resulting work. Corresponding Source for a
non-source form of such a combination shall include the source code for
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
work.

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==*/
#pragma warning(disable: 4503 4786)

#include <algorithm>

#include "plAvatarMgr.h"

// local
#include "plArmatureMod.h"
#include "plSeekPointMod.h"
#include "plOneShotMod.h"
#include "plArmatureMod.h"
#include "plAGModifier.h"
#include "plAnimStage.h"
#include "plCoopCoordinator.h"
#include "plAvBrainCoop.h"

// global
#include "hsResMgr.h"
#include "pnNetCommon/plNetApp.h"
#include "plgDispatch.h"
#include "hsTimer.h"

// other
#include "pnSceneObject/plSceneObject.h"
#include "pnSceneObject/plCoordinateInterface.h"
#include "pnKeyedObject/plKey.h"
#include "pnKeyedObject/plFixedKey.h"
#include "plNetClient/plNetClientMgr.h"
#include "plResMgr/plKeyFinder.h"
#include "pfCCR/plCCRMgr.h" // Only included for defined constants. 
#include "plNetTransport/plNetTransport.h"
#include "plNetTransport/plNetTransportMember.h"
#include "plModifier/plSpawnModifier.h"
#include "plModifier/plMaintainersMarkerModifier.h"
#include "plVault/plDniCoordinateInfo.h"
#include "pnEncryption/plRandom.h"

#include "pnMessage/plPlayerPageMsg.h"
#include "pnMessage/plWarpMsg.h"
#include "pnMessage/plNotifyMsg.h"

#include "plMessage/plMemberUpdateMsg.h"
#include "plMessage/plAvatarMsg.h"
#include "plMessage/plAvCoopMsg.h"
#include "pnMessage/plTimeMsg.h"
#include "plStatusLog/plStatusLog.h"

// The static single instance, allocated on demand by GetInstance()
plAvatarMgr     *plAvatarMgr::fInstance = nil;

// CTOR
plAvatarMgr::plAvatarMgr()
{
    fLog = plStatusLogMgr::GetInstance().CreateStatusLog(40, "Avatar.log", plStatusLog::kFilledBackground | plStatusLog::kAlignToTop | plStatusLog::kTimestamp);
    fLog->AddLine("Initalized avatar mgr"); 
}

// DTOR
plAvatarMgr::~plAvatarMgr()
{
    IReset();

    delete fLog;
    fLog = nil;
}

// GETINSTANCE
plAvatarMgr * plAvatarMgr::GetInstance()
{
    if(!fInstance)
    {
        fInstance = new plAvatarMgr;
        fInstance->RegisterAs(kAvatarMgr_KEY);
        fInstance->Ref();
    }
    return fInstance;
}

// SHUTDOWN
void plAvatarMgr::ShutDown()
{
    if(fInstance)
    {
        fInstance->UnRef();
        if(fInstance)
            fInstance->UnRegister();
        fInstance = nil;
    }
}

// RESET
void plAvatarMgr::IReset()
{
    fSeekPoints.clear();

    fOneShots.clear();
    fAvatars.clear();
    fSpawnPoints.clear();
    fMaintainersMarkers.SetCountAndZero(0);

    plCoopMap::iterator acIt = fActiveCoops.begin();
    while (acIt != fActiveCoops.end())
    {
        plCoopCoordinator* deadCoop = acIt->second;
        delete deadCoop;
        acIt++;
    }
    fActiveCoops.clear();
}

plKey plAvatarMgr::LoadPlayer(const char *name, const char *account)
{
    return LoadAvatar(name, account, true, nil, nil);
}

plKey plAvatarMgr::LoadPlayer(const char *name, const char *account, const char *linkInName)
{
    // what we'd like to do is turn the linkInName into a spawn point key and
    // put that into the plLoadAvatarMsg, which is already set up to handle
    // initial spawn points.
    // however, that will require that we can handle waiting for our spawn point to load,
    // so we're goin to do this the "old way" for now.
    
    plArmatureMod::SetSpawnPointOverride(linkInName);
    return LoadAvatar(name, account, true, nil, nil);
}


plKey plAvatarMgr::LoadAvatar(const char *name, const char *accountName, bool isPlayer, plKey spawnPoint, plAvTask *initialTask, const char *userStr /*=nil*/)
{
    // *** account is currently unused. the idea is that eventually an NPC will
    // *** be able to use a customization account
    plKey result = nil;
    plKey requestor = GetKey(); // avatar manager is always the requestor for avatar loads
    plNetClientMgr *netMgr = plNetClientMgr::GetInstance();

    if(netMgr)      // can't clone without the net manager
    {
        hsAssert(name, "name required by LoadPlayer fxn");
        netMgr->DebugMsg("Local: Loading player %s", name); 

        // look up player by key name provided by user.
        // this string search should be replaced with some other method of 
        // avatar selection and key lookup.

        // Get the location for the player first
        plKey playerKey = nil;
        const plLocation& globalLoc = plKeyFinder::Instance().FindLocation("GlobalAvatars", name);
        const plLocation& maleLoc = plKeyFinder::Instance().FindLocation("GlobalAvatars", "Male");
        const plLocation& custLoc = plKeyFinder::Instance().FindLocation("CustomAvatars", name);

#ifdef PLASMA_EXTERNAL_RELEASE
        // Try global. If that doesn't work, players default to male.
        // If not a player, try custLoc. If that doesn't work, fall back to male
        const plLocation& loc = (globalLoc.IsValid() ? globalLoc : isPlayer ? maleLoc : custLoc.IsValid() ? custLoc : maleLoc);
#else
        // Try global. If that doesn't work try custom. Otherwise fall back to male
        const plLocation& loc = (globalLoc.IsValid() ? globalLoc : custLoc.IsValid() ? custLoc : maleLoc);
#endif

        plString theName = _TEMP_CONVERT_FROM_LITERAL(name);
        if ( loc == maleLoc )
            theName = _TEMP_CONVERT_FROM_LITERAL("Male");

        if (loc.IsValid())
        {
            plUoid uID(loc, plSceneObject::Index(), theName);
            plLoadAvatarMsg *cloneMsg = new plLoadAvatarMsg (uID, requestor, 0, isPlayer, spawnPoint, initialTask, userStr);
            result =  cloneMsg->GetCloneKey();
            
            // the clone message is automatically addressed to the net client manager
            // we'll receive the message back (or a similar message) when the clone is loaded
            cloneMsg->Send();
        }
    }
    return result;
}

void plAvatarMgr::UnLoadAvatar(plKey avatarKey, bool isPlayer)
{
    hsBool isLoading = false;
    plLoadAvatarMsg *msg = new plLoadAvatarMsg(avatarKey, GetKey(), 0, isPlayer, isLoading);
    msg->Send();
}

// our player's already loaded locally, but we've just linked into an age and others there need to be
// told about us
void plAvatarMgr::PropagateLocalPlayer(int spawnPoint)
{
    plKey playerKey = plNetClientMgr::GetInstance()->GetLocalPlayerKey();
    if(playerKey)
    {
        plKey requestor = GetKey();
        bool isPlayer = true;
        hsBool isLoading = true;
        plLoadAvatarMsg *msg = new plLoadAvatarMsg(playerKey, requestor, 0, isPlayer, isLoading);

        if (spawnPoint >= 0)
        {
            const plSpawnModifier * spawn = GetSpawnPoint(spawnPoint);
            if ( spawn )
            {
                const plSceneObject * spawnObj = spawn->GetTarget(0);
                msg->SetSpawnPoint(spawnObj->GetKey());
            }
        }

        // don't propagate locally. this is only for our peers
        msg->SetBCastFlag(plMessage::kLocalPropagate, false);
        msg->Send();
    } else {
        hsStatusMessage("Tried to propagate non-existent local player.");
    }
}

// UNLOADLOCALPLAYERREMOTELY
bool plAvatarMgr::UnPropagateLocalPlayer()
{
    plKey playerKey = plNetClientMgr::GetInstance()->GetLocalPlayerKey();
    if(playerKey)
    {
        plKey requestor = GetKey();
        bool isPlayer = true;
        hsBool isLoading = false;
        plLoadAvatarMsg *msg = new plLoadAvatarMsg(playerKey, requestor, 0, isPlayer, isLoading);
        msg->SetBCastFlag(plMessage::kLocalPropagate, false);
        msg->Send();
        return true;
    }
    return false;
}

// UNLOADREMOTEPLAYER
void plAvatarMgr::UnLoadRemotePlayer(plKey remotePlayer)
{
    if(remotePlayer)
    {
        plKey requestor = GetKey();
        bool isPlayer = true;
        hsBool isLoading = false;
        plLoadAvatarMsg * msg = new plLoadAvatarMsg(remotePlayer, requestor, 0, isPlayer, isLoading);

        // don't propagate over the network. this is just for removing our local version
        msg->SetBCastFlag(plMessage::kNetPropagate, false);
        msg->Send();
    }
}

// UNLOADLOCALPLAYER
void plAvatarMgr::UnLoadLocalPlayer()
{
    plKey playerKey = plNetClientMgr::GetInstance()->GetLocalPlayerKey();
    if(playerKey)
    {
        plKey mgrKey = GetKey();
        bool isPlayer = true;
        hsBool isLoading = false;
        plLoadAvatarMsg *msg = new plLoadAvatarMsg(playerKey, mgrKey, 0, isPlayer, isLoading);
        msg->Send();
    }
}

// MSGRECEIVE
hsBool plAvatarMgr::MsgReceive(plMessage *msg)
{
    plLoadAvatarMsg *cloneM = plLoadAvatarMsg::ConvertNoRef(msg);
    if(cloneM)
    {
        // The only way we get clone messages is if we (or our remote counterparts)
        // requested them.
        if(cloneM->GetIsLoading())
        {
            IFinishLoadingAvatar(cloneM);
        } else {
            IFinishUnloadingAvatar(cloneM);
        }
        return true;
    }

    plLoadCloneMsg* pCloneMsg = plLoadCloneMsg::ConvertNoRef(msg);
    if (pCloneMsg)
    {
        pCloneMsg->Ref();
        fCloneMsgQueue.Append(pCloneMsg);
        plgDispatch::Dispatch()->RegisterForExactType(plEvalMsg::Index(), GetKey());
        return true;
    }

    plEvalMsg* pEval = plEvalMsg::ConvertNoRef(msg);
    if (pEval)
    {
        for (int i = fCloneMsgQueue.Count() - 1; i > -1; i--)
        {
            plArmatureMod* pAvatar = FindAvatarByPlayerID(fCloneMsgQueue[i]->GetUserData());
            if (pAvatar && pAvatar->GetKey()->ObjectIsLoaded())
            {   
                pAvatar->MsgReceive(fCloneMsgQueue[i]);
                fCloneMsgQueue[i]->UnRef();
                fCloneMsgQueue.Remove(i);
            }
        }
        if (fCloneMsgQueue.Count() == 0)
            plgDispatch::Dispatch()->UnRegisterForExactType(plEvalMsg::Index(), GetKey());
        
        return true;
    }
    plAvCoopMsg *coopM = plAvCoopMsg::ConvertNoRef(msg);
    if(coopM)
    {
        return HandleCoopMsg(coopM);
    }

    plNotifyMsg *notifyM = plNotifyMsg::ConvertNoRef(msg);
    if(notifyM)
    {
        if(proEventData * evt = notifyM->FindEventRecord(proEventData::kCoop))
        {
            proCoopEventData *coopE = static_cast<proCoopEventData *>(evt);
            return IPassMessageToActiveCoop(msg, coopE->fID, coopE->fSerial);
        }
    }

    return false;
}

hsBool plAvatarMgr::HandleCoopMsg(plAvCoopMsg *msg)
{
    plAvCoopMsg::Command cmd = msg->fCommand;
    
    uint32_t id = msg->fInitiatorID;
    uint16_t serial = msg->fInitiatorSerial;

    if(cmd == plAvCoopMsg::kStartNew)
    {
        // Currently, there's nothing that removes these coop coordinators when
        // they're done.  Since I can't think of a good way to figure out when
        // they're done, I'm just going to clear them every time a new one starts.
        // With the current usage, you should only get one at a time anyway -Colin
        plCoopMap::iterator it = fActiveCoops.begin();
        while (it != fActiveCoops.end())
        {
            plCoopCoordinator* deadCoop = it->second;
            delete deadCoop;
            it++;
        }
        fActiveCoops.clear();

        // start a new coop
        plCoopCoordinator *coord = msg->fCoordinator;
        plCoopMap::value_type newVal(id, coord);
        fActiveCoops.insert(newVal);
        coord->Run();
        return true;
    } else {
        // it's a message for an existing coop...
        return IPassMessageToActiveCoop(msg, id, serial);
    }
}

hsBool plAvatarMgr::HandleNotifyMsg(plNotifyMsg *msg)
{
    proCoopEventData *ed = static_cast<proCoopEventData *>(msg->FindEventRecord(proEventData::kCoop));
    if(ed)
    {
        uint32_t id = ed->fID;
        uint16_t serial = ed->fSerial;
        return IPassMessageToActiveCoop(msg, id, serial);
    }
    return false;
}

hsBool plAvatarMgr::IPassMessageToActiveCoop(plMessage *msg, uint32_t id, uint16_t serial)
{
    plCoopMap::iterator i = fActiveCoops.find(id);
    while(i != fActiveCoops.end() && (*i).first == id)
    {
        plCoopCoordinator *coord = (*i).second;
        if(coord->GetInitiatorSerial() == serial && coord->IsActiveForReal() )
        {
            // this is the one
            coord->MsgReceive(msg);
            return true;
        }
        i++;
    }
    return false;
}

bool plAvatarMgr::IsACoopRunning()
{
    bool isRunning = false;
    plCoopMap::iterator it = fActiveCoops.begin();
    while (it != fActiveCoops.end())
    {
        plCoopCoordinator* aCoop = it->second;
        if (aCoop->IsActiveForReal())
            isRunning = true;
        it++;
    }
    return isRunning;
}


void plAvatarMgr::IFinishLoadingAvatar(plLoadAvatarMsg *cloneMsg)
{
    plKey avatarKey = cloneMsg->GetCloneKey();
    plUoid playerUoid = avatarKey->GetUoid();
    const plArmatureMod *armature = FindAvatar(avatarKey);

    // we're going to re-send the clone message to the loaded avatar so he can get
    // any necessary details from it.
    cloneMsg->ClearReceivers();     // don't want it coming back to us
    cloneMsg->Ref();                // or going away

    if(armature)
    {
        cloneMsg->AddReceiver(armature->GetKey());
        cloneMsg->Send();
    } else {
        IDeferInit(avatarKey, cloneMsg);        // we'll send this message when the armature mod loads.
    }

    if( cloneMsg->GetIsPlayer() )
    {
        // notify everyone who cares that a new player has arrived
        // *** might want to move this to the human brain so we can make sure the 
        // *** avatar is sufficiently initialized before anyone accesses him
        bool isLocal = cloneMsg->GetOriginatingPlayerID() == plNetClientMgr::GetInstance()->GetPlayerID();
        plPlayerPageMsg* pageM = new plPlayerPageMsg;
        pageM->SetBCastFlag(plMessage::kBCastByExactType);
        pageM->fLocallyOriginated = isLocal;
        pageM->fPlayer = avatarKey;
        pageM->fUnload = false;
        pageM->fClientID = cloneMsg->GetOriginatingPlayerID();
        pageM->Send();
    }

    // This can probably be replaced by the plPlayerPageMsg:
    // ...keeping for the moment for compatibility
    plMemberUpdateMsg* mu = new plMemberUpdateMsg;
    mu->Send();
}

// IFINISHUNLOADINGAVATAR
void plAvatarMgr::IFinishUnloadingAvatar(plLoadAvatarMsg *cloneMsg)
{
    // Note: in the corresponding FinishLoading, above, we give the incoming avatar
    // a look at the message that spawned him. When unloading, however, he doesn't get
    // that benefit because I don't think he'll actually be around to receive it.
    // *** need to test that theory....but it's not a problem for now.
    if( cloneMsg->GetIsPlayer() )
    {
        plKey avatar = cloneMsg->GetCloneKey();

        bool isLocal = cloneMsg->GetOriginatingPlayerID() == plNetClientMgr::GetInstance()->GetPlayerID();
        plPlayerPageMsg *pageM = new plPlayerPageMsg;
        pageM->SetBCastFlag(plMessage::kBCastByExactType);
        pageM->fLocallyOriginated = isLocal;
        pageM->fPlayer = avatar;
        pageM->fUnload = true;
        pageM->fClientID = cloneMsg->GetOriginatingPlayerID();
        if (plNetClientMgr::GetInstance()->RemotePlayerKeys().size() == 0)
            pageM->fLastOut = true;
        pageM->Send();
    }

    // check on this...can it be subsumed by plPlayerPageMsg ?
    plMemberUpdateMsg *mu = new plMemberUpdateMsg;
    mu->Send();
}


// IDEFERINIT
void plAvatarMgr::IDeferInit(plKey playerSOKey, plMessage *initMsg)
{
    plMessage *existing = fDeferredInits[playerSOKey];      // okay to use this form because we're going
                                                            // to do the add either way
    if(existing)
    {
        hsStatusMessage("Avatar was registered twice for init. Discarding initial init message.");
        existing->UnRef();
    }

    fDeferredInits[playerSOKey] = initMsg;
    initMsg->Ref();
}

// ISENDDEFERREDINIT
void plAvatarMgr::ISendDeferredInit(plKey avatarSOKey)
{
    // get armaturemod
    const plArmatureMod * armature = FindAvatar(avatarSOKey);

    if(armature)
    {
        DeferredInits::iterator i = fDeferredInits.find(avatarSOKey);
        bool found = (i != fDeferredInits.end());

        if(i != fDeferredInits.end())
        {
            plMessage * initMsg = (*i).second;
            hsAssert(initMsg, "Tried to init avatar, but found nil initialization message.");
            
            if(initMsg)
            {
                initMsg->AddReceiver(armature->GetKey());
                initMsg->Send();
            }
        }
    }
}


// ADDSEEKPOINT
void plAvatarMgr::AddSeekPoint(plSeekPointMod *seekPoint)
{
    if(seekPoint)
    {
        plString name = seekPoint->GetTarget(0)->GetKey()->GetName();
        plSeekPointMod *alreadyThere = FindSeekPoint(name);

        /// hsAssert( ! alreadyThere, "Tried to add a seek point with duplicate name. Ignoring second seek point.");

        if ( ! alreadyThere)
        {
            fSeekPoints[name] = seekPoint;
        }
    }
}

// REMOVESEEKPOINT
void plAvatarMgr::RemoveSeekPoint(plSeekPointMod *seekPoint)
{
    if(seekPoint)
    {
        plString name = seekPoint->GetTarget(0)->GetKey()->GetName();

        plSeekPointMap::iterator found = fSeekPoints.find(name);

        if(found != fSeekPoints.end())
        {
            fSeekPoints.erase(found);
        }
    }
}

// FINDSEEKPOINT
plSeekPointMod * plAvatarMgr::FindSeekPoint(const plString &name)
{
    plSeekPointMap::iterator found = fSeekPoints.find(name);
    
    if (found == fSeekPoints.end())
    {
        return nil;
    } else {
        return (*found).second;
    }
}

// ADDONESHOT
void plAvatarMgr::AddOneShot(plOneShotMod *oneshot)
{
    if(oneshot)
    {
        plString name = oneshot->GetTarget(0)->GetKey()->GetName();
        plOneShotMod *alreadyThere = FindOneShot(name);


        if ( ! alreadyThere)
        {
            fOneShots[name] = oneshot;
        }
    }
}

// REMOVEONESHOT
void plAvatarMgr::RemoveOneShot(plOneShotMod *oneshot)
{
    plOneShotMap::iterator i = fOneShots.begin();

    while (i != fOneShots.end())
    {
        plString name = i->first;
        plOneShotMod *thisOneshot = i->second;

        if(oneshot == thisOneshot)
        {
            fOneShots.erase(i++);
        } else {
            ++i;
        }
    }
}

// FINDONESHOT
plOneShotMod *plAvatarMgr::FindOneShot(const plString &name)
{
    plOneShotMap::iterator found = fOneShots.find(name);

    if (found == fOneShots.end())
    {
        return nil;
    } else {
        return found->second;
    }
}

// ADDAVATAR
void plAvatarMgr::AddAvatar(plArmatureMod *avatar)
{
    // we shouldn't really need to ref this, as every time we access this object we will be checking it, and we don't care too much if it gets
    // pulled out from under us
    fAvatars.push_back(avatar->GetKey());
    plSceneObject *avatarSO = avatar->GetTarget(0);
    hsAssert(avatarSO, "Adding avatar, but it hasn't been attached to a scene object yet.");
    if(avatarSO)
    {
        plKey soKey = avatarSO->GetKey();
        ISendDeferredInit(soKey);
    }
}

// REMOVEAVATAR
void plAvatarMgr::RemoveAvatar(plArmatureMod *avatar)
{
    if (avatar)
    {
        plAvatarVec::iterator tail = std::remove(fAvatars.begin(), fAvatars.end(), avatar->GetKey());
        if(tail != fAvatars.end())
            fAvatars.erase(tail);
    }
}

plArmatureMod* plAvatarMgr::GetLocalAvatar()
{
    plNetClientApp * app = plNetClientApp::GetInstance();
    if(app)
    {
        plKey key = app->GetLocalPlayerKey();
        if (key && key->ObjectIsLoaded())
        {
            plSceneObject* so = plSceneObject::ConvertNoRef(key->GetObjectPtr());
            if (so)
                return const_cast<plArmatureMod*>((plArmatureMod*)so->GetModifierByType(plArmatureMod::Index()));
        }
    }

    return nil;
}

plKey plAvatarMgr::GetLocalAvatarKey()
{
    plArmatureMod *avatar = GetLocalAvatar();
    if (avatar)
        return avatar->GetKey();

    return nil;
}

plArmatureMod *plAvatarMgr::GetFirstRemoteAvatar()
{
    plNetClientApp * app = plNetClientApp::GetInstance();
    if(app)
    {
        plArmatureMod *localAvatar = GetLocalAvatar();
        
        plAvatarVec::iterator it;
        for (it = fAvatars.begin(); it != fAvatars.end(); ++it)
        {
            plArmatureMod* armature = plArmatureMod::ConvertNoRef((*it)->ObjectIsLoaded());
            if(armature && (armature != localAvatar))
                return armature;
        }
    }
    
    return nil;
}

plArmatureMod* plAvatarMgr::FindAvatar(plKey& avatarKey)
{
    plSceneObject *so = plSceneObject::ConvertNoRef(avatarKey->ObjectIsLoaded());
    if (so)
        return const_cast<plArmatureMod*>((plArmatureMod*)so->GetModifierByType(plArmatureMod::Index()));
    
    return nil;
}

plArmatureMod* plAvatarMgr::FindAvatarByPlayerID(uint32_t pid)
{
    plAvatarVec::iterator it;
    for (it = fAvatars.begin(); it != fAvatars.end(); ++it)
    {
        plArmatureMod* armature = plArmatureMod::ConvertNoRef((*it)->ObjectIsLoaded());
        if (armature && (armature->GetKey()->GetUoid().GetClonePlayerID() == pid))
            return armature;
    }
    return nil;
}

plArmatureMod *plAvatarMgr::FindAvatarByModelName(char *name)
{
    plAvatarVec::iterator it;
    for (it = fAvatars.begin(); it != fAvatars.end(); ++it)
    {
        plArmatureMod* armature = plArmatureMod::ConvertNoRef((*it)->ObjectIsLoaded());
        if (armature && (!armature->GetTarget(0)->GetKeyName().Compare(name)))
            return armature;
    }
    
    return nil;
}

void plAvatarMgr::FindAllAvatarsByModelName(const char* name, plArmatureModPtrVec& outVec)
{
    plAvatarVec::iterator it;
    for (it = fAvatars.begin(); it != fAvatars.end(); ++it)
    {
        plArmatureMod* armature = plArmatureMod::ConvertNoRef((*it)->ObjectIsLoaded());
        if (armature && (!armature->GetTarget(0)->GetKeyName().Compare(name)))
            outVec.push_back(armature);
    }
}

// ADDSPAWNPOINT
void plAvatarMgr::AddSpawnPoint(plSpawnModifier *spawn)
{
    fSpawnPoints.push_back(spawn);
}

// REMOVESPAWNPOINT
void plAvatarMgr::RemoveSpawnPoint(plSpawnModifier *spawn)
{
    plSpawnVec::iterator found = std::find(fSpawnPoints.begin(), fSpawnPoints.end(), spawn);

    if(found != fSpawnPoints.end())
    {
        fSpawnPoints.erase(found);
    }
}

// GETSPAWNPOINT
const plSpawnModifier * plAvatarMgr::GetSpawnPoint(int i)
{
    if(i < fSpawnPoints.size())
    {
        return fSpawnPoints[i];
    } else return nil;
}

int plAvatarMgr::FindSpawnPoint( const char *name ) const
{
    int i;

    for( i = 0; i < fSpawnPoints.size(); i++ )
    {
        if( fSpawnPoints[ i ] != nil && 
            ( fSpawnPoints[ i ]->GetKey()->GetUoid().GetObjectName().Find( name ) >= 0 ||
              fSpawnPoints[ i ]->GetTarget(0)->GetKeyName().Find( name ) >= 0 ))
            return i;
    }

    return -1;
}

int plAvatarMgr::WarpPlayerToAnother(hsBool iMove, uint32_t remoteID)
{
    plNetTransport &mgr = plNetClientMgr::GetInstance()->TransportMgr();
    plNetTransportMember *mbr = mgr.GetMember(mgr.FindMember(remoteID));

    if (!mbr)
        return plCCRError::kCantFindPlayer;
    
    if (!mbr->GetAvatarKey())
        return plCCRError::kPlayerNotInAge;

    plSceneObject *remoteSO = plSceneObject::ConvertNoRef(mbr->GetAvatarKey()->ObjectIsLoaded());
    plSceneObject *localSO = plSceneObject::ConvertNoRef(plNetClientMgr::GetInstance()->GetLocalPlayer());

    if (!remoteSO)
        return plCCRError::kCantFindPlayer;
    if (!localSO)
        return plCCRError::kNilLocalAvatar;

    plWarpMsg *warp = new plWarpMsg(nil, (iMove ? localSO->GetKey() : remoteSO->GetKey()), 
        plWarpMsg::kFlushTransform, (iMove ? remoteSO->GetLocalToWorld() : localSO->GetLocalToWorld()));
    
    warp->SetBCastFlag(plMessage::kNetPropagate);
    plgDispatch::MsgSend(warp);

    return hsOK;
}

int plAvatarMgr::WarpPlayerToXYZ(float x, float y, float z)
{
    plSceneObject *localSO = plSceneObject::ConvertNoRef(plNetClientMgr::GetInstance()->GetLocalPlayer());
    if (!localSO)
        return plCCRError::kNilLocalAvatar;

    hsMatrix44 m = localSO->GetLocalToWorld();
    hsVector3 v(x, y, z);
    m.SetTranslate(&v);

    plWarpMsg *warp = new plWarpMsg(nil, localSO->GetKey(), plWarpMsg::kFlushTransform, m);
    warp->SetBCastFlag(plMessage::kNetPropagate);
    plgDispatch::MsgSend(warp);

    return hsOK;
}

int plAvatarMgr::WarpPlayerToXYZ(int pid, float x, float y, float z)
{
    plNetClientMgr* nc=plNetClientMgr::GetInstance();
    plNetTransportMember* mbr=nc->TransportMgr().GetMember(nc->TransportMgr().FindMember(pid));
    plSceneObject *player = plSceneObject::ConvertNoRef(mbr && mbr->GetAvatarKey() ? 
        mbr->GetAvatarKey()->ObjectIsLoaded() : nil);
    if (!player)
        return plCCRError::kNilLocalAvatar;

    hsMatrix44 m = player->GetLocalToWorld();
    hsVector3 v(x, y, z);
    m.SetTranslate(&v);

    plWarpMsg *warp = new plWarpMsg(nil, player->GetKey(), 0, m);
    warp->SetBCastFlag(plMessage::kNetPropagate);
    plgDispatch::MsgSend(warp);

    return hsOK;
}

// ADD maintainers marker
void plAvatarMgr::AddMaintainersMarker(plMaintainersMarkerModifier *mm)
{
    fMaintainersMarkers.Append(mm);
}

// REMOVE maintainers marker
void plAvatarMgr::RemoveMaintainersMarker(plMaintainersMarkerModifier *mm)
{
    for (int i = 0; i < fMaintainersMarkers.Count(); i++)
    {
        if (fMaintainersMarkers[i] == mm)
            fMaintainersMarkers.Remove(i);
    }
}

void plAvatarMgr::PointToDniCoordinate(hsPoint3 pt, plDniCoordinateInfo* ret)
{
    int count = fMaintainersMarkers.Count();
    //  plDniCoordinateInfo ret = new plDniCoordinateInfo;
    if (count > 0)
    {   

        // find the closest maintainers marker
        int nearestIndex = 0;
        if (count > 1)
        {
            for (int i = 0; i < fMaintainersMarkers.Count(); i++)
            {
                if (fMaintainersMarkers[i]->GetTarget(0))
                {
                    hsVector3 testDist(fMaintainersMarkers[i]->GetTarget(0)->GetCoordinateInterface()->GetLocalToWorld().GetTranslate() - pt);
                    hsVector3 baseDist(fMaintainersMarkers[nearestIndex]->GetTarget(0)->GetCoordinateInterface()->GetLocalToWorld().GetTranslate() - pt);  
                    if (testDist.MagnitudeSquared() < baseDist.MagnitudeSquared())
                        nearestIndex = i;
                }
            }
        }
        // convert the marker position to Dni coordinates
        int status = fMaintainersMarkers[nearestIndex]->GetCalibrated();

        switch (status)
        {
        case  plMaintainersMarkerModifier::kBroken:
            {
                plRandom rnd;
                rnd.SetSeed((int)(hsTimer::GetSeconds()));
                rnd.RandRangeI(1,999);
                ret->SetHSpans( rnd.RandRangeI(1,999) );
                ret->SetVSpans( rnd.RandRangeI(1,999) );
                ret->SetTorans( rnd.RandRangeI(1,62500) );
            }
            break;
        case plMaintainersMarkerModifier::kRepaired:
            {
                ret->SetHSpans(0);
                ret->SetVSpans(0);
                ret->SetTorans(0);
            }
            break;
        case plMaintainersMarkerModifier::kCalibrated:
            {
                // this is the real deal here:
                // vertical spans:
                hsPoint3 retPoint = fMaintainersMarkers[nearestIndex]->GetTarget(0)->GetCoordinateInterface()->GetLocalToWorld().GetTranslate();
                ret->SetVSpans( ((int)(pt.fZ - retPoint.fZ) / 16) );

                // horizontal spans:

                // zero out the z axis...
                retPoint.fZ = pt.fZ = 0.0f;
                hsVector3 hSpanVec(retPoint - pt);
                ret->SetHSpans( (int)hSpanVec.Magnitude() / 16) ;

                // torans
                hsVector3 zeroVec = fMaintainersMarkers[nearestIndex]->GetTarget(0)->GetCoordinateInterface()->GetLocalToWorld().GetAxis(hsMatrix44::kView);
                hsVector3 zeroRight = fMaintainersMarkers[nearestIndex]->GetTarget(0)->GetCoordinateInterface()->GetLocalToWorld().GetAxis(hsMatrix44::kRight);
                zeroVec *= -1; // match the zero vectors to the positive X & Y axes in 3DSMax
                zeroRight *= -1;
                hsVector3 retVec(pt - retPoint);
                retVec.Normalize();

                float dotView = retVec * zeroVec;
                float dotRight = retVec * zeroRight;

                float deg = acosf(dotView);
                deg*=(180/3.141592);
                // account for being > 180
                if (dotRight < 0.0f) 
                {
                    deg = 360.f - deg;
                }
                // convert it to dni radians (torans)
                deg*=173.61;
                ret->SetTorans((int)deg);
            }
            break;
        }

    }
}

void plAvatarMgr::GetDniCoordinate(plDniCoordinateInfo* ret)
{
    plSceneObject* localSO = plSceneObject::ConvertNoRef(plNetClientMgr::GetInstance()->GetLocalPlayer());
    if (localSO)
    {   
        hsPoint3 pos = localSO->GetCoordinateInterface()->GetLocalToWorld().GetTranslate();
        PointToDniCoordinate(pos, ret);
    }
}

// OfferLinkingBook ---------------------------------------------------------------
// ----------
void plAvatarMgr::OfferLinkingBook(plKey hostKey, plKey guestKey, plMessage *linkMsg, plKey replyKey)
{
    if(hostKey != nil && guestKey != nil)
    {
        const plArmatureMod *hostAv = FindAvatar(hostKey);
        const plArmatureMod *guestAv = FindAvatar(guestKey);

        hsAssert(hostAv && guestAv, "Offering linking book: host or guest missing.");

        if(hostAv && guestAv)
        {

            // make the host brain
            plAvBrainCoop * brainH = new plAvBrainCoop(plAvBrainGeneric::kExitNormal, 3.0, 3.0, plAvBrainGeneric::kMoveRelative, guestKey);

            plAnimStage *hostOffer = new plAnimStage("BookOffer", plAnimStage::kNotifyAdvance);     // autoforward, autoadvance
            // repeats until the guest brain tells us that it's done
            plAnimStage *hostIdle = new plAnimStage("BookOfferIdle", plAnimStage::kNotifyEnter, plAnimStage::kForwardAuto, plAnimStage::kBackNone,
                                                    plAnimStage::kAdvanceNone, plAnimStage::kRegressNone, -1);

            plAnimStage *hostFinish = new plAnimStage("BookOfferFinish", plAnimStage::kNotifyAdvance);  // autoforward, autoadvance
            
            brainH->AddStage(hostOffer);
            brainH->AddStage(hostIdle);
            brainH->AddStage(hostFinish);

            uint32_t hostID = brainH->GetInitiatorID();
            uint32_t hostSerial = brainH->GetInitiatorSerial();


            // make the guest brain
            plAvBrainCoop * brainG = new plAvBrainCoop(plAvBrainGeneric::kExitNormal, 3.0, 3.0, plAvBrainGeneric::kMoveRelative,
                                                       hostID, (uint16_t)hostSerial, hostKey);

            plAnimStage *guestAccept = new plAnimStage("BookAccept", plAnimStage::kNotifyAdvance);
            plAnimStage *guestAcceptIdle = new plAnimStage("BookAcceptIdle", plAnimStage::kNotifyEnter, plAnimStage::kForwardAuto, plAnimStage::kBackNone,
                                                           plAnimStage::kAdvanceNone, plAnimStage::kRegressNone, -1);
            
            brainG->AddStage(guestAccept);
            brainG->AddStage(guestAcceptIdle);
            plCoopCoordinator *coord = new plCoopCoordinator(hostKey, guestKey, brainH, brainG, _TEMP_CONVERT_FROM_LITERAL("Convergence"), 1, 1, linkMsg, true);


            plAvCoopMsg *coMg = new plAvCoopMsg(hostKey, coord);
            coMg->SetBCastFlag(plMessage::kNetPropagate);
            coMg->SetBCastFlag(plMessage::kNetForce);

            coMg->Send();
            brainH->SetRecipient(replyKey);
            brainG->SetRecipient(replyKey);

        }
    }
}
