/******************************************************************
  
  Module:  lostable.cpp
  
  Author: Borut Pfeifer
  
  Description: Timeslices and caches line of sight raycast requests
  Copyright 2005 Sony Online Entertainment.  All rights reserved.
  
*******************************************************************/

//-------------------------------------------------------- Includes
#include "lostable.h"
#include "SyEnvRay.h"
#include "SyScene.h"
#include "../debugoverlay.h"
#include "../registry.h"
#include "../areaeffect.h"
#include "../titan.h"

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

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

cLOSTable::LOSEntry cLOSTable::smEntries[cLOSTable::MAX_LOS_ENTRIES];

cLOSTable::LOSEntry::LOSEntry()
: mRadius(0.0f),
  mUsedCount(0),
  mStatus(LOS_INVALID)
{
}

//------------------------------------------- cLOSTable
cLOSTable::LOSStatus cLOSTable::RequestLOS(const SyVect3& sourcePos,
                                           const SyVect3& targetPos,
                                           bool bTestAreaEffects,
                                           int& ticket,
                                           float radius)
{
  static const float DIST_TOLERANCE_SQR = 0.5f * 0.5f;
  int emptyIndex = -1;
  ticket = -1;

  for (int i=0; i < MAX_LOS_ENTRIES; ++i)
  {
    if (0 == smEntries[i].mUsedCount && -1 == emptyIndex)
    {
      emptyIndex = i;
    }

    if (LOS_INVALID != smEntries[i].mStatus)
    {
      if (smEntries[i].mSourcePos.DistanceSquared(sourcePos) <= DIST_TOLERANCE_SQR &&
          smEntries[i].mTargetPos.DistanceSquared(targetPos) <= DIST_TOLERANCE_SQR)
      {
        if (radius > smEntries[i].mRadius)
        {
          smEntries[i].mRadius = radius;
        }

        smEntries[i].mUsedCount++;
        ticket = i;
        return smEntries[i].mStatus;
      }
    }
  }

  ticket = emptyIndex;

  if (emptyIndex >= 0)
  {
    smEntries[emptyIndex].mSourcePos = sourcePos;
    smEntries[emptyIndex].mTargetPos = targetPos;
    smEntries[emptyIndex].mHitPos(0.0f, 0.0f, 0.0f);
    smEntries[emptyIndex].mHitNormal(0.0f, 1.0f, 0.0f);
    smEntries[emptyIndex].mStatus = LOS_PENDING;
    smEntries[emptyIndex].mRequestTime = Titan::Get()->GetTime(); 
    smEntries[emptyIndex].mRadius = radius; 
    smEntries[emptyIndex].mbTestAreaEffects = bTestAreaEffects; 
    smEntries[emptyIndex].mUsedCount++;
    return LOS_PENDING;
  }
  else
  {
    return LOS_INVALID;
  }
}

void cLOSTable::CancelLOS(int ticket)
{
  SyAssertf(ticket>=0 && ticket < MAX_LOS_ENTRIES, "cLosTable - trying to cancel bad ticket %d", ticket);

  if (ticket < 0 || ticket >= MAX_LOS_ENTRIES)
  {
    return;
  }

  SyAssertf(smEntries[ticket].mUsedCount > 0, "CLOSTable trying to cancel already canceled LOS %d", ticket);
  if (smEntries[ticket].mUsedCount > 0)
  {
    smEntries[ticket].mUsedCount -= 1;
  }
}

cLOSTable::LOSStatus cLOSTable::HasLOS(int ticket,
                                       SyVect3* pHitPos,
                                       SyVect3* pHitNormal)
{
  SyAssertf(ticket>=0 && ticket < MAX_LOS_ENTRIES, "cLosTable - trying to access bad ticket %d", ticket);

  if (ticket < 0 || ticket >= MAX_LOS_ENTRIES)
  {
    return LOS_INVALID;
  }

  if (LOS_BLOCKED == smEntries[ticket].mStatus)
  {
    if (pHitPos)
    {
      *pHitPos = smEntries[ticket].mHitPos;
    }

    if (pHitNormal)
    {
      *pHitNormal = smEntries[ticket].mHitNormal;
    }
  }

  SyAssertf(smEntries[ticket].mUsedCount > 0, "CLOSTable trying to access invalid LOS %d", ticket);
  return smEntries[ticket].mStatus;
}

void cLOSTable::Update(cGameObjectRegistry* pRegistry, SyScene* pScene)
{
  SyAssertf(pRegistry != NULL, "cLOSTable::Update - bad registry");
  SyAssertf(pScene != NULL, "cLOSTable::Update - bad scene");

  if (!pRegistry || !pScene)
  {
    return;
  }

  int oldestEntries[MAX_LOS_PER_FRAME];
  int replaceLOS;
  uint64 oldestTimes[MAX_LOS_PER_FRAME];
  uint64 waitTime, replaceTime;
  uint64 curTime = Titan::Get()->GetTime();

  //
  // Find the oldest entries
  //
  for (int iLOS = 0; iLOS < MAX_LOS_PER_FRAME; ++iLOS)
  {
    oldestEntries[iLOS] = -1;
    oldestTimes[iLOS] = 0;
  }

  // store the n-oldest entries in the oldestEntries array
  for (int iEntry = 0; iEntry < MAX_LOS_ENTRIES; ++iEntry)
  {
    if (smEntries[iEntry].mUsedCount > 0 && LOS_PENDING == smEntries[iEntry].mStatus)
    {
      waitTime = curTime - smEntries[iEntry].mRequestTime;
      replaceLOS = -1;
      replaceTime = 0;

      for (int iLOS = 0; iLOS < MAX_LOS_PER_FRAME; ++iLOS)
      {
        if (waitTime >= oldestTimes[iLOS] && oldestTimes[iLOS] >= replaceTime)
        {          
          replaceTime = oldestTimes[iLOS];
          replaceLOS = iLOS;
          break;
        }
      }

      if (replaceLOS >= 0)
      {
        oldestTimes[replaceLOS] = waitTime;
        oldestEntries[replaceLOS] = iEntry;
      }
    }
  }

  SyCollSphere sphere;
  SyCollRay ray;
  cLOSSceneFilter filter(pRegistry);
  bool bHitAreaEffect, bHitScene;
  SyVect3 areaEffectHitPos, hitPoint, hitNormal;
  //
  // Now that we've found the oldest, perform the actual raycasts
  //
  for (int iLOS = 0; iLOS < MAX_LOS_PER_FRAME; ++iLOS)
  {
    if (oldestEntries[iLOS] >= 0)
    {
      filter.Init();

      if (smEntries[oldestEntries[iLOS]].mRadius > 0.00001f)
      {
        sphere.Init(smEntries[oldestEntries[iLOS]].mSourcePos, smEntries[oldestEntries[iLOS]].mTargetPos, smEntries[oldestEntries[iLOS]].mRadius);
        bHitScene = pScene->Collide(sphere, filter) != 0;
        hitPoint = sphere.GetHitPoint();
        hitNormal = sphere.GetHitNormal();
      }
      else
      {
        ray.Init(smEntries[oldestEntries[iLOS]].mSourcePos, smEntries[oldestEntries[iLOS]].mTargetPos);
        bHitScene = pScene->Collide(ray, filter) != 0;
        hitPoint = ray.GetHitPoint();
        hitNormal = ray.GetHitNormal();
      }

      bHitAreaEffect = false;

      if (smEntries[oldestEntries[iLOS]].mbTestAreaEffects)
      {
        bHitAreaEffect = cAreaEffectSys::Get()->IsLineOfSightBlocked(smEntries[oldestEntries[iLOS]].mSourcePos, smEntries[oldestEntries[iLOS]].mTargetPos, areaEffectHitPos);
      }

      if (!bHitScene && !bHitAreaEffect)
      {
//        DEBUGOVERLAY_DRAWLINE(0, "AI", smEntries[oldestEntries[iLOS]].mSourcePos, smEntries[oldestEntries[iLOS]].mTargetPos, cDebugOverlay::GREEN);
        smEntries[oldestEntries[iLOS]].mStatus = LOS_CLEAR;
      }
      else
      {
//        DEBUGOVERLAY_DRAWLINE(0, "AI", smEntries[oldestEntries[iLOS]].mSourcePos, smEntries[oldestEntries[iLOS]].mTargetPos, bHitAreaEffect?cDebugOverlay::MAGENTA:cDebugOverlay::RED);
        smEntries[oldestEntries[iLOS]].mStatus = LOS_BLOCKED;

        if (bHitScene && bHitAreaEffect)
        {
          if (smEntries[oldestEntries[iLOS]].mSourcePos.DistanceSquared(areaEffectHitPos) <
              smEntries[oldestEntries[iLOS]].mSourcePos.DistanceSquared(hitPoint))
          {
            bHitScene = false;
          }
        }
        
        if (bHitScene)
        {
          smEntries[oldestEntries[iLOS]].mHitPos = hitPoint;
          smEntries[oldestEntries[iLOS]].mHitNormal = hitNormal;
        }
        else
        {
          smEntries[oldestEntries[iLOS]].mHitPos = areaEffectHitPos;
          smEntries[oldestEntries[iLOS]].mHitNormal = smEntries[oldestEntries[iLOS]].mSourcePos - smEntries[oldestEntries[iLOS]].mTargetPos;
          smEntries[oldestEntries[iLOS]].mHitNormal.Normalize();
        }
      }
    }
  }  
}

cLOSSceneFilter::cLOSSceneFilter(cGameObjectRegistry* pRegistry)
: SySceneFilter(),
  mpRegistry(pRegistry)
{
  SyAssert(mpRegistry);
}

int cLOSSceneFilter::FilterActor( SyScene& scene, SyActorHandle actorHandle )
{
  int32 userID = scene.GetActorID(actorHandle);

  if (-1 == userID || ID_NONE == userID)
  {
    return false;
  }

  cGameObject* pObj = mpRegistry->Fetch((tGameObjectID)userID);
  if (pObj && pObj->GetType() != cGameObject::OBJ_PROP)
  {
    return true;
  }

  return false;
}

// EOF
