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

#include "sim/decalManager.h"
#include "dgl/dgl.h"
#include "sceneGraph/sceneGraph.h"
#include "sceneGraph/sceneState.h"
#include "ts/tsShapeInstance.h"
#include "core/bitStream.h"
#include "console/consoleTypes.h"

bool DecalManager::smDecalsOn = true;
bool DecalManager::sgThisIsSelfIlluminated = false;
bool DecalManager::sgLastWasSelfIlluminated = false;
const U32 DecalManager::csmFreePoolBlockSize = 256;
U32       DecalManager::smMaxNumDecals = 256;
U32       DecalManager::smDecalTimeout = 5000;

DecalManager* gDecalManager = NULL;
IMPLEMENT_CONOBJECT(DecalManager);
IMPLEMENT_CO_DATABLOCK_V1(DecalData);

namespace {

int QSORT_CALLBACK cmpDecalInstance(const void* p1, const void* p2)
{
   const DecalInstance** pd1 = (const DecalInstance**)p1;
   const DecalInstance** pd2 = (const DecalInstance**)p2;

   return int(((char *)(*pd1)->decalData) - ((char *)(*pd2)->decalData));
}

} // namespace {}


//--------------------------------------------------------------------------
DecalData::DecalData()
{
   sizeX = 1;
   sizeY = 1;
   textureName = "";
   
	selfIlluminated = false;
	lifeSpan = DecalManager::smDecalTimeout;
}

DecalData::~DecalData()
{
   if(gDecalManager)
      gDecalManager->dataDeleted(this);
}


void DecalData::packData(BitStream* stream)
{
   Parent::packData(stream);

   stream->write(sizeX);
   stream->write(sizeY);
   stream->writeString(textureName);
   
	stream->write(selfIlluminated);
	stream->write(lifeSpan);
}

void DecalData::unpackData(BitStream* stream)
{
   Parent::unpackData(stream);

   stream->read(&sizeX);
   stream->read(&sizeY);
   textureName = stream->readSTString();

	stream->read(&selfIlluminated);
	stream->read(&lifeSpan);
}

bool DecalData::preload(bool server, char errorBuffer[256])
{
   if (Parent::preload(server, errorBuffer) == false)
      return false;

   if (sizeX < 0.0) {
      Con::warnf("DecalData::preload: sizeX < 0");
      sizeX = 0;
   }
   if (sizeY < 0.0) {
      Con::warnf("DecalData::preload: sizeX < 0");
      sizeY = 0;
   }
   if (textureName == NULL || textureName[0] == '\0') {
      Con::errorf("No texture name for decal!");
      return false;
   }

   if (!server) {
      textureHandle = TextureHandle(textureName, MeshTexture);
      if (textureHandle.getGLName() == 0) {
         Con::errorf("Unable to load texture: %s for decal!", textureName);
         return false;
      }
   }

   return true;
}

IMPLEMENT_CONSOLETYPE(DecalData)
IMPLEMENT_SETDATATYPE(DecalData)
IMPLEMENT_GETDATATYPE(DecalData)

void DecalData::initPersistFields()
{
   addField("sizeX",       TypeF32,       Offset(sizeX,       DecalData));
   addField("sizeY",       TypeF32,       Offset(sizeY,       DecalData));
   addField("textureName", TypeFilename,  Offset(textureName, DecalData));

	addField("SelfIlluminated", TypeBool, Offset(selfIlluminated, DecalData));
	addField("LifeSpan", TypeS32, Offset(lifeSpan, DecalData));
}

DecalManager::DecalManager()
{
   mTypeMask |= DecalManagerObjectType;

   mObjBox.min.set(-1e7, -1e7, -1e7);
   mObjBox.max.set( 1e7,  1e7,  1e7);
   mWorldBox.min.set(-1e7, -1e7, -1e7);
   mWorldBox.max.set( 1e7,  1e7,  1e7);

   mFreePool = NULL;
   VECTOR_SET_ASSOCIATION(mDecalQueue);
   VECTOR_SET_ASSOCIATION(mFreePoolBlocks);
}


DecalManager::~DecalManager()
{
   mFreePool = NULL;
   for (S32 i = 0; i < mFreePoolBlocks.size(); i++)
   {
      delete [] mFreePoolBlocks[i];
   }
   mDecalQueue.clear();
}


DecalInstance* DecalManager::allocateDecalInstance()
{
   if (mFreePool == NULL)
   {
      // Allocate a new block of decals
      mFreePoolBlocks.push_back(new DecalInstance[csmFreePoolBlockSize]);

      // Init them onto the free pool chain
      DecalInstance* pNewInstances = mFreePoolBlocks.last();
      for (U32 i = 0; i < csmFreePoolBlockSize - 1; i++)
         pNewInstances[i].next = &pNewInstances[i + 1];
      pNewInstances[csmFreePoolBlockSize - 1].next = NULL;
      mFreePool = pNewInstances;
   }
   AssertFatal(mFreePool != NULL, "Error, should always have a free pool available here!");

   DecalInstance* pRet = mFreePool;
   mFreePool = pRet->next;
   pRet->next = NULL;
   return pRet;
}


void DecalManager::freeDecalInstance(DecalInstance* trash)
{
   AssertFatal(trash != NULL, "Error, no trash pointer to free!");

   trash->next = mFreePool;
   mFreePool = trash;
}


void DecalManager::dataDeleted(DecalData *data)
{
   for(S32 i = mDecalQueue.size() - 1; i >= 0; i--)
   {
      DecalInstance *inst = mDecalQueue[i];
      if(inst->decalData == data)
      {
         freeDecalInstance(inst);
         mDecalQueue.erase(U32(i));
      }
   }
}

void DecalManager::consoleInit()
{
   Con::addVariable("$pref::decalsOn",     TypeBool, &smDecalsOn);
   Con::addVariable("$pref::Decal::maxNumDecals", TypeS32, &smMaxNumDecals);
   Con::addVariable("$pref::Decal::decalTimeout", TypeS32, &smDecalTimeout);
}

void DecalManager::addDecal(const Point3F& pos,
                            Point3F normal,
                            DecalData* decalData)
{
   if (smMaxNumDecals == 0)
      return;

   // DMM: Rework this, should be based on time
   if(mDecalQueue.size() >= smMaxNumDecals)
   {
      findSpace();
   }

   Point3F vecX, vecY;
   DecalInstance* newDecal = allocateDecalInstance();
   newDecal->decalData = decalData;
   newDecal->allocTime = Platform::getVirtualMilliseconds();

   if(mFabs(normal.z) > 0.9f)
      mCross(normal, Point3F(0.0f, 1.0f, 0.0f), &vecX);
   else
      mCross(normal, Point3F(0.0f, 0.0f, 1.0f), &vecX);

   mCross(vecX, normal, &vecY);

   normal.normalizeSafe();
   Point3F position = Point3F(pos.x + (normal.x * 0.008), pos.y + (normal.y * 0.008), pos.z + (normal.z * 0.008));

   vecX.normalizeSafe();
   vecY.normalizeSafe();

   vecX *= decalData->sizeX;
   vecY *= decalData->sizeY;

   newDecal->point[0] = position + vecX + vecY;
   newDecal->point[1] = position + vecX - vecY;
   newDecal->point[2] = position - vecX - vecY;
   newDecal->point[3] = position - vecX + vecY;

   mDecalQueue.push_back(newDecal);
   mQueueDirty = true;
}

void DecalManager::addDecal(const Point3F& pos,
                            const Point3F& rot,
                            Point3F normal,
                            DecalData* decalData)
{
   if (smMaxNumDecals == 0)
      return;

    addDecal( pos, rot, normal, Point3F( 1, 1, 1 ), decalData );
}

void DecalManager::addDecal(const Point3F& pos,
                            const Point3F& rot,
                            Point3F normal,
                            const Point3F& scale,
                            DecalData* decalData)
{
   if (smMaxNumDecals == 0)
      return;

   if(mDot(rot, normal) < 0.98)
   {
      // DMM: Rework this, should be based on time
      if(mDecalQueue.size() >= smMaxNumDecals)
      {
         findSpace();
      }

      Point3F vecX, vecY;
      DecalInstance* newDecal = allocateDecalInstance();
      newDecal->decalData = decalData;
      newDecal->allocTime = Platform::getVirtualMilliseconds();

      mCross(rot, normal, &vecX);
      mCross(normal, vecX, &vecY);

      normal.normalize();
      Point3F position = Point3F(pos.x + (normal.x * 0.008), pos.y + (normal.y * 0.008), pos.z + (normal.z * 0.008));

      vecX.normalize();
      vecX.convolve( scale );
      vecY.normalize();
      vecY.convolve( scale );

      vecX *= decalData->sizeX;
      vecY *= decalData->sizeY;

      newDecal->point[0] = position + vecX + vecY;
      newDecal->point[1] = position + vecX - vecY;
      newDecal->point[2] = position - vecX - vecY;
      newDecal->point[3] = position - vecX + vecY;

      mDecalQueue.push_back(newDecal);
      mQueueDirty = true;
   }
}

bool DecalManager::prepRenderImage(SceneState* state, const U32 stateKey,
                                   const U32 /*startZone*/, const bool /*modifyBaseState*/)
{
   if (!smDecalsOn) return false;

   if (isLastState(state, stateKey))
      return false;
   setLastState(state, stateKey);

   if (mDecalQueue.size() == 0)
      return false;

   // This should be sufficient for most objects that don't manage zones, and
   //  don't need to return a specialized RenderImage...
   SceneRenderImage* image = new SceneRenderImage;
   image->obj = this;
   image->isTranslucent = true;
   image->sortType      = SceneRenderImage::BeginSort;
   state->insertRenderImage(image);

   U32 currMs = Platform::getVirtualMilliseconds();
   for (S32 i = mDecalQueue.size() - 1; i >= 0; i--)
   {
      U32 age = currMs - mDecalQueue[i]->allocTime;
	  U32 timeout = mDecalQueue[i]->decalData->lifeSpan;
      if (age > timeout)
      {
         freeDecalInstance(mDecalQueue[i]);
         mDecalQueue.erase(i);
      }
      else if (age > ((3 * timeout) / 4))
      {
         mDecalQueue[i]->fade = 1.0f - (F32(age - ((3 * timeout) / 4)) / F32(timeout / 4));
      }
      else
      {
         mDecalQueue[i]->fade = 1.0f;
      }
   }

   if (mQueueDirty == true)
   {
      // Sort the decals based on the data pointers...
      dQsort(mDecalQueue.address(),
             mDecalQueue.size(),
             sizeof(DecalInstance*),
             cmpDecalInstance);
      mQueueDirty = false;
   }

   return false;
}

void DecalManager::renderObject(SceneState* state, SceneRenderImage*)
{
   if (!smDecalsOn) return;

   AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on entry");

   RectI viewport;
   glMatrixMode(GL_PROJECTION);
   glPushMatrix();
   dglGetViewport(&viewport);

   state->setupBaseProjection();

   glMatrixMode(GL_MODELVIEW);
   glPushMatrix();

   renderDecal();

   glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);

   glMatrixMode(GL_MODELVIEW);
   glPopMatrix();

   glMatrixMode(GL_PROJECTION);
   glPopMatrix();
   glMatrixMode(GL_MODELVIEW);
   dglSetViewport(viewport);

   AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on exit");
}

struct DecalVert
{
   Point3F vert;
   Point2F texCoord;
   ColorI color;
};

void DecalManager::renderDecal()
{
   static Vector<DecalVert> renderVerts(__FILE__, __LINE__);
   static Vector<U16> indices(__FILE__, __LINE__);
   renderVerts.clear();
   indices.clear();
   U32 decalCount = 0;
   
   glEnableClientState(GL_VERTEX_ARRAY);
   glEnableClientState(GL_TEXTURE_COORD_ARRAY);
   glEnableClientState(GL_COLOR_ARRAY);
    
   glEnable(GL_TEXTURE_2D);
   glEnable(GL_BLEND);
   glEnable(GL_ALPHA_TEST);
   glDepthMask(GL_FALSE);
   glAlphaFunc(GL_GREATER, 0.1f);
   glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
   glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

   sgThisIsSelfIlluminated = false;
   sgLastWasSelfIlluminated = false;
   glEnable(GL_POLYGON_OFFSET_FILL);
   glPolygonOffset(-1,-1);
   glDepthMask(GL_FALSE);
	
   DecalData* pLastData = NULL;
   for (S32 x = 0; x < mDecalQueue.size(); x++)
   {
      if (mDecalQueue[x]->decalData != pLastData)
      {         
         glVertexPointer(3, GL_FLOAT, sizeof(DecalVert), &(renderVerts[0].vert));
         glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(DecalVert), &(renderVerts[0].color));
         glTexCoordPointer(2, GL_FLOAT, sizeof(DecalVert), &(renderVerts[0].texCoord));
      
		 glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_SHORT, indices.address());

		 renderVerts.clear();
		 indices.clear();
		 decalCount = 0;

		 glBindTexture(GL_TEXTURE_2D, mDecalQueue[x]->decalData->textureHandle.getGLName());
		 pLastData = mDecalQueue[x]->decalData;
      }

	  sgThisIsSelfIlluminated = mDecalQueue[x]->decalData->selfIlluminated;
	  if(sgThisIsSelfIlluminated != sgLastWasSelfIlluminated)
	  {     
         glVertexPointer(3, GL_FLOAT, sizeof(DecalVert), &(renderVerts[0].vert));
         glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(DecalVert), &(renderVerts[0].color));
         glTexCoordPointer(2, GL_FLOAT, sizeof(DecalVert), &(renderVerts[0].texCoord));
      
         glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_SHORT, indices.address());
         
         renderVerts.clear();
         indices.clear();
         decalCount = 0;
        
		 if(sgThisIsSelfIlluminated)
		    glBlendFunc(GL_SRC_ALPHA, GL_ONE);
		 else
		    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		 sgLastWasSelfIlluminated = sgThisIsSelfIlluminated;
	  }
     
     renderVerts.increment(4);
     indices.increment(6);
     DecalVert *verts = &(renderVerts[decalCount * 4]);
     U16 *ind = &(indices[decalCount * 6]);
     
     const U8	fade = mDecalQueue[x]->fade * 255;
     
     verts[0].vert = mDecalQueue[x]->point[3];
     verts[0].texCoord.set(0, 0);
     verts[0].color.set(255, 255, 255, fade);
     ind[0] = decalCount * 4;
     ind[5] = decalCount * 4;
      
     verts[1].vert = mDecalQueue[x]->point[2];
     verts[1].texCoord.set(0, 1);
     verts[1].color.set(255, 255, 255, fade);
     ind[1] = decalCount * 4 + 1;
      
     verts[2].vert = mDecalQueue[x]->point[1];
     verts[2].texCoord.set(1, 1);
     verts[2].color.set(255, 255, 255, fade);
     ind[2] = decalCount * 4 + 2;
     ind[3] = decalCount * 4 + 2;
      
     verts[3].vert = mDecalQueue[x]->point[0];
     verts[3].texCoord.set(1, 0);
     verts[3].color.set(255, 255, 255, fade);
     ind[4] = decalCount * 4 + 3;
      
     decalCount++;   
   }
   
   if(decalCount)
   {
      glVertexPointer(3, GL_FLOAT, sizeof(DecalVert), &(renderVerts[0].vert));
      glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(DecalVert), &(renderVerts[0].color));
	  glTexCoordPointer(2, GL_FLOAT, sizeof(DecalVert), &(renderVerts[0].texCoord));
      
      glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_SHORT, indices.address());
   }
   
   glDisableClientState(GL_VERTEX_ARRAY);
   glDisableClientState(GL_COLOR_ARRAY);
   glDisableClientState(GL_TEXTURE_COORD_ARRAY);

   glDisable(GL_POLYGON_OFFSET_FILL);
   glDepthMask(GL_TRUE);

   glDepthMask(GL_TRUE);
   glDisable(GL_BLEND);
   glDisable(GL_TEXTURE_2D);
   glDisable(GL_ALPHA_TEST);
}

void DecalManager::findSpace()
{
	S32 besttime = S32_MAX;
	U32 bestindex = 0;
	DecalInstance *bestdecal = NULL;

	U32 time = Platform::getVirtualMilliseconds();

	for(U32 i=0; i<mDecalQueue.size(); i++)
	{
		DecalInstance *inst = mDecalQueue[i];
		U32 age = time - inst->allocTime;
		U32 timeleft = inst->decalData->lifeSpan - age;
		if(besttime > timeleft)
		{
			besttime = timeleft;
			bestindex = i;
			bestdecal = inst;
		}
	}

	AssertFatal((bestdecal), "No good decals?");

	mDecalQueue.erase_fast(bestindex);
	freeDecalInstance(bestdecal);
}

void DecalManager::addDecal(const Point3F& pos, const Point3F& rot, Point3F normal,
							  const Point3F& scale, DecalData *decaldata, U32 ownerid)
{
	if(smMaxNumDecals == 0)
		return;

	if(mDot(rot, normal) < 0.98)
	{
		if(mDecalQueue.size() >= smMaxNumDecals)
			findSpace();

		Point3F vecX, vecY;
		DecalInstance* newDecal = allocateDecalInstance();
		newDecal->decalData = decaldata;
		newDecal->allocTime = Platform::getVirtualMilliseconds();
		newDecal->ownerId = ownerid;

		mCross(rot, normal, &vecX);
		mCross(normal, vecX, &vecY);

		normal.normalize();
		Point3F position = Point3F(pos.x + (normal.x * 0.008), pos.y + (normal.y * 0.008), pos.z + (normal.z * 0.008));

		vecX.normalize();
		vecX.convolve( scale );
		vecY.normalize();
		vecY.convolve( scale );

		vecX *= decaldata->sizeX;
		vecY *= decaldata->sizeY;

		newDecal->point[0] = position + vecX + vecY;
		newDecal->point[1] = position + vecX - vecY;
		newDecal->point[2] = position - vecX - vecY;
		newDecal->point[3] = position - vecX + vecY;

		mDecalQueue.push_back(newDecal);
		mQueueDirty = true;
	}
}

void DecalManager::ageDecal(U32 ownerid)
{
	for(U32 i=0; i<mDecalQueue.size(); i++)
	{
		DecalInstance *inst = mDecalQueue[i];
		if(inst->ownerId == ownerid)
		{
			freeDecalInstance(inst);
			mDecalQueue.erase(U32(i));
		}
	}
}