//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------

#include "sceneGraph/detailManager.h"
#include "ts/tsShapeInstance.h"
#include "ts/tsPartInstance.h"
#include "dgl/dgl.h"

// bias towards using same detail level as previous frame
#define MatchPreviousReward 0.99f
#define NotMatchPreviousPenalty 1.01f

// this is the pref value that the user should be able to set
F32 DetailManager::smDetailScale = 1.0f;
// this should be fixed at some large upper-bound
S32 DetailManager::smMaxPolyLimit = 20000;
// this should be fixed at some small lower-bound
S32 DetailManager::smMinPolyLimit = 2000;
// this determines how much we can under-shoot poly limit when reducing
S32 DetailManager::smLimitRange = 1000;

// default detail profile -- delay detailing out 2 times...
DetailManager::DetailProfile DetailManager::smDefaultProfile = { 0, 0, 2 };

DetailManager * DetailManager::smDetailManager = NULL;

S32 DetailManager::smPolysDidRender = 0;
S32 DetailManager::smPolysTriedToRender = 0;

S32 QSORT_CALLBACK FN_CDECL compareDetailData( const void * e1, const void * e2 )
{
   const DetailManager::DetailData * dd1 = *(const DetailManager::DetailData**)e1;
   const DetailManager::DetailData * dd2 = *(const DetailManager::DetailData**)e2;

   if (dd1->priority < dd2->priority)
      return -1;
   else if (dd2->priority < dd1->priority)
      return 1;
   else
      return 0;
}

//---------------------------------------------------------
DetailManager::DetailManager()
{
   mInPrepRender = false;
}

DetailManager::~DetailManager()
{
   U32 i;
   for (i=0; i<mDetailData.size(); i++)
      delete mDetailData[i];
   mDetailData.clear();
   for (i=0; i<mFreeDetailData.size(); i++)
      delete mFreeDetailData[i];
   mFreeDetailData.clear();
}

//---------------------------------------------------------

inline void DetailManager::bumpOne(DetailData * dd, S32 bump)
{
   dd->dl = dd->dls[bump];
   dd->intraDL = 1.0f;
   mPolyCount -= dd->bump[bump];
   for (S32 i=0; i<MAX_BUMP; i++)
      dd->bump[i] -= dd->bump[bump];
}

void DetailManager::bumpAll(S32 bump)
{
   for (U32 i=0; i<mDetailData.size(); i++)
      bumpOne(mDetailData[i],bump);
}

//---------------------------------------------------------
void DetailManager::begin()
{
   // enter prepRender stage
   mInPrepRender = true;

   // inc tag...
   mTag++;

   // clear poly count
   mPolyCount = 0;

   // set poly limit for this frame
   mPolyLimit = (S32)(smMinPolyLimit + smDetailScale * (smMaxPolyLimit-smMinPolyLimit));

   // clear bump count
   for (U32 i=0; i<MAX_BUMP; i++)
      mBumpPolyCount[i] = 0;
}

void DetailManager::end()
{
   S32 i;

   // leave prepRender stage
   mInPrepRender = false;

   // update poly count for new frame
   smPolysDidRender = mPolyCount;
   smPolysTriedToRender = mPolyCount;

   // drop detailData for shapes rendered last time but not this time
   for (i=mDetailData.size()-1; i>=0;i--)
      if (mDetailData[i]->tag!=mTag)
      {
         // not rendering this time
         mDetailData[i]->shapeInstance = NULL; // enough to disconnect it from shape instance...
         mDetailData[i]->partInstance = NULL;  // enough to disconnect it from part instance...
         mFreeDetailData.push_back(mDetailData[i]);
         mDetailData.erase(i);
      }

   // we may already be below the poly limit
   if (mPolyCount<getMax(1,mPolyLimit))
      // we accept the preferred details
      return;
/*
   // do as many full bumps as we can
   S32 bump;
   for (bump=0; bump<MAX_BUMP; bump++)
      if (mPolyCount-mBumpPolyCount[bump]<mPolyLimit)
         break;
   if (bump)
      // bump everyone this many times...
      bumpAll(bump-1);

   // update poly count for new frame
   smPolysDidRender = mPolyCount;

   if (bump==MAX_BUMP)
      // can do no more...
      return;

   // compute priority function...sort
   for (i=0; i<mDetailData.size(); i++)
      computePriority(mDetailData[i],bump);
   dQsort(mDetailData.address(),mDetailData.size(),sizeof(DetailData*),compareDetailData);

   // keep reducing until done...
   for (i=0; i<mDetailData.size(); i++)
   {
      if (mPolyCount-mDetailData[i]->bump[bump] > mPolyLimit-smLimitRange)
      {
         bumpOne(mDetailData[i],bump);
         if (mPolyCount<mPolyLimit)
            // we're done...
            break;
      }
   }
*/
   // update poly count for new frame
   smPolysDidRender = mPolyCount;
}

//---------------------------------------------------------
void DetailManager::selectPotential(TSShapeInstance * si, F32 dist, F32 invScale, const DetailProfile * dp)
{
   AssertFatal(mInPrepRender,"DetailManager::selectPotentialDetails");

   dist *= invScale;
   S32 dl = si->selectCurrentDetail2(dist);
   if (dl<0)
      // don't render
      return;

   DetailData * dd;
   if (!si->mData || ((DetailData*)si->mData)->shapeInstance!=si)
   {
      // we weren't rendered last time
      // get new detail data and set prevDL to -2 to encode that we're fresh meat...
      si->mData = getNewDetailData();
      dd = (DetailData*)si->mData;
      dd->prevDL = -2;
   }
   else
   {
      // we were rendered last time
      dd = (DetailData*)si->mData;
      dd->prevDL = dd->dl;
   }

   dd->shapeInstance = si;
   dd->partInstance = NULL;
   dd->tag = mTag;
   dd->dl = dl;
   dd->pixelSize = -dist; //pixelRadius; BROKEN, but this'll be fine for now
   dd->intraDL = si->getCurrentIntraDetail();

   // add in poly count for preferred detail level
   S32 polyCount = si->getShape()->details[dl].polyCount;
   mPolyCount += polyCount;

   S32 countFirst = 0;
   S32 countMiddle = 0;
   S32 countLast = 0;
   for (S32 i=0; i<MAX_BUMP; i++)
   {
      // duplicate first detail...or...
      bool dup = false;
      if (dl==0 && countFirst<dp->skipFirst)
      {
         countFirst++;
         dup = true;
      }

      // duplicate last detail...or...
      if (dl==si->getShape()->mSmallestVisibleDL && countLast<dp->skipLast)
      {
         countLast++;
         dup = true;
      }

      // duplicate other details...
      if (countMiddle<dp->skipMiddle)
      {
         countMiddle++;
         dup = true;
      }
      else
         countMiddle = 0;

      // find the next detail...
      if (!dup)
      {
         if (dl==si->getShape()->mSmallestVisibleDL)
            dl = -1;
         else
            dl++;
      }

      dd->dls[i] = dl;
      S32 detailPolys = dl>=0 ? si->getShape()->details[dl].polyCount : 0;
      dd->bump[i] = polyCount - detailPolys;
      mBumpPolyCount[i] += dd->bump[i];

      if (dl<0)
      {
         for (S32 j=i+1; j<MAX_BUMP; j++)
         {
            dd->dls[j] = -1;
            dd->bump[j] = polyCount;
            mBumpPolyCount[j] += dd->bump[j];
         }
         break;
      }
   }
}

//---------------------------------------------------------
void DetailManager::selectPotential(TSPartInstance * pi, F32 dist, F32 invScale, const DetailProfile * dp)
{
   AssertFatal(mInPrepRender,"DetailManager::selectPotentialDetails");

   dist *= invScale;
   pi->selectCurrentDetail2(dist);
   S32 dl = pi->getCurrentObjectDetail();
   if (dl<0)
      // don't render
      return;

   DetailData * dd;
   if (!pi->mData || ((DetailData*)pi->mData)->partInstance!=pi)
   {
      // we weren't rendered last time
      // get new detail data and set prevDL to -2 to encode that we're fresh meat...
      pi->mData = getNewDetailData();
      dd = (DetailData*)pi->mData;
      dd->prevDL = -2;
   }
   else
   {
      // we were rendered last time
      dd = (DetailData*)pi->mData;
      dd->prevDL = dd->dl;
   }

   dd->shapeInstance = NULL;
   dd->partInstance = pi;
   dd->tag = mTag;
   dd->dl = dl;
   dd->pixelSize = -dist;
   dd->intraDL = pi->getCurrentIntraDetail();

   // add in poly count for preferred detail level
   S32 polyCount = pi->getPolyCount(dl);
   mPolyCount += polyCount;

   S32 countFirst = 0;
   S32 countMiddle = 0;
   S32 countLast = 0;
   for (S32 i=0; i<MAX_BUMP; i++)
   {
      // duplicate first detail...or...
      bool dup = false;
      if (dl==0 && countFirst<dp->skipFirst)
      {
         countFirst++;
         dup = true;
      }

      // duplicate last detail...or...
      if (dl==pi->getNumDetails()-1 && countLast<dp->skipLast)
      {
         countLast++;
         dup = true;
      }

      // duplicate other details...
      if (countMiddle<dp->skipMiddle)
      {
         countMiddle++;
         dup = true;
      }
      else
         countMiddle = 0;

      // find the next detail...
      if (!dup)
      {
         if (dl==pi->getNumDetails()-1)
            dl = -1;
         else
            dl++;
      }

      dd->dls[i] = dl;
      S32 detailPolys = pi->getPolyCount(dl);
      dd->bump[i] = polyCount - detailPolys;
      mBumpPolyCount[i] += dd->bump[i];

      if (dl<0)
      {
         for (S32 j=i+1; j<MAX_BUMP; j++)
         {
            dd->dls[j] = -1;
            dd->bump[j] = polyCount;
            mBumpPolyCount[j] += dd->bump[j];
         }
         break;
      }
   }
}

//---------------------------------------------------------
bool DetailManager::selectCurrent(TSShapeInstance * si)
{
   AssertFatal(!mInPrepRender,"DetailManager::selectCurrentDetail");

   DetailData * dd = (DetailData*)si->mData;
   if ( !dd || dd->shapeInstance != si)
      // not rendering...
      return false;
   si->setCurrentDetail(dd->dl,dd->intraDL);
   return true;
}

//---------------------------------------------------------
bool DetailManager::selectCurrent(TSPartInstance * pi)
{
   AssertFatal(!mInPrepRender,"DetailManager::selectCurrentDetail");

   DetailData * dd = (DetailData*)pi->mData;
   if ( !dd || dd->partInstance != pi)
      // not rendering...
      return false;
   pi->setCurrentObjectDetail(dd->dl);
   pi->setCurrentIntraDetail(dd->intraDL);
   return true;
}

//---------------------------------------------------------
void DetailManager::computePriority(DetailData * detailData, S32 bump)
{
   S32 oldSize, newSize;
   if (detailData->shapeInstance)
   {
      // shape instance...
      if (bump>0)
         oldSize = (S32)(detailData->dl>=0 ? detailData->shapeInstance->getShape()->details[detailData->dl].size : 0);
      else
         oldSize = (S32)detailData->pixelSize;
      newSize = (S32)(detailData->dls[bump]>=0 ? detailData->shapeInstance->getShape()->details[detailData->dls[bump]].size : 0);
   }
   else
   {
      // part instance...
      AssertFatal(detailData->partInstance,"DetailManager::computePriority");
      if (bump>0)
         oldSize = (S32)detailData->partInstance->getDetailSize(detailData->dl);
      else
         oldSize = (S32)detailData->pixelSize;
      newSize = (S32)detailData->partInstance->getDetailSize(detailData->dls[bump]);
   }

   // priority weighted by both total bump and recent bump (total bump component helps break ties
   // when recent bump has the same effect).
   detailData->priority = 0.5f * (detailData->pixelSize - oldSize) + (oldSize - newSize);

   // try to be consistent between frames...as much as possible
   if (detailData->prevDL<0)
   {
      if (detailData->prevDL==-1)
      {
         // weren't visible last time...penalize for being visible this time, reward for being invisible
         if (detailData->dls[bump]==-1)
            detailData->priority *= MatchPreviousReward;
         else if (detailData->dl!=-1)
            detailData->priority *= NotMatchPreviousPenalty;
      }
   }
   else
   {
      // reward for bumping us down if we are at a higher detail than last time,
      // penalize for bumping us down if our detail is at or lower than last time
      // Note: higher detail --> smaller dl number
      if (detailData->dl > detailData->prevDL)
         detailData->priority *= MatchPreviousReward;
      else
         detailData->priority *= NotMatchPreviousPenalty;
   }
}

//---------------------------------------------------------
DetailManager::DetailData * DetailManager::getNewDetailData()
{
   DetailData * ret;
   if (mFreeDetailData.size())
   {
      ret = mFreeDetailData.last();
      mFreeDetailData.decrement();
   }
   else
      ret = new DetailData;
   mDetailData.push_back(ret);
   return ret;
}