/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.

// Description : Contains an agent's target tracks and handles updating them


#include "CryLegacy_precompiled.h"
#include "TargetTrackGroup.h"
#include "TargetTrackManager.h"
#include "TargetTrack.h"
#include "ObjectContainer.h"

#ifdef TARGET_TRACK_DOTARGETTHREAT
    #include "Puppet.h"
#endif //TARGET_TRACK_DOTARGETTHREAT

#ifdef TARGET_TRACK_DEBUG
    #include "DebugDrawContext.h"
    #include "IDebugHistory.h"
    #include "IGame.h"
    #include "IGameFramework.h"
#endif //TARGET_TRACK_DEBUG

//////////////////////////////////////////////////////////////////////////
CTargetTrackGroup::CTargetTrackGroup(TargetTrackHelpers::ITargetTrackPoolProxy* pTrackPoolProxy, tAIObjectID aiObjectId, uint32 uConfigHash, int nTargetLimit)
    : m_pTrackPoolProxy(pTrackPoolProxy)
    , m_aiObjectId(aiObjectId)
    , m_aiLastBestTargetId(0)
    , m_uConfigHash(uConfigHash)
    , m_nTargetLimit(max(nTargetLimit, 0))
    , m_bNeedSort(false)
    , m_bEnabled(true)
{
    assert(m_pTrackPoolProxy);
    assert(m_aiObjectId > 0);
    assert(m_uConfigHash > 0);

    CAISystem* pAISystem = GetAISystem();
    assert(pAISystem);

    CWeakRef<CAIObject> refAIObject = gAIEnv.pObjectContainer->GetWeakRef(aiObjectId);
    const bool bAIObjectValid = refAIObject.IsValid();

    // Create dummy representation for the dummy potential target
    //  NB: during serialization, dummy objects are created after loading to
    //  ensure we keep the same AI object id
    if (!gEnv->pSystem->IsSerializingFile())
    {
        gAIEnv.pAIObjectManager->CreateDummyObject(m_dummyPotentialTarget.refDummyRepresentation, "Target Track Dummy");
        if (bAIObjectValid)
        {
            InitDummy();
        }
    }

#ifdef TARGET_TRACK_DEBUG
    m_fLastGraphUpdate = 0.0f;
    m_pDebugHistoryManager = gEnv->pGame->GetIGameFramework()->CreateDebugHistoryManager();
    assert(m_pDebugHistoryManager);

    memset(m_bDebugGraphOccupied, 0, sizeof(m_bDebugGraphOccupied));
#endif //TARGET_TRACK_DEBUG
}

//////////////////////////////////////////////////////////////////////////
CTargetTrackGroup::~CTargetTrackGroup()
{
    Reset();

#ifdef TARGET_TRACK_DEBUG
    SAFE_RELEASE(m_pDebugHistoryManager);
#endif //TARGET_TRACK_DEBUG
}

//////////////////////////////////////////////////////////////////////////
void CTargetTrackGroup::Reset()
{
    FUNCTION_PROFILER(GetISystem(), PROFILE_AI);

    // Add all active tracks back to the pool
    TTargetTrackContainer::iterator itTrack = m_TargetTracks.begin();
    TTargetTrackContainer::iterator itTrackEnd = m_TargetTracks.end();
    for (; itTrack != itTrackEnd; ++itTrack)
    {
        CTargetTrack* pTrack = itTrack->second;
        assert(pTrack);

        m_pTrackPoolProxy->AddTargetTrackToPool(pTrack);
    }

    m_TargetTracks.clear();
    m_SortedTracks.clear();
    m_bNeedSort = false;

#ifdef TARGET_TRACK_DEBUG
    m_pDebugHistoryManager->Clear();
#endif //TARGET_TRACK_DEBUG
}

//////////////////////////////////////////////////////////////////////////
void CTargetTrackGroup::Serialize_Write(TSerialize ser)
{
    assert(ser.IsWriting());

    int iTrackCount = m_TargetTracks.size();
    ser.Value("iTrackCount", iTrackCount);

    TTargetTrackContainer::iterator itTrack = m_TargetTracks.begin();
    TTargetTrackContainer::iterator itTrackEnd = m_TargetTracks.end();
    for (; itTrack != itTrackEnd; ++itTrack)
    {
        tAIObjectID targetId = itTrack->first;
        CTargetTrack* pTrack = itTrack->second;
        assert(targetId > 0 && pTrack);

        ser.BeginGroup("Track");
        {
            ser.Value("targetId", targetId);

            pTrack->Serialize(ser);
        }
        ser.EndGroup();
    }

    m_dummyPotentialTarget.Serialize(ser);
}

//////////////////////////////////////////////////////////////////////////
void CTargetTrackGroup::Serialize_Read(TSerialize ser)
{
    assert(ser.IsReading());

    assert(m_TargetTracks.empty());

    int iTrackCount = 0;
    ser.Value("iTrackCount", iTrackCount);

    for (int iTrack = 0; iTrack < iTrackCount; ++iTrack)
    {
        tAIObjectID targetId;

        ser.BeginGroup("Track");
        {
            ser.Value("targetId", targetId);
            assert(targetId != INVALID_AIOBJECTID);

            CTargetTrack* pTrack = GetTargetTrack(targetId);
            assert(pTrack);
            if (pTrack)
            {
                pTrack->Serialize(ser);
            }
        }
        ser.EndGroup();
    }

    m_dummyPotentialTarget.Serialize(ser);

    InitDummy();
}

//////////////////////////////////////////////////////////////////////////
void CTargetTrackGroup::InitDummy()
{
    CAIObject* pDummyRep = m_dummyPotentialTarget.refDummyRepresentation.GetAIObject();
    assert(pDummyRep);
    if (pDummyRep)
    {
        CWeakRef<CAIObject> refAIObject = gAIEnv.pObjectContainer->GetWeakRef(m_aiObjectId);
        pDummyRep->SetAssociation(refAIObject);

        pDummyRep->SetShouldSerialize(false);
    }
}

//////////////////////////////////////////////////////////////////////////
void CTargetTrackGroup::Update(TargetTrackHelpers::ITargetTrackConfigProxy* pConfigProxy)
{
    FUNCTION_PROFILER(GetISystem(), PROFILE_AI);

    const float fCurrTime = GetAISystem()->GetFrameStartTimeSeconds();

    m_SortedTracks.clear();
    m_SortedTracks.reserve(m_TargetTracks.size());

    // Update the tracks
    TTargetTrackContainer::iterator itTrack = m_TargetTracks.begin();
    TTargetTrackContainer::iterator itTrackEnd = m_TargetTracks.end();
    for (; itTrack != itTrackEnd; ++itTrack)
    {
        CTargetTrack* pTrack = itTrack->second;
        assert(pTrack);

        if (pTrack->Update(fCurrTime, pConfigProxy))
        {
            m_SortedTracks.push_back(pTrack);
        }
    }

    m_bNeedSort = (!m_SortedTracks.empty());
}

struct TargetTrackSorter
{
    bool operator()(const CTargetTrack* left, const CTargetTrack* right) const
    {
        if (left && right)
        {
            return *left < *right;
        }

        assert(false);
        return left < right;
    }
};

//////////////////////////////////////////////////////////////////////////
void CTargetTrackGroup::UpdateSortedTracks(bool bForced)
{
    if (m_bNeedSort || bForced)
    {
        m_bNeedSort = false;
        std::sort(m_SortedTracks.begin(), m_SortedTracks.end(), TargetTrackSorter());
    }
}

//////////////////////////////////////////////////////////////////////////
CTargetTrack* CTargetTrackGroup::GetTargetTrack(tAIObjectID aiTargetId)
{
    CTargetTrack* pTrack = NULL;

    TTargetTrackContainer::iterator itTrack = m_TargetTracks.find(aiTargetId);
    if (itTrack != m_TargetTracks.end())
    {
        pTrack = itTrack->second;
    }
    else
    {
        // Request a new track to use from the pool
        pTrack = m_pTrackPoolProxy->GetUnusedTargetTrackFromPool();
        assert(pTrack);

        pTrack->Init(m_aiObjectId, aiTargetId, m_uConfigHash);
        m_TargetTracks[aiTargetId] = pTrack;

#ifdef TARGET_TRACK_DEBUG
        // Create debug history for it
        uint32 uDebugGraphIndex;
        if (FindFreeGraphSlot(uDebugGraphIndex))
        {
            CWeakRef<CAIObject> refTarget = gAIEnv.pObjectContainer->GetWeakRef(aiTargetId);
            CAIObject* pTarget = refTarget.GetAIObject();
            if (pTarget) // might be null in the case of bookmarked entities
            {
                m_bDebugGraphOccupied[uDebugGraphIndex] = true;
                pTrack->SetDebugGraphIndex(uDebugGraphIndex);

                IDebugHistory* pDebugHistory = m_pDebugHistoryManager->CreateHistory(pTarget->GetName());
                if (pDebugHistory)
                {
                    pDebugHistory->SetVisibility(false);
                    pDebugHistory->SetupScopeExtent(-360.0f, 360.0f, 0.0f, 200.0f);
                }
            }
        }
#endif //TARGET_TRACK_DEBUG
    }

    assert(pTrack);
    return pTrack;
}

//////////////////////////////////////////////////////////////////////////
bool CTargetTrackGroup::HandleStimulusEvent(const TargetTrackHelpers::STargetTrackStimulusEvent& stimulusEvent, uint32 uStimulusNameHash)
{
    FUNCTION_PROFILER(GetISystem(), PROFILE_AI);

    assert(!stimulusEvent.m_sStimulusName.empty());

    bool bResult = false;

    CTargetTrack* pTrack = GetTargetTrack(stimulusEvent.m_targetId);
    bResult = HandleStimulusEvent_Target(stimulusEvent, uStimulusNameHash, pTrack);

    return bResult;
}

bool CTargetTrackGroup::TriggerPulse(tAIObjectID targetID, uint32 uStimulusNameHash, uint32 uPulseNameHash)
{
    FUNCTION_PROFILER(GetISystem(), PROFILE_AI);

    bool bResult = false;

    if (CTargetTrack* pTrack = GetTargetTrack(targetID))
    {
        bResult = pTrack->TriggerPulse(uStimulusNameHash, uPulseNameHash);
    }

    return bResult;
}

//////////////////////////////////////////////////////////////////////////
bool CTargetTrackGroup::TriggerPulse(uint32 uStimulusNameHash, uint32 uPulseNameHash)
{
    FUNCTION_PROFILER(GetISystem(), PROFILE_AI);

    bool bResult = true;

    TTargetTrackContainer::iterator itTrack = m_TargetTracks.begin();
    TTargetTrackContainer::iterator itTrackEnd = m_TargetTracks.end();
    for (; itTrack != itTrackEnd; ++itTrack)
    {
        CTargetTrack* pTrack = itTrack->second;
        assert(pTrack);

        bResult &= pTrack->TriggerPulse(uStimulusNameHash, uPulseNameHash);
    }

    return bResult;
}

//////////////////////////////////////////////////////////////////////////
bool CTargetTrackGroup::HandleStimulusEvent_All(const TargetTrackHelpers::STargetTrackStimulusEvent& stimulusEvent, uint32 uStimulusNameHash)
{
    FUNCTION_PROFILER(GetISystem(), PROFILE_AI);

    bool bResult = true;

    TTargetTrackContainer::iterator itTrack = m_TargetTracks.begin();
    TTargetTrackContainer::iterator itTrackEnd = m_TargetTracks.end();
    for (; itTrack != itTrackEnd; ++itTrack)
    {
        bResult &= HandleStimulusEvent_Target(stimulusEvent, uStimulusNameHash, itTrack->second);
    }

    return bResult;
}

//////////////////////////////////////////////////////////////////////////
bool CTargetTrackGroup::HandleStimulusEvent_Target(const TargetTrackHelpers::STargetTrackStimulusEvent& stimulusEvent, uint32 uStimulusNameHash, CTargetTrack* pTrack)
{
    FUNCTION_PROFILER(GetISystem(), PROFILE_AI);

    assert(pTrack);

    return (pTrack && pTrack->InvokeStimulus(stimulusEvent, uStimulusNameHash));
}

//////////////////////////////////////////////////////////////////////////
bool CTargetTrackGroup::IsPotentialTarget(tAIObjectID aiTargetId) const
{
    assert(aiTargetId > 0);

    TTargetTrackContainer::const_iterator itTrack = m_TargetTracks.find(aiTargetId);
    return (itTrack != m_TargetTracks.end());
}

//////////////////////////////////////////////////////////////////////////
bool CTargetTrackGroup::IsDesiredTarget(tAIObjectID aiTargetId) const
{
    assert(aiTargetId > 0);

    return (aiTargetId == m_aiLastBestTargetId);
}

//////////////////////////////////////////////////////////////////////////
bool CTargetTrackGroup::GetDesiredTarget(TargetTrackHelpers::EDesiredTargetMethod eMethod, CWeakRef<CAIObject>& outTarget, SAIPotentialTarget*& pOutTargetInfo)
{
    FUNCTION_PROFILER(GetISystem(), PROFILE_AI);

    bool bResult = false;

    CTargetTrack* pBestTrack;
    uint32 count = GetBestTrack(eMethod, &pBestTrack, 1);

    if (count)
    {
        UpdateTargetRepresentation(pBestTrack, outTarget, pOutTargetInfo);
        bResult = true;
    }

    m_aiLastBestTargetId = outTarget.GetObjectID();

    return bResult;
}

//////////////////////////////////////////////////////////////////////////
uint32 CTargetTrackGroup::GetBestTrack(TargetTrackHelpers::EDesiredTargetMethod eMethod,
    CTargetTrack** tracks, uint32 maxCount)
{
    UpdateSortedTracks();

    uint32 count = 0;
    switch (eMethod & TargetTrackHelpers::eDTM_SELECTION_MASK)
    {
    case TargetTrackHelpers::eDTM_Select_Highest:
    {
        TSortedTracks::iterator itBestTrack = m_SortedTracks.begin();
        TSortedTracks::iterator itTrackEnd = m_SortedTracks.end();

        for (; itBestTrack != itTrackEnd; ++itBestTrack)
        {
            CTargetTrack* track(*itBestTrack);

            if (track && TestTrackAgainstFilters(track, eMethod))
            {
                tracks[count++] = track;

                if (count >= maxCount)
                {
                    break;
                }
            }
        }
    }
    break;

    case TargetTrackHelpers::eDTM_Select_Lowest:
    {
        TSortedTracks::reverse_iterator itBestTrack = m_SortedTracks.rbegin();
        TSortedTracks::reverse_iterator itTrackEnd = m_SortedTracks.rend();

        for (; itBestTrack != itTrackEnd; ++itBestTrack)
        {
            CTargetTrack* track(*itBestTrack);

            if (track && TestTrackAgainstFilters(track, eMethod))
            {
                tracks[count++] = track;

                if (count >= maxCount)
                {
                    break;
                }
            }
        }
    }
    break;

    default:
        CRY_ASSERT_MESSAGE(false, "CTargetTrackGroup::GetBestTrack Unhandled desired target method");
        break;
    }

    return count;
}

//////////////////////////////////////////////////////////////////////////
bool CTargetTrackGroup::TestTrackAgainstFilters(CTargetTrack* pTrack, TargetTrackHelpers::EDesiredTargetMethod eMethod) const
{
    assert(pTrack);

    bool bResult = true;

    // [Kevin:26.02.2010] Need a better method than using the track manager here...
    CTargetTrackManager* pManager = gAIEnv.pTargetTrackManager;
    assert(pManager);

    const uint32 uFilterBitmask = eMethod & TargetTrackHelpers::eDTM_FILTER_MASK;

    if (uFilterBitmask & TargetTrackHelpers::eDTM_Filter_CanAquireTarget)
    {
        CAIActor* pOwner = CastToCAIActorSafe(gAIEnv.pObjectContainer->GetAIObject(m_aiObjectId));
        IAIObject* pTargetAI = pTrack->GetAIObject().GetIAIObject();

        if ((pOwner != NULL) && pTargetAI)
        {
            bResult = pOwner->CanAcquireTarget(pTargetAI);
        }
    }

    if (uFilterBitmask & TargetTrackHelpers::eDTM_Filter_LimitDesired)
    {
        /*const int iTargetLimit = pManager->GetTargetLimit(aiTargetId);
        bResult &= (iTargetLimit <= 0 || pManager->GetDesiredTargetCount(aiTargetId, m_aiObjectId) < iTargetLimit);*/
    }

    if (uFilterBitmask & TargetTrackHelpers::eDTM_Filter_LimitPotential)
    {
        /*const int iTargetLimit = pManager->GetTargetLimit(aiTargetId);
        bResult &= (iTargetLimit <= 0 || pManager->GetPotentialTargetCount(aiTargetId, m_aiObjectId) < iTargetLimit);*/
    }

    return bResult;
}

//////////////////////////////////////////////////////////////////////////
void CTargetTrackGroup::UpdateTargetRepresentation(const CTargetTrack* pBestTrack, CWeakRef<CAIObject>& outTarget, SAIPotentialTarget*& pOutTargetInfo)
{
    FUNCTION_PROFILER(GetISystem(), PROFILE_AI);

    CWeakRef<CAIObject> refTarget = pBestTrack->GetAITarget();

#ifdef CRYAISYSTEM_DEBUG
    CAIObject* pTarget = refTarget.GetAIObject();
#endif

    CAIObject* pDummyRep = m_dummyPotentialTarget.refDummyRepresentation.GetAIObject();
    assert(pDummyRep);
    if (!pDummyRep)
    {
        return;
    }

    pDummyRep->SetPos(pBestTrack->GetTargetPos());
    pDummyRep->SetEntityDir(pBestTrack->GetTargetDir());
    pDummyRep->SetBodyDir(pBestTrack->GetTargetDir());
    pDummyRep->SetViewDir(pBestTrack->GetTargetDir());
    pDummyRep->SetFireDir(pBestTrack->GetTargetDir());
    pDummyRep->SetAssociation(refTarget);

#ifdef CRYAISYSTEM_DEBUG
    stack_string sDummyRepName;
#endif


    const EAITargetType eTargetType = pBestTrack->GetTargetType();
    const EAITargetContextType eTargetContextType = pBestTrack->GetTargetContext();
    const EAITargetThreat eTargetThreat = pBestTrack->GetTargetThreat();

    switch (eTargetType)
    {
    case AITARGET_VISUAL:
    {
#ifdef CRYAISYSTEM_DEBUG
        sDummyRepName.Format("Target Track Dummy - Visual \'%s\'", pTarget ? pTarget->GetName() : "(Null)");
#endif
        pDummyRep->SetSubType(IAIObject::STP_NONE);
        outTarget = refTarget;
    }
    break;

    case AITARGET_MEMORY:
    {
#ifdef CRYAISYSTEM_DEBUG
        sDummyRepName.Format("Target Track Dummy - Memory \'%s\'", pTarget ? pTarget->GetName() : "(Null)");
#endif
        pDummyRep->SetSubType(IAIObject::STP_MEMORY);
        outTarget =  pDummyRep->GetSelfReference();
    }
    break;

    case AITARGET_SOUND:
    {
#ifdef CRYAISYSTEM_DEBUG
        sDummyRepName.Format("Target Track Dummy - Sound \'%s\'", pTarget ? pTarget->GetName() : "(Null)");
#endif
        switch (eTargetContextType)
        {
        case AITARGET_CONTEXT_GUNFIRE:
            pDummyRep->SetSubType(IAIObject::STP_GUNFIRE);
            break;
        default:
            pDummyRep->SetSubType(IAIObject::STP_SOUND);
            break;
        }
        outTarget = pDummyRep->GetSelfReference();
    }
    break;

    case AITARGET_NONE:
    {
#ifdef CRYAISYSTEM_DEBUG
        sDummyRepName.Format("Target Track Dummy - No Type \'%s\'", pTarget ? pTarget->GetName() : "(Null)");
#endif
        pDummyRep->SetSubType(IAIObject::STP_NONE);
        outTarget = pDummyRep->GetSelfReference();
    }
    break;

    default:
        CRY_ASSERT_MESSAGE(0, "CTargetTrackGroup::UpdateDummyTargetRep Unhandled AI target type");
        break;
    }

#ifdef CRYAISYSTEM_DEBUG
    pDummyRep->SetName(sDummyRepName.c_str());
#endif

    // Update the dummy target
    m_dummyPotentialTarget.bNeedsUpdating = false;
    m_dummyPotentialTarget.type = eTargetType;
    m_dummyPotentialTarget.threat = eTargetThreat;
    m_dummyPotentialTarget.exposureThreat = eTargetThreat;

    pOutTargetInfo = &m_dummyPotentialTarget;
}

#ifdef TARGET_TRACK_DEBUG
//////////////////////////////////////////////////////////////////////////
bool CTargetTrackGroup::FindFreeGraphSlot(uint32& outIndex) const
{
    outIndex = UINT_MAX;

    for (uint32 i = 0; i < DEBUG_GRAPH_OCCUPIED_SIZE; ++i)
    {
        if (!m_bDebugGraphOccupied[i])
        {
            outIndex = i;
            break;
        }
    }

    return (outIndex < UINT_MAX);
}

//////////////////////////////////////////////////////////////////////////
void CTargetTrackGroup::DebugDrawTracks(TargetTrackHelpers::ITargetTrackConfigProxy* pConfigProxy, bool bLastDraw)
{
    CDebugDrawContext dc;
    float fColumnX = 1.0f;
    float fColumnY = 11.0f;
    float fColumnGraphX = fColumnX + 475.0f;
    float fColumnGraphY = fColumnY;
    const float fGraphWidth = 150.0f;
    const float fGraphHeight = 150.0f;
    const float fGraphMargin = 5.0f;

    static float s_fGraphUpdateInterval = 0.125f;
    bool bUpdateGraph = false;
    const float fCurrTime = GetAISystem()->GetFrameStartTimeSeconds();
    if (fCurrTime - m_fLastGraphUpdate >= s_fGraphUpdateInterval)
    {
        m_fLastGraphUpdate = fCurrTime;
        bUpdateGraph = true;
    }

    CWeakRef<CAIObject> refObject = gAIEnv.pObjectContainer->GetWeakRef(m_aiObjectId);
    CAIObject* pObject = refObject.GetAIObject();
    assert(pObject);
    if (!pObject || !pObject->IsEnabled())
    {
        return;
    }

    const ColorB textCol(255, 255, 255, 255);
    dc->Draw2dLabel(fColumnX, fColumnY, 1.5f, textCol, false, "Target Tracks for Agent \'%s\':", pObject->GetName());
    fColumnY += 20.0f;

    UpdateSortedTracks();

    TTargetTrackContainer::const_iterator itTrack = m_TargetTracks.begin();
    TTargetTrackContainer::const_iterator itTrackEnd = m_TargetTracks.end();
    for (; itTrack != itTrackEnd; ++itTrack)
    {
        const CTargetTrack* pTrack = itTrack->second;
        assert(pTrack);

        // TODO Not returning right distance?
        const int iIndex = (int)std::distance(m_SortedTracks.begin(), std::find(m_SortedTracks.begin(), m_SortedTracks.end(), pTrack));
        pTrack->DebugDraw(dc, iIndex, fColumnX, fColumnY, pConfigProxy);
        fColumnY += 20.0f;

        if (bUpdateGraph)
        {
            CWeakRef<CAIObject> refTarget = pTrack->GetAIObject();
            if (CAIObject* pTarget = refTarget.GetAIObject())
            {
                const uint32 uDebugGraphIndex = pTrack->GetDebugGraphIndex();
                const uint32 uGraphX = uDebugGraphIndex % 3;
                const uint32 uGraphY = uDebugGraphIndex / 4;

                // Debug graph update
                IDebugHistory* pDebugHistory = m_pDebugHistoryManager->GetHistory(pTarget->GetName());
                if (pDebugHistory)
                {
                    pDebugHistory->SetupLayoutAbs(fColumnGraphX + (fGraphWidth + fGraphMargin) * uGraphX, fColumnGraphY + (fGraphHeight + fGraphMargin) * uGraphY, fGraphWidth, fGraphHeight, fGraphMargin);
                    pDebugHistory->AddValue(pTrack->GetTrackValue());
                    pDebugHistory->SetVisibility(!bLastDraw);
                }
            }
        }

        pTrack->SetLastDebugDrawTime(fCurrTime);
    }
}

//////////////////////////////////////////////////////////////////////////
void CTargetTrackGroup::DebugDrawTargets(int nMode, int nTargetedCount, bool bExtraInfo)
{
    assert(nMode > 0);

    CDebugDrawContext dc;
    const ColorB visualColor(255, 0, 0, 255);
    const ColorB memoryColor(255, 255, 0, 255);
    const ColorB soundColor(0, 255, 0, 255);
    const ColorB invalidColor(120, 120, 120, 255);
    const float fProbableRatio = 0.45f;

    const float fCurrTime = GetAISystem()->GetFrameStartTimeSeconds();

    CWeakRef<CAIObject> refObject = gAIEnv.pObjectContainer->GetWeakRef(m_aiObjectId);
    CAIObject* pObject = refObject.GetAIObject();
    assert(pObject);
    if (!pObject || !pObject->IsEnabled())
    {
        return;
    }

    const Vec3& vPos = pObject->GetPos();

    UpdateSortedTracks();

    TSortedTracks::const_iterator itTrack = m_SortedTracks.begin();
    TSortedTracks::const_iterator itTrackEnd = m_SortedTracks.end();
    bool bFirst = true;
    for (; itTrack != itTrackEnd; ++itTrack)
    {
        const CTargetTrack* pTrack = *itTrack;
        assert(pTrack);

        ColorB drawColor;
        EAITargetType eType = pTrack->GetTargetType();

        CWeakRef<CAIObject> refTarget = pTrack->GetAIObject();
        assert(refTarget.IsValid());
        CAIObject* pTarget = refTarget.GetAIObject();

        switch (eType)
        {
        case AITARGET_VISUAL:
        {
            drawColor = visualColor;
        }
        break;

        case AITARGET_MEMORY:
        {
            drawColor = memoryColor;
        }
        break;

        case AITARGET_SOUND:
        {
            drawColor = soundColor;
        }
        break;

        case AITARGET_NONE:
        {
            drawColor = invalidColor;
        }
        break;

        default:
            CRY_ASSERT_MESSAGE(0, "CTargetTrackGroup::DebugDrawTargets Unhandled target type");
            break;
        }
        if (!bFirst)
        {
            drawColor.r = uint8((float)drawColor.r * fProbableRatio);
            drawColor.g = uint8((float)drawColor.g * fProbableRatio);
            drawColor.b = uint8((float)drawColor.b * fProbableRatio);
            drawColor.a = uint8((float)drawColor.a * fProbableRatio);
        }

        const Vec3& vTargetPos = pTrack->GetTargetPos();
        const Vec3 vTargetToPos = (vPos - vTargetPos).GetNormalizedSafe();
        const Vec3 vTargetToPosRight = Vec3_OneZ.Cross(vTargetToPos).GetNormalizedSafe();

        dc->DrawLine(vPos, drawColor, vTargetPos, drawColor, 2.0f);
        dc->DrawLine(vTargetPos, drawColor, vTargetPos + vTargetToPos + vTargetToPosRight * 0.5f, drawColor, 2.0f);
        dc->DrawLine(vTargetPos, drawColor, vTargetPos + vTargetToPos - vTargetToPosRight * 0.5f, drawColor, 2.0f);

        if (bExtraInfo)
        {
            string sExtraInfoText;
            sExtraInfoText.Format("%.3f", pTrack->GetTrackValue());

            dc->Draw3dLabel(vTargetPos + (vPos - vTargetPos) * 0.5f, 1.25f, sExtraInfoText.c_str());
        }

        pTrack->SetLastDebugDrawTime(fCurrTime);

        if (bFirst && nMode == 1)
        {
            break;
        }
        bFirst = false;
    }

    dc->Draw3dLabel(vPos + Vec3(0.0f, 0.0f, 1.0f), 1.5f, "%d", nTargetedCount);
}
#endif //TARGET_TRACK_DEBUG