/******************************************************************
  
  Module:  debugoverlay.cpp
  
  Author: Borut Pfeifer
  
  Copyright 2005 Sony Online Entertainment.  All rights reserved.
  
*******************************************************************/

//-------------------------------------------------------- Includes
#include "debugoverlay.h"
//---------------------------------------------- Class Declarations
//----------------------------------------- Functions Declarations
//------------------------------------ Member Functions Definitions
//------------------------------------ cLOSTable

#ifdef _DRAWDEBUGOVERLAY

#include "registry.h"
#include "stats.h"
#include "SyRaster.h"
#include "SyCamera.h"
#include "SyPool.h"
#include "SyMap.h"
#include "SyStr.h"

cDebugOverlay* cDebugOverlay::smpThis     = NULL;

const SyColor32F cDebugOverlay::RED       = SyColor32F(1.0f, 0.0f, 0.0f, 1.0f);
const SyColor32F cDebugOverlay::GREEN     = SyColor32F(0.0f, 1.0f, 0.0f, 1.0f);
const SyColor32F cDebugOverlay::BLUE      = SyColor32F(0.0f, 0.0f, 1.0f, 1.0f);
const SyColor32F cDebugOverlay::CYAN      = SyColor32F(0.0f, 1.0f, 1.0f, 1.0f);
const SyColor32F cDebugOverlay::MAGENTA   = SyColor32F(1.0f, 0.0f, 1.0f, 1.0f);
const SyColor32F cDebugOverlay::YELLOW    = SyColor32F(1.0f, 1.0f, 0.0f, 1.0f);
const SyColor32F cDebugOverlay::ORANGE    = SyColor32F(1.0f, 0.54f, 0.0f, 1.0f);
const SyColor32F cDebugOverlay::PURPLE    = SyColor32F(0.58f, 0.0f, 0.82f, 1.0f);
const SyColor32F cDebugOverlay::GRAY      = SyColor32F(0.5f, 0.5f, 0.5f, 1.0f);
const SyColor32F cDebugOverlay::BROWN     = SyColor32F(0.72f, 0.52f, 0.05f, 1.0f);
const SyColor32F cDebugOverlay::WHITE     = SyColor32F(1.0f, 1.0f, 1.0f, 1.0f);
const SyColor32F cDebugOverlay::BLACK     = SyColor32F(0.0f, 0.0f, 0.0f, 1.0f);

static const int MAX_OVERLAY_TEXT_DISPLAY = 64;
//-----------------------------------------------------------Overlay Primitives

struct OverlayLine
{
  tGameObjectID objID;
  SyColor32F color;
  SyVect3 start, end;
};

struct OverlayText
{
  char text[MAX_OVERLAY_TEXT_DISPLAY+1];
  tGameObjectID objID;
  SyColor32F color;
  SyVect3 loc;
  bool bWorldSpace;
};

//-----------------------------------------------------------cOverlayChannel
class cOverlayChannel
{
  public:
    cOverlayChannel();
    ~cOverlayChannel();

    void Clear();

    cNameID mName;
    bool mbEnabled;
    SyPool<OverlayLine> mLines;
    SyPool<OverlayText> mText;
};

cOverlayChannel::cOverlayChannel()
: mName(0),
  mbEnabled(false)
{
}

cOverlayChannel::~cOverlayChannel()
{
  Clear();
}

void cOverlayChannel::Clear()
{
  mLines.Clear();
  mText.Clear();
}

//-----------------------------------------------------------cOverlayChannel
class cDebugOverlay::Implementation
{
  public:
    Implementation();
    ~Implementation();

    cOverlayChannel* AddChannel(const char* channelName);
    cOverlayChannel* FindChannel(const char* channelName);

    tGameObjectID mSelectedID;
    bool mbEnabled;
    SyMap<SyResourceID, cOverlayChannel*> mChannels;
};



cDebugOverlay::Implementation::Implementation()
: mSelectedID(ID_NONE),
  mbEnabled(false)
{
}

cDebugOverlay::Implementation::~Implementation()
{
  for (int i=mChannels.Begin(); i!= mChannels.End(); i = mChannels.Next(i))
  {
    delete mChannels(i);
  }
  mChannels.Clear();
}

cOverlayChannel* cDebugOverlay::Implementation::AddChannel(const char* channelName)
{
  cOverlayChannel* pChannel = new cOverlayChannel();
  pChannel->mName.SetName(channelName);
  mChannels.Insert(pChannel->mName.GetID(), pChannel);
  return pChannel;
}

cOverlayChannel* cDebugOverlay::Implementation::FindChannel(const char* channelName)
{
  int i = mChannels.Find(SyHashResourceID(channelName));

  if (i != mChannels.End())
  {
    return mChannels(i);
  }

  return NULL;
}

//-----------------------------------------------------------cOverlayChannel
cDebugOverlay::cDebugOverlay()
{
  SyAssert(smpThis == NULL);
  smpThis = this;

  mpImp = new Implementation();
}

cDebugOverlay::~cDebugOverlay()
{
  delete mpImp;
  mpImp = NULL;

  SyAssert(smpThis == this);
  smpThis = NULL;
}

bool cDebugOverlay::IsChannelEnabled(const char* channelName)
{
  cOverlayChannel* pChannel = mpImp->FindChannel(channelName);

  if (!pChannel)
  {
    return false;
  }

  return pChannel->mbEnabled;
}

void cDebugOverlay::EnableChannel(const char* channelName, bool bEnable)
{
  cOverlayChannel* pChannel = mpImp->FindChannel(channelName);

  if (!pChannel)
  {
    pChannel = mpImp->AddChannel(channelName);
  }

  SyAssert(pChannel!=NULL);

  if (!pChannel)
  {
    return;
  }

  pChannel->mbEnabled = bEnable;
}

void cDebugOverlay::DrawLine(tGameObjectID objID,
                             const char* channelName,
                             const SyVect3& start,
                             const SyVect3& end,
                             SyColor32F color)
{
  if (!mpImp->mbEnabled)
  {
    return;
  }

  cOverlayChannel* pChannel = mpImp->FindChannel(channelName);

  if (!pChannel)
  {
    pChannel = mpImp->AddChannel(channelName);
  }

  SyAssert(pChannel!=NULL);

  if (!pChannel || !pChannel->mbEnabled)
  {
    return;
  }
  
  OverlayLine l;
  l.start = start;
  l.end = end;
  l.color = color;
  l.objID = objID;
  pChannel->mLines.Add(l);
}

void cDebugOverlay::DrawArrow(tGameObjectID objID,
                              const char* channelName,
                              const SyVect3& start,
                              const SyVect3& end, SyColor32F color)
{
  if (!mpImp->mbEnabled)
  {
    return;
  }

  cOverlayChannel* pChannel = mpImp->FindChannel(channelName);

  if (!pChannel)
  {
    pChannel = mpImp->AddChannel(channelName);
  }

  SyAssert(pChannel!=NULL);

  if (!pChannel || !pChannel->mbEnabled)
  {
    return;
  }

  OverlayLine l;
  l.color = color;
  l.objID = objID;
  l.start = start;
  l.end = end;
  pChannel->mLines.Add(l);

  SyVect3 right, dir(end-start);
  dir.Normalize();

  right.Cross(dir, SyVect3(0.0f, 1.0f, 0.0f));
  
  float mag = right.NormalizeMagn();
  if (mag < 0.00001f)
  {
    right.Cross(dir, SyVect3(1.0f, 0.0f, 0.0f));
  }

  right *= 0.3f;
  dir *= 0.3f;

  l.start = end;
  l.end = end+right-dir;
  pChannel->mLines.Add(l);

  l.end = end-right-dir;
  pChannel->mLines.Add(l);
}

void cDebugOverlay::DrawBBox(tGameObjectID objID,
                             const char* channelName,
                             const SyVect3& min,
                             const SyVect3& max,
                             SyColor32F color)
{
  if (!mpImp->mbEnabled)
  {
    return;
  }

  cOverlayChannel* pChannel = mpImp->FindChannel(channelName);

  if (!pChannel)
  {
    pChannel = mpImp->AddChannel(channelName);
  }

  SyAssert(pChannel!=NULL);

  if (!pChannel || !pChannel->mbEnabled)
  {
    return;
  }

  OverlayLine l;
  l.start = l.end = min;
  l.end.Y = max.Y;
  l.color = color;
  l.objID = objID;
  pChannel->mLines.Add(l);

  l.start = l.end = min;
  l.end.X = max.X;
  pChannel->mLines.Add(l);

  l.start = l.end = min;
  l.end.Z = max.Z;
  pChannel->mLines.Add(l);

  l.start = l.end = min;
  l.start.Y = max.Y;
  l.end.Y = max.Y;
  l.end.Z = max.Z;
  pChannel->mLines.Add(l);

  l.start = l.end = min;
  l.start.Y = max.Y;
  l.end.Y = max.Y;
  l.end.X = max.X;
  pChannel->mLines.Add(l);

  l.start = l.end = max;
  l.end.Y = min.Y;
  pChannel->mLines.Add(l);

  l.start = l.end = max;
  l.end.X = min.X;
  pChannel->mLines.Add(l);

  l.start = l.end = max;
  l.end.Z = min.Z;
  pChannel->mLines.Add(l);

  l.start = l.end = max;
  l.start.Y = min.Y;
  l.end.Y = min.Y;
  l.end.Z = min.Z;
  pChannel->mLines.Add(l);

  l.start = l.end = max;
  l.start.Y = min.Y;
  l.end.Y = min.Y;
  l.end.X = min.X;
  pChannel->mLines.Add(l);

  l.start.X = min.X;
  l.start.Y = min.Y;
  l.start.Z = max.Z;
  l.end.X = min.X;
  l.end.Y = max.Y;
  l.end.Z = max.Z;
  pChannel->mLines.Add(l);

  l.start.X = max.X;
  l.start.Y = min.Y;
  l.start.Z = min.Z;
  l.end.X = max.X;
  l.end.Y = max.Y;
  l.end.Z = min.Z;
  pChannel->mLines.Add(l);
}

void cDebugOverlay::DrawOrientedBox(tGameObjectID objID,
                                    const char* channelName,
                                    const SyVect3& center,
                                    const SyVect3& halfLength, 
                                    const SyVect3& halfWidth,
                                    const SyVect3& height,
                                    SyColor32F color)
{
  if (!mpImp->mbEnabled)
  {
    return;
  }

  cOverlayChannel* pChannel = mpImp->FindChannel(channelName);

  if (!pChannel)
  {
    pChannel = mpImp->AddChannel(channelName);
  }

  SyAssert(pChannel!=NULL);

  if (!pChannel || !pChannel->mbEnabled)
  {
    return;
  }

  OverlayLine l;
  l.color = color;
  l.objID = objID;

  l.start = l.end = center;
  l.start += halfLength;
  l.start += halfWidth;
  l.end += halfLength;
  l.end -= halfWidth;
  pChannel->mLines.Add(l);

  l.start = l.end = center;
  l.start += halfLength;
  l.start += halfWidth;
  l.end -= halfLength;
  l.end += halfWidth;
  pChannel->mLines.Add(l);

  l.start = l.end = center;
  l.start -= halfLength;
  l.start -= halfWidth;
  l.end += halfLength;
  l.end -= halfWidth;
  pChannel->mLines.Add(l);

  l.start = l.end = center;
  l.start -= halfLength;
  l.start -= halfWidth;
  l.end -= halfLength;
  l.end += halfWidth;
  pChannel->mLines.Add(l);

  l.start = center;
  l.start -= halfLength;
  l.start -= halfWidth;
  l.end = l.start;
  l.end += height;
  pChannel->mLines.Add(l);

  l.start = center;
  l.start += halfLength;
  l.start -= halfWidth;
  l.end = l.start;
  l.end += height;
  pChannel->mLines.Add(l);

  l.start = center;
  l.start -= halfLength;
  l.start += halfWidth;
  l.end = l.start;
  l.end += height;
  pChannel->mLines.Add(l);

  l.start = center;
  l.start += halfLength;
  l.start += halfWidth;
  l.end = l.start;
  l.end += height;
  pChannel->mLines.Add(l);

  l.start = l.end = center + height;
  l.start += halfLength;
  l.start += halfWidth;
  l.end += halfLength;
  l.end -= halfWidth;
  pChannel->mLines.Add(l);

  l.start = l.end = center + height;
  l.start += halfLength;
  l.start += halfWidth;
  l.end -= halfLength;
  l.end += halfWidth;
  pChannel->mLines.Add(l);

  l.start = l.end = center + height;
  l.start -= halfLength;
  l.start -= halfWidth;
  l.end += halfLength;
  l.end -= halfWidth;
  pChannel->mLines.Add(l);

  l.start = l.end = center + height;
  l.start -= halfLength;
  l.start -= halfWidth;
  l.end -= halfLength;
  l.end += halfWidth;
  pChannel->mLines.Add(l);
}

void cDebugOverlay::DrawSphere(tGameObjectID objID,
                               const char* channelName,
                               const SyVect3& center,
                               float radius,
                               SyColor32F color)
{
  if (!mpImp->mbEnabled)
  {
    return;
  }

  cOverlayChannel* pChannel = mpImp->FindChannel(channelName);

  if (!pChannel)
  {
    pChannel = mpImp->AddChannel(channelName);
  }

  SyAssert(pChannel!=NULL);

  if (!pChannel || !pChannel->mbEnabled)
  {
    return;
  }


  int         i, j;
  float       angle1, angle2;
  float       angle1Delta, angle2Delta;
  SyVect3     vert, lastVert;

  OverlayLine l;
  l.color = color;
  l.objID = objID;

  angle1Delta = ((2.0f * SY_PI) / 6.0f);
  angle2Delta = ((SY_PI) / 3.0f);
  angle1 = 0.0f;

  for(i = 0; i < 6; i++, angle1 += angle1Delta)
  {
    angle2 = 0.0f;
    lastVert.X = SY_COS( -angle1 ) * SY_SIN( angle2 );
    lastVert.Y = SY_COS( angle2 );
    lastVert.Z = SY_SIN( -angle1 ) * SY_SIN( angle2 );
    lastVert *= radius;
    lastVert += center;

    angle2 = angle2Delta;
    for(j = 0; j < 3; j++, angle2 += angle2Delta)
    {
      vert.X = SY_COS( angle1 ) * SY_SIN( angle2 );
      vert.Y = SY_COS( angle2 );
      vert.Z = SY_SIN( angle1 ) * SY_SIN( angle2 );
      vert *= radius;
      vert += center;

      l.start = lastVert;
      l.end = vert;
      pChannel->mLines.Add(l);
      lastVert = vert;

      if (0 == j || 1 == j)
      {
        vert.X = SY_COS( angle1-angle1Delta ) * SY_SIN( angle2 );
        vert.Y = SY_COS( angle2 );
        vert.Z = SY_SIN( angle1-angle1Delta ) * SY_SIN( angle2 );
        vert *= radius;
        vert += center;

        l.start = vert;
        l.end = lastVert;
        pChannel->mLines.Add(l);
      }
    }
  }
}

void cDebugOverlay::DrawCylinder(tGameObjectID objID,
                                 const char* channelName,
                                 const SyVect3& center,
                                 float radius,
                                 const SyVect3& axis,
                                 SyColor32F color)
{
  if (!mpImp->mbEnabled)
  {
    return;
  }

  cOverlayChannel* pChannel = mpImp->FindChannel(channelName);

  if (!pChannel)
  {
    pChannel = mpImp->AddChannel(channelName);
  }

  SyAssert(pChannel!=NULL);

  if (!pChannel || !pChannel->mbEnabled)
  {
    return;
  }


  int         i;
  float       angle1, angle1Delta;
  SyVect3     vert, lastVert;

  OverlayLine l;
  l.color = color;
  l.objID = objID;

  angle1Delta = ((2.0f * SY_PI) / 6.0f);
  angle1 = 0.0f;

  lastVert.X = SY_COS( -angle1Delta );
  lastVert.Y = 0.0f;
  lastVert.Z = SY_SIN( -angle1Delta );
  lastVert *= radius;
  lastVert += center;

  for(i = 0; i < 6; i++, angle1 += angle1Delta)
  {
    vert.X = SY_COS( angle1 );
    vert.Y = 0.0f;
    vert.Z = SY_SIN( angle1 );
    vert *= radius;
    vert += center;

    l.start = vert;
    l.end = lastVert;
    pChannel->mLines.Add(l);

    l.start = vert;
    l.end = vert;
    l.end.Y += axis.Y;
    pChannel->mLines.Add(l);

    l.start = vert;
    l.end = lastVert;
    l.start.Y += axis.Y;
    l.end.Y += axis.Y;
    pChannel->mLines.Add(l);

    lastVert = vert;
  }
}
void cDebugOverlay::DrawWorldText(tGameObjectID objID,
                                  const char* channelName,
                                  const SyVect3& loc,
                                  const char* text,
                                  SyColor32F color)
{
  if (!mpImp->mbEnabled)
  {
    return;
  }

  cOverlayChannel* pChannel = mpImp->FindChannel(channelName);

  if (!pChannel)
  {
    pChannel = mpImp->AddChannel(channelName);
  }

  SyAssert(pChannel!=NULL);

  if (!pChannel || !pChannel->mbEnabled)
  {
    return;
  }

  OverlayText t;
  t.objID = objID;
  t.color = color;
  t.bWorldSpace = true;
  t.loc = loc;
  SyStr::strncpy(t.text, text, MAX_OVERLAY_TEXT_DISPLAY);
  pChannel->mText.Add(t);
}

void cDebugOverlay::DrawScreenText(tGameObjectID objID,
                                   const char* channelName,
                                   int screenX, int screenY,
                                   const char* text,
                                   SyColor32F color)
{
  if (!mpImp->mbEnabled)
  {
    return;
  }

  cOverlayChannel* pChannel = mpImp->FindChannel(channelName);

  if (!pChannel)
  {
    pChannel = mpImp->AddChannel(channelName);
  }

  SyAssert(pChannel!=NULL);

  if (!pChannel || !pChannel->mbEnabled)
  {
    return;
  }

  OverlayText t;
  t.objID = objID;
  t.color = color;
  t.bWorldSpace = false;
  t.loc.X = (float)screenX;
  t.loc.Y = (float)screenY;
  SyStr::strncpy(t.text, text, MAX_OVERLAY_TEXT_DISPLAY);
  pChannel->mText.Add(t);
}

bool cDebugOverlay::IsEnabled()
{
  return mpImp->mbEnabled;
}

void cDebugOverlay::Enable(bool bEnable)
{
  if (bEnable != mpImp->mbEnabled)
  {
    mpImp->mbEnabled = bEnable;
    mpImp->mSelectedID = ID_NONE;
  }
}

void cDebugOverlay::SelectAllObjects()
{
  mpImp->mSelectedID = ID_NONE;
}

void cDebugOverlay::SelectNextObject(cGameObjectRegistry* pRegistry, SyCamera* pCam)
{
  if (!mpImp->mbEnabled)
  {
    return;
  }

  SyFrustum frustum = pCam->GetFrustum();
  SyVect3 camLoc(pCam->GetLocation());
  tGameObjectID bestID = ID_NONE;
  float distSqr, bestDistSqr = 0.0f;
  float selectedDistSqr = 0.0f;


  if (mpImp->mSelectedID != ID_NONE)
  {
    cGameObject* pCur = pRegistry->Fetch(mpImp->mSelectedID);

    if (pCur)
    {
      selectedDistSqr = camLoc.DistanceSquared(pCur->GetLocation());
    }
  }

  cGameObject* pObj = pRegistry->BeginType(cGameObject::OBJ_NPC);
  for(;pObj != NULL;pObj= pRegistry->NextType(pObj))
  {
    SyAssert(pObj->GetType()==cGameObject::OBJ_NPC);

    if (pObj != NULL && 
        pObj->GetID() != mpImp->mSelectedID && 
        frustum.Cull(pObj->GetLocation(), 1.0f) == 0 &&
        !pObj->GetStats()->IsDead())
    {      
      distSqr = pObj->GetLocation().DistanceSquared(camLoc) - selectedDistSqr;
      if (ID_NONE == bestID || distSqr < bestDistSqr)
      {
        bestID = pObj->GetID();
        bestDistSqr = distSqr;
      }
    }
  }

  if (bestID != ID_NONE)
  {
    mpImp->mSelectedID = bestID;
  }
}

void cDebugOverlay::SelectPrevObject(cGameObjectRegistry* pRegistry,
                                     SyCamera* pCam)
{
  if (!mpImp->mbEnabled)
  {
    return;
  }

  cGameObject* pObj;
  SyFrustum frustum = pCam->GetFrustum();
  SyVect3 camLoc(pCam->GetLocation());
  tGameObjectID bestID = ID_NONE;
  float distSqr, bestDistSqr = 0.0f;
  float selectedDistSqr = 0.0f;

  if (mpImp->mSelectedID != ID_NONE)
  {
    cGameObject* pCur = pRegistry->Fetch(mpImp->mSelectedID);

    if (pCur)
    {
      selectedDistSqr = camLoc.DistanceSquared(pCur->GetLocation());
    }
  }

  pObj = pRegistry->BeginType(cGameObject::OBJ_NPC);
  for(;pObj != NULL;pObj= pRegistry->NextType(pObj))
  {
    if (pObj != NULL && 
        pObj->GetID() != mpImp->mSelectedID &&
        frustum.Cull(pObj->GetLocation(), 1.0f) == 0 &&
        !pObj->GetStats()->IsDead())
    {      
      distSqr = pObj->GetLocation().DistanceSquared(camLoc) - selectedDistSqr;
      if (ID_NONE == bestID || distSqr > bestDistSqr)
      {
        bestID = pObj->GetID();
        bestDistSqr = distSqr;
      }
    }
  }

  if (bestID != ID_NONE)
  {
    mpImp->mSelectedID = bestID;
  }
}


void cDebugOverlay::Render(SyRaster* pRasterDev, SyCamera* pCam)
{
  if (!mpImp->mbEnabled)
  {
    return;
  }

  SyMatrix44 identityMat;
  identityMat.Identity();

  pRasterDev->SetWorld(identityMat);
  pRasterDev->SetSolidFillUIMaterial();
  SyVect3 normal( 0.0f, 0.0f, 0.0f );
  SyVect2I screenPos;

  cOverlayChannel* pChannel;

  for (int i=mpImp->mChannels.Begin(); i!=mpImp->mChannels.End(); i=mpImp->mChannels.Next(i))
  {
    pChannel = mpImp->mChannels(i);

    if (!pChannel->mbEnabled)
    {
      continue;
    }

    for (int j=pChannel->mLines.Begin(); j!=pChannel->mLines.End(); j=pChannel->mLines.Next(j))
    {
      OverlayLine& l = pChannel->mLines(j);

      if (ID_NONE == l.objID ||
          ID_NONE == mpImp->mSelectedID ||
          l.objID == mpImp->mSelectedID)
      {
        pRasterDev->BeginLines();
        pRasterDev->Color( l.color );
        pRasterDev->Normal( normal );
        pRasterDev->Vertex( l.start );
        pRasterDev->Vertex( l.end );
        pRasterDev->EndLines();
      }
    }
  }

  for (int i=mpImp->mChannels.Begin(); i!=mpImp->mChannels.End(); i=mpImp->mChannels.Next(i))
  {
    pChannel = mpImp->mChannels(i);

    if (!pChannel->mbEnabled)
    {
      pChannel->Clear();
      continue;
    }

    pRasterDev->SetWorld(identityMat);

    if (pChannel->mText.Size() > 0)
    {
      for (int j=pChannel->mText.Begin(); j!=pChannel->mText.End(); j=pChannel->mText.Next(j))
      {
        OverlayText& t = pChannel->mText(j);

        if (ID_NONE == t.objID ||
            ID_NONE == mpImp->mSelectedID ||
            t.objID == mpImp->mSelectedID)
        {
          if (t.bWorldSpace)
          {
            pRasterDev->DrawCenteredString(0, t.loc, t.color, t.text);
          }
          else
          {
            pRasterDev->Begin2D();
            screenPos.X = (int)t.loc.X;
            screenPos.Y = (int)t.loc.Y;
            pRasterDev->DrawString(0, screenPos, t.color, t.text);
            pRasterDev->End2D();
          }
        }
      }
    }

    pChannel->Clear();
  }
}

const char* cDebugOverlay::BufferPrintf(const char* text, ...)
{
  static char buf[2048];

  va_list marker;

  va_start(marker, text);     /* Initialize variable arguments. */
  vsprintf(buf, text, marker);
  va_end(marker);              /* Reset variable arguments.      */

  return buf;
}

#endif
// EOF
