/******************************************************************
  
  Module:  ailodmanager.cpp
  
  Author: Borut Pfeifer
  
  Description: Manages updates for NPCs based on distance to camera

  Copyright 2006 Sony Online Entertainment.  All rights reserved.
  
*******************************************************************/

//-------------------------------------------------------- Includes
#include "ailodmanager.h"
#include "SyCamera.h"
#include "../cameracontroller.h"
#include "../titan.h"
#include "../registry.h"
#include "../gameobj.h"
#include "../intel.h"
#include "../debugoverlay.h"
#include "../gameerror.h"
#include "aigoal.h"
#include "aigoal_noncombat.h"

#include <algorithm>

//---------------------------------------------- Class Declarations

//----------------------------------------- Functions Declarations
//------------------------------------ Member Functions Definitions
//------------------------------------ cLOSTable


cAILODManager::NPCVector cAILODManager::smNPCs;
bool cAILODManager::smbDisabled = false;

cAILODManager::NPCEntry::NPCEntry()
: mpAI(NULL),
  mNPCID(ID_NONE),
  mDistSqr(0.0f),
  mFindTargetTime(0.0f),
  mbAlwaysUpdate(false)
{
}

//------------------------------------------- cAILODManager

void cAILODManager::DisableAll(bool bDisable)
{
  smbDisabled = bDisable;
}

void cAILODManager::AddNPC(cGameObject* pNPC, bool bAlwaysUpdate)
{
  SyAssert(pNPC!=NULL);

  if (pNPC->IsRemote())
  {
    return;
  }

  // test to make sure we're not adding twice
  NPCVector::iterator iNPC = smNPCs.begin();
  NPCVector::iterator endNPC = smNPCs.end();

  tGameObjectID id = pNPC->GetID();

  for (; iNPC != endNPC; ++iNPC)
  {
    if (iNPC->mNPCID == id)
    {
      return;
    }
  }

  SyAssert(prop_cast<cIntelNPC*>(pNPC->GetIntel())!=NULL);

  NPCEntry entry;
  entry.mpAI = static_cast<cAi*>(static_cast<cIntelNPC*>(pNPC->GetIntel())->GetAi());
  entry.mNPCID = pNPC->GetID();
  entry.mDistSqr = 0.0f;
  entry.mFindTargetTime = 0.0f;
  entry.mbAlwaysUpdate = bAlwaysUpdate;
  smNPCs.push_back(entry);
}

void cAILODManager::RemoveNPC(cGameObject* pNPC)
{
  SyAssert(pNPC!=NULL);

  NPCVector::iterator iNPC = smNPCs.begin();
  NPCVector::iterator endNPC = smNPCs.end();

  tGameObjectID id = pNPC->GetID();

  for (; iNPC != endNPC; ++iNPC)
  {
    if (iNPC->mNPCID == id)
    {
      iNPC->mpAI = NULL;
      iNPC->mNPCID = ID_NONE;
    }
  }
}

void cAILODManager::Update(Titan* pTitan, float time)
{
  SyAssertf(pTitan != NULL, "cAILODManager::Update - bad titan");
  
  if (!pTitan)
  {
    return;
  }

  cGoal_Spawn::StartFrame();

  cGameObject* pNPC;
  NPCVector::iterator iNPC = smNPCs.begin();

  if (smbDisabled)
  {
    while (iNPC != smNPCs.end())
    {
      if (iNPC->mpAI != NULL && iNPC->mNPCID != ID_NONE)
      {
        pNPC = pTitan->GetRegistry()->Fetch(iNPC->mNPCID);

        if (pNPC && !pNPC->IsRemote())
        {
          static_cast<cIntelNPC*>(iNPC->mpAI->mpOwner->GetIntel())->Stop();   
        }
      }

      ++iNPC;
    }

    return;
  }

//  SyTime startTime = SyTimer::GetTime();

  SyCamera* pCam = pTitan->GetCameraController()->GetCamera();
  SyAssert(pCam!=NULL);

  static const float CAMERA_FORWARD_OFFSET = 20.0f;
  SyVect3 camLoc(pCam->GetLocation());
  camLoc.MulAdd(pCam->GetDir(), CAMERA_FORWARD_OFFSET);
  
  while (iNPC != smNPCs.end())
  {
    if (iNPC->mpAI == NULL || iNPC->mNPCID == ID_NONE)
    {
      iNPC = smNPCs.erase(iNPC);
    }
    else
    {
      pNPC = pTitan->GetRegistry()->Fetch(iNPC->mNPCID);
      GAME_ASSERT(ERROR_CODE, pNPC != NULL, "AI LOD update has null NPC handle!");
      GAME_ASSERT(ERROR_CODE, !pNPC || !pNPC->IsRemote(), "AI LOD update has remote NPC handle!");

      if (!pNPC || pNPC->IsRemote())
      {
        iNPC = smNPCs.erase(iNPC);
      }
      else
      {
        iNPC->mDistSqr = camLoc.DistanceSquared(iNPC->mpAI->mpOwner->GetLocation());   
        iNPC->mFindTargetTime += time;
        ++iNPC;
      }
    }
  }

  if (pTitan->IsPaused())
  {
    return;
  }

  // todo, use sort with temporal coherence?
  std::sort(smNPCs.begin(), smNPCs.end(), NPCEntry::CompareDist);

  static const unsigned int NUM_UPDATE_LEVELS = 4;

  static const unsigned int LEVEL_UPDATE_MAX[NUM_UPDATE_LEVELS] = {5, 7, 10, 20};
  static const float LEVEL_FINDTARGET_RATE[NUM_UPDATE_LEVELS] = {0.25f, 0.5f, 1.0f, 3.0f};
  static const unsigned int MAX_FINDTARGET_UPDATES = 10;

  unsigned int updated = 0;
  unsigned int level = 0;
  unsigned int findTargetUpdates = 0;

  bool bRunFindTarget;
  NPCVector::iterator endNPC = smNPCs.end();

  for (iNPC = smNPCs.begin(); iNPC != endNPC; ++iNPC)
  {
    if (iNPC->mpAI == NULL || iNPC->mNPCID == ID_NONE)
    {
      // test to see if NPC got removed from the list during the update,
      // entry will get deleted next update
      continue;
    }

    bRunFindTarget = iNPC->mFindTargetTime >= LEVEL_FINDTARGET_RATE[level];

    if (bRunFindTarget && findTargetUpdates < MAX_FINDTARGET_UPDATES)
    {
      iNPC->mpAI->UpdateLOD(iNPC->mFindTargetTime);
      iNPC->mFindTargetTime = 0.0f;
      ++findTargetUpdates;
    }

    if (level < NUM_UPDATE_LEVELS - 1)
    {
      ++updated;
    }
    else if (bRunFindTarget)
    {
      ++updated;
    }

    iNPC->mpAI->Update(time);

    if ((level < (NUM_UPDATE_LEVELS - 1))
        && updated >= LEVEL_UPDATE_MAX[level])
    {
      updated = 0;
      ++level;
    }
    else if (level == (NUM_UPDATE_LEVELS - 1) && updated >= LEVEL_UPDATE_MAX[level])
    {
      break; // hit our max NPC update this frame, for all LOD levels
    }
  }

//  SyTime endTime = SyTimer::GetTime();
//  DEBUGOVERLAY_ENABLECHANNEL("AI", true); // for ps3 debugging
//  DEBUGOVERLAY_DRAWSCREENTEXT(0, "AI", 100, 100, ("AIUpd: %d", endTime-startTime), cDebugOverlay::WHITE);
}


// EOF
