/*==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 "plExcludeRegionModifier.h"
#include "plMessage/plExcludeRegionMsg.h"
#include "hsTemplates.h"
#include "hsResMgr.h"
#include "pnSceneObject/plCoordinateInterface.h"
#include "plDetectorLog.h"
// For MsgReceive
#include "plMessage/plCollideMsg.h"
#include "pnSceneObject/plSceneObject.h"

// For IClear and IRelease
#include "pnMessage/plWarpMsg.h"
#include "plMessage/plAvatarMsg.h"
#include "plPhysical.h"
#include "plPhysical/plSimDefs.h"
#include "plAvatar/plAvCallbackAction.h"

#include "plAvatar/plAvBrainGeneric.h"

#include "plSDL/plSDL.h"
#include "pnMessage/plSDLModifierMsg.h"
//for hack
#include "plPhysX/plPXPhysical.h"
#include "plPhysX/plPXPhysicalControllerCore.h"
#include <NxCapsule.h>
static plPhysical* GetPhysical(plSceneObject* obj)
{
    if (obj)
    {
        const plSimulationInterface* si = obj->GetSimulationInterface();
        if (si)
            return si->GetPhysical();
    }
    return nil;
}

plExcludeRegionModifier::plExcludeRegionModifier() :
    fSDLModifier(nil),
    fSeek(false),
    fSeekTime(10.0f)
{
}


plExcludeRegionModifier::~plExcludeRegionModifier()
{
}

void plExcludeRegionModifier::Read(hsStream* stream, hsResMgr* mgr)
{
    plSingleModifier::Read(stream, mgr);

    int numPoints = stream->ReadSwap32();
    for (int i = 0; i < numPoints; i++)
    {
        fSafePoints.push_back(mgr->ReadKey(stream));
    }
    fSeek = stream->ReadBool();
    fSeekTime = stream->ReadSwapScalar();
}

void plExcludeRegionModifier::Write(hsStream* stream, hsResMgr* mgr)
{
    plSingleModifier::Write(stream, mgr);

    int numPoints = fSafePoints.size();
    stream->WriteSwap32(numPoints);
    for (int i = 0; i < numPoints; i++)
    {
        mgr->WriteKey(stream,fSafePoints[i]);
    }
    stream->WriteBool(fSeek);
    stream->WriteSwapScalar(fSeekTime);
}

void plExcludeRegionModifier::ISetPhysicalState(bool cleared)
{
    plPhysical* phys = GetPhysical(GetTarget());
    if (phys)
    {
        phys->ExcludeRegionHack(cleared);

        if (cleared)
        {
            phys->AddLOSDB(plSimDefs::kLOSDBUIBlockers);
            if (HasFlag(kBlockCameras))
                phys->AddLOSDB(plSimDefs::kLOSDBCameraBlockers);
        }
        else
        {
            phys->RemoveLOSDB(plSimDefs::kLOSDBUIBlockers);
            if (HasFlag(kBlockCameras))
                phys->RemoveLOSDB(plSimDefs::kLOSDBCameraBlockers);
        }
    }
}

hsBool plExcludeRegionModifier::MsgReceive(plMessage* msg)
{
    plExcludeRegionMsg *exclMsg = plExcludeRegionMsg::ConvertNoRef(msg);
    if (exclMsg)
    {
        if (exclMsg->GetCmd() == plExcludeRegionMsg::kClear)
        {
            DetectorLogSpecial("Clearing exclude region %s", GetKeyName());
            IMoveAvatars();
            fContainedAvatars.Reset();
            ISetPhysicalState(true);
        }
        else if (exclMsg->GetCmd() == plExcludeRegionMsg::kRelease)
        {
            DetectorLogSpecial("Releasing exclude region %s", GetKeyName());
            ISetPhysicalState(false);
        }

        GetTarget()->DirtySynchState(kSDLXRegion, exclMsg->fSynchFlags);
    
        return true;
    }

    // Avatar entering or exiting our volume.  Only the owner of the SO logs this since
    // it does all the moving at clear time.
    plCollideMsg *collideMsg = plCollideMsg::ConvertNoRef(msg);
    
    if (collideMsg)
    {
        if (collideMsg->fEntering)
        {
            //DetectorLogSpecial("Avatar enter exclude region %s",GetKeyName());
            fContainedAvatars.Append(collideMsg->fOtherKey);
        }
        else
        {
            //DetectorLogSpecial("Avatar exit exclude region %s",GetKeyName());
            int idx = fContainedAvatars.Find(collideMsg->fOtherKey);
            if (idx != fContainedAvatars.kMissingIndex)
                fContainedAvatars.Remove(idx);
        }

        return true;
    }

    return plSingleModifier::MsgReceive(msg);
}

void plExcludeRegionModifier::AddTarget(plSceneObject* so)
{
    if (so)
    {
        delete fSDLModifier;
        fSDLModifier = TRACKED_NEW plExcludeRegionSDLModifier(this);
        so->AddModifier(fSDLModifier);
    }
    plSingleModifier::SetTarget(so);
}

void plExcludeRegionModifier::RemoveTarget( plSceneObject *so )
{
    if (so && fSDLModifier)
    {
        so->RemoveModifier(fSDLModifier);
        delete fSDLModifier;
        fSDLModifier = nil;
    }
    plSingleModifier::RemoveTarget(so);
}

void plExcludeRegionModifier::AddSafePoint(plKey& key)
{
    fSafePoints.push_back(key);
}

int plExcludeRegionModifier::IFindClosestSafePoint(plKey avatar)
{
    float closestDist = 0.f;
    int closestIdx = -1;

    plSceneObject* avObj = plSceneObject::ConvertNoRef(avatar->GetObjectPtr());
    if (!avObj)
        return -1;

    hsVector3 avPos;
    avObj->GetCoordinateInterface()->GetLocalToWorld().GetTranslate(&avPos);

    for (int i = 0; i < fSafePoints.size(); i++)
    {
        plSceneObject* safeObj = plSceneObject::ConvertNoRef(fSafePoints[i]->GetObjectPtr());
        hsVector3 safePos;
        safeObj->GetCoordinateInterface()->GetLocalToWorld().GetTranslate(&safePos);

        float dist = (safePos - avPos).Magnitude();

        if (dist < closestDist || closestIdx == -1)
        {
            closestDist = dist;
            closestIdx = i;
        }
    }

    return closestIdx;
}

// check to make sure that the avatar and the exclude region are in the same subworld
bool plExcludeRegionModifier::ICheckSubworlds(plKey avatar)
{
    plSceneObject* avObj = plSceneObject::ConvertNoRef(avatar->GetObjectPtr());
    if (avObj)
    {
        // get the avatar modifier
        const plArmatureMod *avMod = (plArmatureMod*)avObj->GetModifierByType(plArmatureMod::Index());
        if (avMod)
        {
            // get the avatar controller
            plPhysicalControllerCore* avController = avMod->GetController();
            if (avController)
            {
                // get physical of the detector region
                plPhysical* phys = GetPhysical(GetTarget());
                if (phys)
                {
                    // are they in the same subworld?
                    if ( phys->GetWorldKey() == avController->GetSubworld() )
                        return true;
                    else
                        return false;
                }
            }
        }
    }
    return false;
}

// Move avatars out of volume
void plExcludeRegionModifier::IMoveAvatars()
{
    //some reason this is not always finding all avatars might have something to do with
    //Physx trigger flutter. Adding in brute force method
    /*
    for (int i = 0; i < fContainedAvatars.Count(); i++)
    {
        if ( ICheckSubworlds(fContainedAvatars[i]) )
        {
            int closestIdx = IFindClosestSafePoint(fContainedAvatars[i]);

            if (closestIdx != -1)
            {
                plAvSeekMsg* msg = TRACKED_NEW plAvSeekMsg;
                msg->SetBCastFlag(plMessage::kPropagateToModifiers);
                msg->AddReceiver(fContainedAvatars[i]);
                msg->fSmartSeek = fSeek;
                msg->fDuration = fSeekTime;
                msg->fSeekPoint = fSafePoints[closestIdx];
                msg->fFlags |= plAvSeekMsg::kSeekFlagUnForce3rdPersonOnFinish;
                msg->Send();
            }
        }
    }
    */
    
    plPXPhysical* phys =(plPXPhysical*) GetPhysical(GetTarget());
    if (phys)
    {
        plKey DetectorWorldKey = phys->GetWorldKey();
        int numControllers = plPXPhysicalControllerCore::GetNumberOfControllersInThisSubWorld(phys->GetWorldKey());
        if (numControllers > 0)
        {
            plPXPhysicalControllerCore** controllers = TRACKED_NEW plPXPhysicalControllerCore*[numControllers];

            int actualCount = plPXPhysicalControllerCore::GetControllersInThisSubWorld(phys->GetWorldKey(), numControllers, controllers);
            
            for (int i=0;i<actualCount;i++)
            {   
                NxCapsule cap;
                controllers[i]->GetWorldSpaceCapsule(cap);
                if(phys->OverlapWithCapsule(cap))
                {
                    plSceneObject* so = plSceneObject::ConvertNoRef(controllers[i]->GetOwner()->ObjectIsLoaded());
                    const plArmatureMod* constAvMod = (plArmatureMod*)so->GetModifierByType(plArmatureMod::Index());
                    if(constAvMod)
                    {
                        plAvBrainGeneric *curGenBrain = (plAvBrainGeneric *)constAvMod->FindBrainByClass(plAvBrainGeneric::Index());
                        // *** warning; if there's more than one generic brain active, this will only look at the first
                        if (curGenBrain  && curGenBrain->GetType() == plAvBrainGeneric::kLadder)
                        {
                            plAvBrainGenericMsg* pMsg = TRACKED_NEW plAvBrainGenericMsg(
                                nil,
                                constAvMod->GetKey(),
                                plAvBrainGenericMsg::kGotoStage,
                                -1,
                                false,
                                0.0,
                                false,
                                false,
                                0.0
                            );
                            pMsg->Send();
                        }
                        else
                        {
                            int closestIdx = IFindClosestSafePoint(controllers[i]->GetOwner());
                            if (closestIdx != -1)
                            {
                                plAvSeekMsg* msg = TRACKED_NEW plAvSeekMsg;
                                msg->SetBCastFlag(plMessage::kPropagateToModifiers);
                                msg->AddReceiver(controllers[i]->GetOwner());
                                msg->fSmartSeek = fSeek;
                                msg->fDuration = fSeekTime;
                                msg->fSeekPoint = fSafePoints[closestIdx];
                                msg->fFlags |= plAvSeekMsg::kSeekFlagUnForce3rdPersonOnFinish;
                                msg->Send();
                            }
                        }
                    }
                }
            }

            delete[] controllers;
        }
    }
}


///////////////////////////////////////////////////////////////////////////////

plExcludeRegionSDLModifier::plExcludeRegionSDLModifier() : fXRegion(nil)
{
}

plExcludeRegionSDLModifier::plExcludeRegionSDLModifier(plExcludeRegionModifier* xregion) :
    fXRegion(xregion)
{
}

static const char* kXRegionSDLCleared = "cleared";

void plExcludeRegionSDLModifier::IPutCurrentStateIn(plStateDataRecord* dstState)
{
    plSimpleStateVariable* var = dstState->FindVar(kXRegionSDLCleared);
    if (var)
    {
        plPhysical* phys = GetPhysical(GetTarget());
        if (phys)
        {
            //bool cleared = phys->GetGroup() == plSimDefs::kGroupStatic;
            bool cleared = phys->GetGroup() == plSimDefs::kGroupExcludeRegion;
            var->Set(cleared);
        }
    }
}

void plExcludeRegionSDLModifier::ISetCurrentStateFrom(const plStateDataRecord* srcState)
{
    plSimpleStateVariable* var = srcState->FindVar(kXRegionSDLCleared);
    if (var)
    {
        bool cleared;
        var->Get(&cleared);

        DetectorLogSpecial("SDL %s exclude region %s", cleared ? "clearing" : "releasing", fXRegion->GetKeyName());
        fXRegion->ISetPhysicalState(cleared);
    }
}