/*==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 "plAvCallbackAction.h"     // havok-contaminated file: must go first

// singular
#include "plAvBrainGeneric.h"

// local
#include "plAnimStage.h"
#include "plArmatureMod.h"
// #include "plAvatarTasks.h"
#include "plAvTask.h"
#include "plAvTaskBrain.h"
#include "plAvBrainHuman.h"
#include "plAGAnimInstance.h"
#include "plMatrixChannel.h"

// global
#include "hsTimer.h"
#include "plgDispatch.h"

// other
#include "pnNetCommon/plSDLTypes.h"
#include "pnMessage/plCameraMsg.h"
#include "pnMessage/plNotifyMsg.h"
#include "plMessage/plAvatarMsg.h"
#include "plMessage/plInputEventMsg.h"
#include "plMessage/plSimStateMsg.h"
#include "plMessage/plConsoleMsg.h"
#include "plPipeline/plDebugText.h"
#include "plInputCore/plAvatarInputInterface.h"
#include "plMessage/plInputIfaceMgrMsg.h"

#ifdef DEBUG_MULTISTAGE
#include "plAvatarMgr.h"
#include "plStatusLog/plStatusLog.h"
#endif

hsBool plAvBrainGeneric::fForce3rdPerson = true;
const hsScalar plAvBrainGeneric::kDefaultFadeIn = 6.f; // 1/6th of a second to fade in
const hsScalar plAvBrainGeneric::kDefaultFadeOut = 0.f; // instant fade out.

// plAvBrainGeneric ----------------
// -----------------
plAvBrainGeneric::plAvBrainGeneric()
: fRecipient(nil),
  fStages(TRACKED_NEW plAnimStageVec),
  fCurStage(0),
  fType(kGeneric),
  fExitFlags(kExitNormal),
  fMode(kEntering),
  fForward(true),
  fStartMessage(nil),
  fEndMessage(nil),
  fFadeIn(0.0f),
  fFadeOut(0.0f),
  fMoveMode(kMoveRelative),
  fCallbackAction(nil),
  fBodyUsage(plAGAnim::kBodyUnknown)
{
}

// plAvBrainGeneric --------------------------------------
// -----------------
plAvBrainGeneric::plAvBrainGeneric(plAnimStageVec *stages,
                                   plMessage *startMessage,
                                   plMessage *endMessage,
                                   plKey recipient,
                                   UInt32 exitFlags,
                                   float fadeIn,
                                   float fadeOut,
                                   MoveMode moveMode)
: plArmatureBrain(),
  fRecipient(recipient),
  fStages(stages),
  fCurStage(0),
  fType(kGeneric),
  fExitFlags(exitFlags),
  fMode(kEntering),
  fForward(true),
  fStartMessage(startMessage),
  fEndMessage(endMessage),
  fFadeIn(fadeIn),
  fFadeOut(fadeOut),
  fMoveMode(moveMode),
  fCallbackAction(nil),
  fBodyUsage(plAGAnim::kBodyUnknown)
{
}

// plAvBrainGeneric 
plAvBrainGeneric::plAvBrainGeneric(UInt32 exitFlags, float fadeIn, float fadeOut, MoveMode moveMode)
: fRecipient(nil),
  fStages(nil),
  fCurStage(0),
  fType(kGeneric),
  fExitFlags(exitFlags),
  fMode(kEntering),
  fForward(true),
  fStartMessage(nil),
  fEndMessage(nil),
  fFadeIn(fadeIn),
  fFadeOut(fadeOut),
  fMoveMode(moveMode),
  fCallbackAction(nil),
  fBodyUsage(plAGAnim::kBodyUnknown)
{
    
}


// ~plAvBrainGeneric ----------------
// ------------------
plAvBrainGeneric::~plAvBrainGeneric()
{
    int fNumStages = fStages->size();

    for(int i = 0; i < fNumStages; i++)
    {
        plAnimStage *stage = (*fStages)[i];
        (*fStages)[i] = nil;
        stage->Detach(fAvMod);
        delete stage;
    }
    delete fStages;
}

// Activate -------------------------------------------
// ---------
void plAvBrainGeneric::Activate(plArmatureModBase *avMod)
{
    plArmatureBrain::Activate(avMod);

    if ((GetType() == kEmote || GetType() == kAFK || GetType() == kSitOnGround) && fAvMod->IsLocalAvatar())
    {
        plInputIfaceMgrMsg* msg = TRACKED_NEW plInputIfaceMgrMsg(plInputIfaceMgrMsg::kDisableClickables );
        plgDispatch::MsgSend(msg);
    }

    int numStages = fStages->size();
    if (!numStages)
        return; 
    plAnimStage *stage = (*fStages)[fCurStage];

    bool useFadeIn = fFadeIn > 0.0f;
    float initialBlend = useFadeIn ? 0.0f : 1.0f;

    if (GetType() == kEmote)
        ((plArmatureMod*)avMod)->SendBehaviorNotify(plHBehavior::kBehaviorTypeEmote,true);
    double worldTime = hsTimer::GetSysSeconds();

    if (fMoveMode == kMoveRelative || fMoveMode == kMoveAbsolute)
    {
        // enable kinematic... ignore outside forces... but still collide with detector regions
        fAvMod->EnablePhysicsKinematic( true );
    }
    else if(fMoveMode == kMoveStandstill)
    {
        // Avatar stands still automatically now, so we do nothing here
    }
    if (stage->Attach(fAvMod, this, initialBlend, worldTime))
    {
        if(fStartMessage)
        {
            fStartMessage->Send();
            fStartMessage = nil;
        }
        
        if (plAvBrainGeneric::fForce3rdPerson && fAvMod->IsLocalAvatar())
        {
            // create message to force 3rd person mode
            plCameraMsg* pMsg = TRACKED_NEW plCameraMsg;
            pMsg->SetBCastFlag(plMessage::kBCastByExactType);
            pMsg->SetCmd(plCameraMsg::kResponderSetThirdPerson);
            pMsg->SetBCastFlag(plMessage::kNetPropagate, false);
            plgDispatch::MsgSend( pMsg );   // whoosh... off it goes
        }
        
    }

    if (fType == kLadder && fAvMod->IsLocalAvatar())
        plAvatarInputInterface::GetInstance()->SetLadderMode(); 

    if (fReverseFBControlsOnRelease)
        fAvMod->SetReverseFBOnIdle(true);
}

hsBool plAvBrainGeneric::IsRunningTask()
{
    if ( fStages->size() > 0 )
        return true;
    return false;
}

bool plAvBrainGeneric::MatchAnimNames(const char *names[], int count)
{
    if (count != GetStageCount())
        return false;

    int i;
    for (i = 0; i < count; i++)
    {
        if (strcmp(names[i], GetStage(i)->GetAnimName()))
            return false; // different names.
    }

    return true;
}

// Apply ----------------------------------------------------
// ------
hsBool plAvBrainGeneric::Apply(double time, hsScalar elapsed)
{
    hsBool result = false;

    switch(fMode)
    {
    case kAbort:
        break;
    case kEntering:
    case kFadingIn:
        result = IProcessFadeIn(time, elapsed);
        break;
    case kExit:
    case kFadingOut:
        // go through the fade logic whether or not we actually need a fade;
        // centralizes some exit conditions.
        result = IProcessFadeOut(time, elapsed);
        break;
    case kNormal:
        result = IProcessNormal(time, elapsed);
        break;
    }
    plArmatureBrain::Apply(time, elapsed);
    return result;
}

// Deactivate -----------------------
// -----------
void plAvBrainGeneric::Deactivate()
{
    if (fEndMessage)
    {
        fEndMessage->Send();
        fEndMessage = nil;
    }
    if (fMode != kAbort)        // we're being forcibly removed...
        IExitMoveMode();

    if (fMoveMode == kMoveRelative || fMoveMode == kMoveAbsolute) 
    {
        // re-enable normal physics... outside forces affect us
        fAvMod->EnablePhysicsKinematic( false );
    } 
    else if (fMoveMode == kMoveStandstill) 
    {
        // Avatar stands still automaticaly now, so we do nothing here
    }

    if (fType == plAvBrainGeneric::kLadder && fAvMod->IsLocalAvatar())
    {
        plAvatarInputInterface::GetInstance()->ClearLadderMode();
    }           
    
    if (fReverseFBControlsOnRelease)
        fAvMod->SetReverseFBOnIdle(false);
    
    if (plAvBrainGeneric::fForce3rdPerson && fAvMod->IsLocalAvatar())
    {
        // create message to force 3rd person mode
        plCameraMsg* pMsg = TRACKED_NEW plCameraMsg;
        pMsg->SetBCastFlag(plMessage::kBCastByExactType);
        pMsg->SetBCastFlag(plMessage::kNetPropagate, false);
        pMsg->SetCmd(plCameraMsg::kResponderUndoThirdPerson);
        plgDispatch::MsgSend( pMsg );   // whoosh... off it goes
    }
        
    plArmatureBrain::Deactivate();

    if ((GetType() == kEmote || GetType() == kAFK || GetType() == kSitOnGround) && fAvMod->IsLocalAvatar())
    {
        plInputIfaceMgrMsg* msg = TRACKED_NEW plInputIfaceMgrMsg(plInputIfaceMgrMsg::kEnableClickables );
        plgDispatch::MsgSend(msg);
    }
}

// GETRECIPIENT
plKey plAvBrainGeneric::GetRecipient()
{
    return fRecipient;
}

// SETRECIPIENT
void plAvBrainGeneric::SetRecipient(const plKey &recipient)
{
    fRecipient = recipient;
}

// RELAYNOTIFYMSG
bool plAvBrainGeneric::RelayNotifyMsg(plNotifyMsg *msg)
{
    if(fRecipient)
    {
        msg->AddReceiver(fRecipient);
        msg->Send();
        return true;
    } else {
        return false;
    }
}

// IGetAnimDelta ------------------------------------------------
// --------------
float plAvBrainGeneric::IGetAnimDelta(double time, float elapsed)
{
    float delta = 0.0f;
    plAnimStage *curStage = (*fStages)[fCurStage];
    plAnimStage::ForwardType forward = curStage->GetForwardType();
    plAnimStage::BackType back = curStage->GetBackType();
    bool fwdIsDown = (fReverseFBControlsOnRelease && fAvMod->IsFBReversed()) ? fAvMod->BackwardKeyDown() : fAvMod->ForwardKeyDown();
    hsBool backIsDown = (fReverseFBControlsOnRelease && fAvMod->IsFBReversed()) ? fAvMod->ForwardKeyDown() : fAvMod->BackwardKeyDown();

    // forward with a key down gets top priority
    if(forward == plAnimStage::kForwardKey && fwdIsDown)
    {
        // key drive forward, forward key is down
        delta = elapsed;
        fForward = true;
    } else if(back == plAnimStage::kBackKey && backIsDown)
    {
        // key drive back, back key is down
        delta = -elapsed;
        fForward = false;
    } else if (forward == plAnimStage::kForwardAuto && fForward)
    {
        // auto drive forward
        delta = elapsed;
    } else if (back == plAnimStage::kBackAuto && ! fForward)
    {
        // auto drive backward
        delta = -elapsed;
    } 
    return delta;
}

// IProcessNormal -------------------------------------------------
// ---------------
hsBool plAvBrainGeneric::IProcessNormal(double time, float elapsed)
{
    plAnimStage *curStage = (*fStages)[fCurStage];
    if(curStage)
    {
        float animDelta = IGetAnimDelta(time, elapsed);     // how far to move the anim (may be negative)
        float overage;
        hsBool done = curStage->MoveRelative(time, animDelta, overage, fAvMod);

        if(done)
        {
            bool forward = animDelta > 0.0f;
            int nextStage = forward ? curStage->GetNextStage(fCurStage) : curStage->GetPrevStage(fCurStage);

            if((nextStage == -1) || (nextStage >= fStages->size()))
            {
                // ran off one end; we're done.
                fMode = kExit;
            } else {
                ISwitchStages(fCurStage, nextStage, overage, false, 0.0f, 1.0f, -1.0f, time);
            }
        }

        return true;
    } else {
        // current stage is missing; abort
        return false;
    }
}

// IProcessFadeIn -------------------------------------------------
// ---------------
hsBool plAvBrainGeneric::IProcessFadeIn(double time, float elapsed)
{
    plAnimStage *curStage = (*fStages)[fCurStage];

    if(fMode != kFadingIn)
    {
        bool needFade = fFadeIn != 0.0f;

        if(fFadeIn == 0.0f)
        {
            IEnterMoveMode(time);   // if fadeIn's not zero, we have to wait until fade's done
                                            // before animating
        } else {
            curStage->GetAnimInstance()->Fade(1.0f, fFadeIn);
        }
        fMode = kFadingIn;
    } else {
        float curBlend = curStage->GetAnimInstance()->GetBlend();
        if(curBlend == 1.0f)
        {
            IEnterMoveMode(time);
        }
    }
    return true;
}

// IProcessFadeOut -------------------------------------------------
// ----------------
hsBool plAvBrainGeneric::IProcessFadeOut(double time, float elapsed)
{
    plAnimStage *curStage = (*fStages)[fCurStage];

    if(fMode != kFadingOut)
    {
        // haven't actually started fading; see if we need to
        if(fFadeOut > 0.0f)
        {
            plAGAnimInstance *curAnim = curStage->GetAnimInstance();
            if(curAnim)
            {
                curStage->GetAnimInstance()->Fade(0.0f, fFadeOut);  
                IExitMoveMode();
                fMode = kFadingOut;
            } else {
                fMode = kAbort;
                return false;
            }
        } else {
            curStage->Detach(fAvMod);
            IExitMoveMode();
            fMode = kAbort;
        }
    } else {
        // already fading; just keeping looking for the anim to zero out.
        float curBlend = curStage->GetAnimInstance()->GetBlend();
        if(curBlend == 0.0f)
        {
            curStage->Detach(fAvMod);
            fMode = kAbort;
        }
    }
    return true;
}

// ISwitchStages ---------------------------------------------------------------------------------------------------
// --------------
hsBool plAvBrainGeneric::ISwitchStages(int oldStageNum, int newStageNum, float delta, hsBool setTime, float newTime,
                                       float fadeNew, hsScalar fadeOld, double worldTime)
{
#ifdef DEBUG_MULTISTAGE
    char sbuf[256];
    sprintf(sbuf,"ISwitchStage - old=%d new=%d (fCurStage=%d)",oldStageNum,newStageNum,fCurStage);
    plAvatarMgr::GetInstance()->GetLog()->AddLine(sbuf);
#endif
    if(oldStageNum != newStageNum) {
        plAnimStage *newStage = fStages->at(newStageNum);
        plAnimStage *oldStage = fStages->at(oldStageNum);
        if(setTime)
            newStage->SetLocalTime(newTime);

        hsAssert(oldStageNum < fStages->size(), "PLAVBRAINGENERIC: Stage out of range.");

        oldStage->Detach(fAvMod);
        newStage->Attach(fAvMod, this, 1.0f, worldTime);

        fCurStage = newStageNum;
        fAvMod->DirtySynchState(kSDLAvatar, 0);     // write our new stage to the server
    }
    if(setTime) {
        plAnimStage *curStage = fStages->at(fCurStage);
        curStage->SetLocalTime(newTime);
    }

    if(fMoveMode == kMoveRelative)
        fAvMod->GetRootAnimator()->Reset(worldTime);
    return true;
}

void plAvBrainGeneric::IEnterMoveMode(double time)
{
    if(fMoveMode == kMoveRelative)
    {
        fAvMod->GetRootAnimator()->Enable(true);
        fAvMod->GetRootAnimator()->Reset(time);
    }
    fMode = kNormal;
}

void plAvBrainGeneric::IExitMoveMode()
{
    if(fAvMod)
    {
        if(fMoveMode == kMoveRelative)
        {
            if(fAvMod->GetRootAnimator())
                fAvMod->GetRootAnimator()->Enable(false);
        }

        if (fFadeOut == 0.f)
        {
            // if we're exiting instantly (no fade out) then the end of the animation expects to line up with
            // the first frame of the idle animation, so we need to reset it.
            plAvBrainHuman *brain = plAvBrainHuman::ConvertNoRef(fAvMod->FindBrainByClass(plAvBrainHuman::Index()));
            if (brain)
                brain->ResetIdle();
        }
    }
}

/////////////////////////////////////////////////////////////////////////////////////////
//
// MESSAGE HANDLING
//
/////////////////////////////////////////////////////////////////////////////////////////

// MsgReceive -------------------------------------
// -----------
hsBool plAvBrainGeneric::MsgReceive(plMessage *msg)
{
    hsBool result = false;

    plAvBrainGenericMsg *genMsg = plAvBrainGenericMsg::ConvertNoRef(msg);
    //plAvExitModeMsg *exitMsg = plAvExitModeMsg::ConvertNoRef(msg);
    plAvTaskMsg *taskMsg = plAvTaskMsg::ConvertNoRef(msg);
    plControlEventMsg *ctrlMsg = plControlEventMsg::ConvertNoRef(msg);
    

    if(genMsg)
    {
        result = IHandleGenBrainMsg(genMsg);
    } 
//  else if(exitMsg) {
//      fMode = kExit;
//      result = true;
//  } 
    else if (taskMsg) {
        result =  IHandleTaskMsg(taskMsg);
    } 
    else if (ctrlMsg && (fExitFlags & kExitAnyInput) ) {
        fMode = kExit;
    }

    if(result == false)                                     // if still haven't handled msg
    {
        if(fMode == kExit)                                      // if we're exiting
        {
            result = fAvMod->GetNextBrain(this)->MsgReceive(msg);       // pass msg to next brain
        } else {                                                // otherwise
            result = plArmatureBrain::MsgReceive(msg);              // pass msg to base class
        }
    }
    
    return result;
}

// IHandleGenBrainMsg -----------------------------------------------------
// -------------------
hsBool plAvBrainGeneric::IHandleGenBrainMsg(const plAvBrainGenericMsg *msg)
{
    hsBool setTime = msg->fSetTime;
    float newTime = msg->fNewTime;
    hsBool setDirection = msg->fSetDirection;
    bool newDirection = msg->fNewDirection ? true : false;
    double worldTime = hsTimer::GetSysSeconds();

    switch(msg->fType)
    {
    case plAvBrainGenericMsg::kGotoStage:
        {
            int wantStage = msg->fWhichStage;
#ifdef DEBUG_MULTISTAGE
            char sbuf[256];
            sprintf(sbuf,"GenericMsg - Goto Stage %d (oldstage %d)",wantStage,fCurStage);
            plAvatarMgr::GetInstance()->GetLog()->AddLine(sbuf);
#endif
            if(wantStage == -1) {
                fMode = kExit;
            } else {
                int count = fStages->size();
                if(wantStage < count && wantStage >= 0)
                {
                    ISwitchStages(fCurStage, wantStage, 0.0f, setTime, newTime, 1.0f, -1.0f, worldTime);
                    // direction is set within the brain, not the stage
                    if(setDirection)
                        fForward = newDirection;
                }
            }
        }
        break;
    case plAvBrainGenericMsg::kNextStage:
        {
            int wantStage = fCurStage + 1;
#ifdef DEBUG_MULTISTAGE
            char sbuf[256];
            sprintf(sbuf,"GenericMsg - Next Stage %d (oldstage %d)",wantStage,fCurStage);
            plAvatarMgr::GetInstance()->GetLog()->AddLine(sbuf);
#endif
            if(wantStage == fStages->size())
            {
                fMode = kExit;  // walked off the end of the brain
            } else {
                ISwitchStages(fCurStage, wantStage, 0.0f, setTime, newTime, 1.0f, -1.0f, worldTime);
                if(setDirection)
                    fForward = newDirection;
            }
        }
        break;
    case plAvBrainGenericMsg::kPrevStage:
        {
            int wantStage = fCurStage - 1;
#ifdef DEBUG_MULTISTAGE
            char sbuf[256];
            sprintf(sbuf,"GenericMsg - PrevStage %d (oldstage %d)",wantStage,fCurStage);
            plAvatarMgr::GetInstance()->GetLog()->AddLine(sbuf);
#endif
            if(wantStage < 0)
            {
                fMode = kExit;  // walked off the beginning of the brain
            } else {
                ISwitchStages(fCurStage, wantStage, 0.0f, setTime, 0.0f, 1.0f, -1.0f, worldTime);
                if(setDirection)
                    fForward = newDirection;
            }
        }
        break;
#ifdef DEBUG_MULTISTAGE
    default:
        {
            char sbuf[256];
            sprintf(sbuf,"GenericMsg - Unknown command %d ",msg->fType);
            plAvatarMgr::GetInstance()->GetLog()->AddLine(sbuf);
        }
        break;
#endif
    }
    return true;
}

hsBool plAvBrainGeneric::IHandleTaskMsg(plAvTaskMsg *msg)
{
    plAvTask *task = msg->GetTask();
    plAvTaskBrain *brainTask = plAvTaskBrain::ConvertNoRef(task);

    if(brainTask)
    {
        plArmatureBrain * brain = brainTask->GetBrain();

        if(brain)
        {
            if(fExitFlags & kExitNewBrain)
            {
                // RULE 1: if kExitNewBrain, exit on any new brain
                fMode = kExit;
                return false;       // we didn't consume the message
            } else {
                plAvBrainGeneric * gBrain = plAvBrainGeneric::ConvertNoRef(brain);

                if(gBrain && IBrainIsCompatible(gBrain))
                {
                    // RULE 2: if not kExitNewBrain and brain is compatible, apply it
                    QueueTask(brainTask);
                    return true;
                } else {
                    if(fMode == kExit || fMode == kFadingOut)
                    {
                        // RULE 3: if brain is incompatible and we're exiting anyway,
                        // queue it to be next
                        fAvMod->GetNextBrain(this)->QueueTask(brainTask);
                        return true;
                    }
                    // RULE 4: if brain is incompatible and we're still running, ignore.
                }
            }
        } else {
            // no brain; it's an exit task, exit and CONSUME it.
            fMode = kExit;
            return true;
        }
    } else {
        // note that this check has to come after the brain task check; if it's a brain
        // task we need to examine it so we can say whether we consumed it or not.
        // popbrain messages get consumed, even if we exit on any task.
        if(fExitFlags & kExitAnyTask)
        {
            // RULE 4: if kExitAnyTask, exit on any task (but if it was an exit brain task,
            //          make sure to consume it )
            fMode = kExit;
            return false;
        }
    }

    // RULE 4: if brain is incompatible and we're still running, ignore.    
    return false;
}

bool plAvBrainGeneric::IBrainIsCompatible(plAvBrainGeneric *otherBrain)
{
    plAGAnim::BodyUsage otherUsage = otherBrain->GetBodyUsage();

    switch(fBodyUsage)
    {
    case plAGAnim::kBodyUnknown:
        return false;
        break;
    case plAGAnim::kBodyFull:
        return false;
        break;
    case plAGAnim::kBodyUpper:
        if(otherUsage == plAGAnim::kBodyLower)
            return true;
        else
            return false;
        break;
    case plAGAnim::kBodyLower:
        if(otherUsage == plAGAnim::kBodyUpper)
            return true;
        else
            return false;
        break;
    }
    return false;
}

/////////////////////////////////////////////////////////////////////////////////////////
//
// READ/WRITE
//
/////////////////////////////////////////////////////////////////////////////////////////

// Write ----------------------------------------------------
// ------
void plAvBrainGeneric::Write(hsStream *stream, hsResMgr *mgr)
{
    plArmatureBrain::Write(stream, mgr);
    int numStages = fStages->size();
    stream->WriteSwap32(numStages);

    for(int i = 0; i < numStages; i++)
    {
        plAnimStage *stage = (*fStages)[i];
        plCreatable *cre = reinterpret_cast<plCreatable *>(stage);
        mgr->WriteCreatable(stream, cre);       // save base state
        // ** replace this with Write(..)
        stage->SaveAux(stream, mgr);            // save ephemeral state 
    }

    stream->WriteSwap32(fCurStage);
    stream->WriteSwap32(fType);
    stream->WriteSwap32(fExitFlags);
    stream->WriteByte(fMode);
    stream->Writebool(fForward);

    if(fStartMessage) {
        stream->WriteBool(true);
        mgr->WriteCreatable(stream, fStartMessage);
    } else {
        stream->WriteBool(false);
    }

    if(fEndMessage) {
        stream->WriteBool(true);
        mgr->WriteCreatable(stream, fEndMessage);
    } else {
        stream->WriteBool(false);
    }

    stream->WriteSwapScalar(fFadeIn);
    stream->WriteSwapScalar(fFadeOut);
    stream->WriteByte(fMoveMode);
    stream->WriteByte(fBodyUsage);
    mgr->WriteKey(stream, fRecipient);
}

// Read ----------------------------------------------------
// -----
void plAvBrainGeneric::Read(hsStream *stream, hsResMgr *mgr)
{
    plArmatureBrain::Read(stream, mgr);
    int numStages = stream->ReadSwap32();

    for(int i = 0; i < numStages; i++)
    {
        plCreatable *created = mgr->ReadCreatable(stream);              // load base state
        plAnimStage *stage = reinterpret_cast<plAnimStage *>(created);
        // Replace this with Read(..)
        stage->LoadAux(stream, mgr, 0.0);                               // load ephemeral state

        fStages->push_back(stage);
    }

    fCurStage = stream->ReadSwap32();
    fType = static_cast<plAvBrainGeneric::BrainType>(stream->ReadSwap32());
    fExitFlags = stream->ReadSwap32();
    fMode = static_cast<Mode>(stream->ReadByte());
    fForward = stream->Readbool();

    if(stream->ReadBool()) {
        fStartMessage = plMessage::ConvertNoRef(mgr->ReadCreatable(stream));
    } else {
        fStartMessage = nil;
    }
    if(stream->ReadBool()) {
        fEndMessage = plMessage::ConvertNoRef(mgr->ReadCreatable(stream));
    } else {
        fEndMessage = nil;
    }

    fFadeIn = stream->ReadSwapScalar();
    fFadeOut = stream->ReadSwapScalar();
    fMoveMode = static_cast<MoveMode>(stream->ReadByte());
    fBodyUsage = static_cast<plAGAnim::BodyUsage>(stream->ReadByte());
    fRecipient = mgr->ReadKey(stream);
}

/////////////////////////////////////////////////////////////////////////////////////////
//
// MINOR FNs / GETTERS & SETTERS
//
/////////////////////////////////////////////////////////////////////////////////////////

// LeaveAge ---------------------
// ---------
hsBool plAvBrainGeneric::LeaveAge()
{
    IExitMoveMode();

    fMode = kAbort;
    return true;
}

// AddStage --------------------------------------
// ---------
int plAvBrainGeneric::AddStage(plAnimStage *stage)
{
    if(!fStages)
        fStages = TRACKED_NEW plAnimStageVec;
    fStages->push_back(stage);
    return fStages->size() - 1;
}

// GetStageNum --------------------------------------
// ------------
int plAvBrainGeneric::GetStageNum(plAnimStage *stage)
{
    int count = fStages->size();
    for(int i = 0; i < count; i++)
    {
        plAnimStage *any = (*fStages)[i];
        if(any == stage)
        {
            return i;
        }
    }
    return -1;
}

// GetCurStageNum --------------------
// ---------------
int plAvBrainGeneric::GetCurStageNum()
{
    return fCurStage;
}

// GetStageCount --------------------
// --------------
int plAvBrainGeneric::GetStageCount()
{
    return fStages->size();
}

// GetStage ---------------------------------------
// ---------
plAnimStage * plAvBrainGeneric::GetStage(int which)
{
    return fStages->at(which);
}

// GetCurStage ------------------------------
// ------------
plAnimStage * plAvBrainGeneric::GetCurStage()
{
    return fStages->at(fCurStage);
}

// SetType -------------------------------------------------------------------------------
// --------
plAvBrainGeneric::BrainType plAvBrainGeneric::SetType(plAvBrainGeneric::BrainType newType)
{
    BrainType oldType = fType;
    fType = newType;
    return oldType;
}

// GetType --------------------------------------------
// --------
plAvBrainGeneric::BrainType plAvBrainGeneric::GetType()
{
    return fType;
}

plAGAnim::BodyUsage plAvBrainGeneric::GetBodyUsage()
{
    return fBodyUsage;
}

void plAvBrainGeneric::SetBodyUsage(plAGAnim::BodyUsage bodyUsage)
{
    fBodyUsage = bodyUsage;
}


/////////////////////////////////////////////////////////////////////////////////////////
//
// DEBUGGING
//
/////////////////////////////////////////////////////////////////////////////////////////

// DumpToDebugDisplay ----------------------------------------------------------------------------------------
// -------------------
void plAvBrainGeneric::DumpToDebugDisplay(int &x, int &y, int lineHeight, char *strBuf, plDebugText &debugTxt)
{
    debugTxt.DrawString(x, y, "Brain type: Generic AKA Multistage");
    y += lineHeight;

    int stageCount = fStages->size();
    for(int i = 0; i < stageCount; i++)
    {
        plAnimStage *stage = (*fStages)[i];
        stage->DumpDebug(i == fCurStage, x, y, lineHeight, strBuf, debugTxt);
    }
}