//-----------------------------------------------------------------------------
// Torque Shader Engine - JLAKER
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------

#ifndef _PROJECTILE_H_
#include "game/projectile.h"
#endif

#include "sceneGraph/sceneState.h"
#include "sceneGraph/sceneGraph.h"
#include "console/consoleTypes.h"
#include "console/typeValidators.h"
#include "core/bitStream.h"
#include "game/fx/explosion.h"
#include "game/shapeBase.h"
#include "ts/tsShapeInstance.h"

#include "gfx/gfxDevice.h"
#include "gfx/PrimBuilder.h"	// Used for debug / mission edit rendering

#include "game/gameConnection.h"

#include "audio/audio.h"
#include "math/mathUtils.h"
#include "math/mathIO.h"
#include "sim/netConnection.h"
#include "game/fx/particleEmitter.h"
#include "renderInstance/renderInstMgr.h"

class ExplosionData;
class ShapeBase;
class TSShapeInstance;
class TSThread;

DefineConsoleType( TypeLaserDataPtr )

//--------------------------------------------------------------------------
class LaserData : public ProjectileData
{
   typedef ProjectileData Parent;
      
public:

   F32 beamStartRadius;
   F32 beamEndRadius;
   StringTableEntry materialList;
   S32 interval;

   LaserData();

   void packData(BitStream*);
   void unpackData(BitStream*);

   static void initPersistFields();
   DECLARE_CONOBJECT(LaserData);
};

IMPLEMENT_CO_DATABLOCK_V1(LaserData);
//--------------------------------------------------------------------------
class Laser : public Projectile
{
   typedef Projectile Parent;
   LaserData* mDataBlock;

protected:
   GFXTexHandle  mTextureHandles[20];
   MaterialList  mMaterialList;
   S32           mNumTextures;
   S32           mIndex;
   S32           mLastTime;
   F32			  mRange;

   void loadDml();

   bool onAdd();
   bool onNewDataBlock(GameBaseData*);
   void processTick(const Move* move);
   U32  packUpdate  (NetConnection *conn, U32 mask, BitStream *stream);
   void unpackUpdate(NetConnection *conn,           BitStream *stream);

   // Rendering
   bool prepRenderImage(SceneState*, const U32, const U32, const bool);
   //void renderObject(SceneState*, SceneRenderImage*);
	void renderObject(SceneState* state, RenderInst *ri);

public:

   static void initPersistFields();

   Laser();
   ~Laser();

   DECLARE_CONOBJECT(Laser);
};

IMPLEMENT_CO_NETOBJECT_V1(Laser);
//--------------------------------------------------------------------------
//
LaserData::LaserData()
{
   ProjectileData::ProjectileData();

   SourceIdTimeoutTicks = 4095;

   beamStartRadius = 0.0;
   beamEndRadius = 0.0;
   materialList = NULL;
   interval = 0;
}

//--------------------------------------------------------------------------

void LaserData::initPersistFields()
{
   Parent::initPersistFields();

   addNamedField(beamStartRadius, TypeF32, LaserData);
   addNamedField(beamEndRadius, TypeF32, LaserData);
   addNamedField(materialList, TypeFilename, LaserData);
   addNamedField(interval, TypeS32, LaserData);
}

//--------------------------------------------------------------------------
void LaserData::packData(BitStream* stream)
{
   Parent::packData(stream);

   stream->writeFloat(beamStartRadius/20.0, 8);
   stream->writeFloat(beamEndRadius/20.0, 8);
   stream->writeString(materialList);
   stream->writeInt(interval, 32);
}

void LaserData::unpackData(BitStream* stream)
{
   Parent::unpackData(stream);

   beamStartRadius = stream->readFloat(8) * 20;
   beamEndRadius = stream->readFloat(8) * 20;
   materialList = StringTable->insert(stream->readSTString());
   interval = stream->readInt(32);
}


//--------------------------------------------------------------------------
Laser::Laser()
{
   Projectile::Projectile();

   mLastTime = 0;
   mIndex = 0;
   mRange = 0;
}

Laser::~Laser()
{
}

//--------------------------------------------------------------------------
void Laser::initPersistFields()
{
   Parent::initPersistFields();
   addField("range", TypeF32, Offset(mRange, Laser));
}

//--------------------------------------------------------------------------
U32 Laser::packUpdate(NetConnection* con, U32 mask, BitStream* stream)
{
   U32 retMask = Parent::packUpdate(con, mask, stream);

   if (stream->writeFlag(mask & GameBase::InitialUpdateMask))
   {
      // Initial update
      stream->write(mRange);
   }
   return retMask;
}

void Laser::unpackUpdate(NetConnection* con, BitStream* stream)
{
   Parent::unpackUpdate(con, stream);

   if (stream->readFlag())
   {
      // initial update
      stream->read(&mRange);
   }
}

//--------------------------------------------------------------------------
void Laser::loadDml()
{
   S32 x;
   mNumTextures = 0;      
   Stream *stream = ResourceManager->openStream(mDataBlock->materialList);

   if(stream)
   {
      // match this code to the new sky loading code
      char path[1024], *p;
      dStrcpy(path, mDataBlock->materialList);
      if ((p = dStrrchr(path, '/')) != NULL)
         *p = 0;

      mMaterialList.read(*stream);
      ResourceManager->closeStream(stream);
      mMaterialList.load(path);
      for(x = 0; x < mMaterialList.size(); ++x, ++mNumTextures)
         mTextureHandles[x] = mMaterialList.getMaterial(x); 
   }
	else
	{
		Con::warnf("Laser material list is missing: %s", mMaterialList);
	}
}

//--------------------------------------------------------------------------
bool Laser::onAdd()
{
   if(!Parent::onAdd())
      return false;

   if (isClientObject())
   {
	  if(mDataBlock->materialList[0])
         loadDml();
   }
      
   return true;
}

bool Laser::onNewDataBlock(GameBaseData* dptr)
{
   mDataBlock = dynamic_cast<LaserData*>(dptr);
   if (!mDataBlock || !Parent::onNewDataBlock(dptr))
      return false;

	//scriptOnNewDataBlock();

   return true;
}

//----------------------------------------------------------------------------

class ObjectDeleteEvent : public SimEvent
{
public:
   void process(SimObject *obj)
   {
      obj->deleteObject();
   }
};

//--------------------------------------------------------------------------
void Laser::processTick(const Move* move)
{

   mCurrTick++;

   if (isServerObject() && mCurrTick >= mDataBlock->lifetime)
   {
      deleteObject();
      return;
   }
   else if (mHidden == true)
      return;

   if(mSourceObject) {

      Point3F newPosition;

      Point3F mMuzzlePosition;
		Point3F mMuzzleVector;
      MatrixF muzzleTrans;
      mSourceObject->getMuzzleTransform(mSourceObjectSlot, &muzzleTrans);
		muzzleTrans.getColumn(3, &mMuzzlePosition);
      muzzleTrans.getColumn(1, &mMuzzleVector);
 
		newPosition = mMuzzleVector * mRange;
      newPosition += mMuzzlePosition;

      mSourceObject->disableCollision();

      RayInfo rInfo;
      if (getContainer()->castRay(mMuzzlePosition, newPosition, csmDynamicCollisionMask | csmStaticCollisionMask, &rInfo) == true) {

         // Next order of business: do we explode on this hit?
         if (mCurrTick > mDataBlock->armingDelay) {
            MatrixF xform(true);
            xform.setColumn(3, rInfo.point);
            setTransform(xform);
            newPosition    = rInfo.point;

            // Get the object type before the onCollision call, in case
            // the object is destroyed.
            U32 objectType = rInfo.object->getType();

            mSourceObject->enableCollision();
            onCollision(rInfo.point, rInfo.normal, rInfo.object);
            mSourceObject->disableCollision();
         }

      }

      mSourceObject->enableCollision();

      if(isClientObject())
      {
         emitParticles(mCurrPosition, newPosition, Point3F(1, 1, 1), TickMs * 4);
         updateSound();
      }

      mCurrDeltaBase = newPosition;
      mCurrBackDelta = mCurrPosition - newPosition;
      mCurrPosition = newPosition;

      MatrixF xform(true);
      xform.setColumn(3, mCurrPosition);
      setTransform(xform);

   } else {
      deleteObject();
   }

}

//--------------------------------------------------------------------------
bool Laser::prepRenderImage(SceneState* state, const U32 stateKey,
                                       const U32 /*startZone*/, const bool /*modifyBaseState*/)
{
   if (isLastState(state, stateKey))
      return false;
   setLastState(state, stateKey);

   if (mHidden == true || mFadeValue <= (1.0/255.0))
      return false;

   // Is Object Rendered?
   if (state->isObjectRendered(this))
   {	   
	   RenderInst *ri = gRenderInstManager.allocInst();
	   ri->obj = this;
	   ri->state = state;
	   ri->type = RenderInstManager::RIT_ObjectTranslucent;
	   ri->translucent = true;
	   ri->calcSortPoint(this, state->getCameraPosition());
	   gRenderInstManager.addInst(ri);
   }

   return false;
}


static ColorF cubeColors[8] = {
   ColorF(0, 0, 0), ColorF(1, 0, 0), ColorF(0, 1, 0), ColorF(0, 0, 1),
   ColorF(1, 1, 0), ColorF(1, 0, 1), ColorF(0, 1, 1), ColorF(1, 1, 1)
};

static Point3F cubePoints[8] = {
   Point3F(-1, -1, -1), Point3F(-1, -1,  1), Point3F(-1,  1, -1), Point3F(-1,  1,  1),
   Point3F( 1, -1, -1), Point3F( 1, -1,  1), Point3F( 1,  1, -1), Point3F( 1,  1,  1)
};

static U32 cubeFaces[6][4] = {
   { 0, 2, 6, 4 }, { 0, 2, 3, 1 }, { 0, 1, 5, 4 },
   { 3, 2, 6, 7 }, { 7, 6, 4, 5 }, { 3, 7, 5, 1 }
};

void Laser::renderObject(SceneState* state, RenderInst *ri)
{
	RectI viewport = GFX->getViewport();
	MatrixF projection = GFX->getProjectionMatrix();
	MatrixF world = GFX->getWorldMatrix();
	MatrixF view = GFX->getViewMatrix();

	state->setupBaseProjection();

	// Uncomment this if this is a "simple" (non-zone managing) object
	GFX->setProjectionMatrix(projection);

   if(mProjectileShape)
   {
      AssertFatal(mProjectileShape != NULL,
                  "Laser::renderObject: Error, projectile shape should always be present in renderObject");
      mProjectileShape->selectCurrentDetail();
      mProjectileShape->animate();

      Point3F cameraOffset;
      mObjToWorld.getColumn(3,&cameraOffset);
      cameraOffset -= state->getCameraPosition();
      F32 fogAmount = state->getHazeAndFog(cameraOffset.len(),cameraOffset.z);

      if (mFadeValue == 1.0) {
         mProjectileShape->setupFog(fogAmount, state->getFogColor());
      } else {
         mProjectileShape->setupFog(0.0, state->getFogColor());
         mProjectileShape->setAlphaAlways(mFadeValue * (1.0 - fogAmount));
      }
      mProjectileShape->render();
   }

	GFX->setAlphaBlendEnable(false);
	GFX->setLightingEnable(false);

	GFX->setTextureStageColorOp(0, GFXTOPDisable);

	GFX->setWorldMatrix(world);
	GFX->setViewMatrix(view);
	GFX->setProjectionMatrix(projection);
	GFX->setViewport(viewport);

	S32 thisTime = Sim::getCurrentTime();
	S32 timeDelta = thisTime - mLastTime;
	if (timeDelta > mDataBlock->interval)
	{
	 mIndex ++;
	 if (mIndex >= mNumTextures)
		mIndex = 0;
	}

	Point3F mMuzzlePosition;
	Point3F mMuzzleVector;

	if (bool(mSourceObject))
	{
	 MatrixF muzzleTrans;
	 mSourceObject->getMuzzleTransform(mSourceObjectSlot, &muzzleTrans);
	 muzzleTrans.getColumn(3, &mMuzzlePosition);
	 mSourceObject->getMuzzleVector(mSourceObjectSlot, &mMuzzleVector);
	}

	// Sang -> 'billboarding' calculations
	Point3F clientView = state->getCameraPosition();
	Point3F clientVec = clientView - mMuzzlePosition;
	clientVec.normalize();

	Point3F crossA, crossB;

	mCross(clientVec, mMuzzleVector, crossA);
	mCross(mMuzzleVector, clientVec, crossB);
	crossA.normalize();
	crossB.normalize();

	Point3F sPt1, sPt2, ePt1, ePt2;

	sPt1 = (crossA * mDataBlock->beamStartRadius) + mMuzzlePosition;
	sPt2 = (crossB * mDataBlock->beamStartRadius) + mMuzzlePosition;
	ePt1 = (crossA * mDataBlock->beamEndRadius) + mCurrPosition;
	ePt2 = (crossB * mDataBlock->beamEndRadius) + mCurrPosition;

 // Setup our rendering state (alpha blending).
   //glEnable(GL_BLEND);
	GFX->setAlphaBlendEnable(true);

   //glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	GFX->setSrcBlend(GFXBlendSrcAlpha);
	GFX->setDestBlend(GFXBlendInvSrcAlpha);

   // Enable Texturing.
   //glEnable(GL_TEXTURE_2D);  ---> Check tsLastDetail.cpp for reference
	GFX->setTextureStageColorOp(0, GFXTOPModulate);
	GFX->setTextureStageColorOp(1, GFXTOPDisable);

   // Select the objects' texture.
 //glBindTexture(GL_TEXTURE_2D, mTextureHandles[mIndex].getGLName());  ---> Check fxFoilageReplicator.cpp for reference
	GFX->setTexture(0, mTextureHandles[mIndex]);

	// Set Colour/Alpha.
	PrimBuild::color4f(1,1,1,1);
	
	//glBegin(GL_QUADS);  ---> Check fxlight.cpp for reference
	PrimBuild::begin(GFXTriangleFan, 4);
	PrimBuild::texCoord2f(0, 0); 
	PrimBuild::vertex3f(sPt1.x, sPt1.y, sPt1.z);
	PrimBuild::texCoord2f(1, 0);
	PrimBuild::vertex3f(sPt2.x, sPt2.y, sPt2.z);
	PrimBuild::texCoord2f(1, 1);
	PrimBuild::vertex3f(ePt2.x, ePt2.y, ePt2.z);
	PrimBuild::texCoord2f(0, 1);
	PrimBuild::vertex3f(ePt1.x, ePt1.y, ePt1.z);
	PrimBuild::end();                                   
       
	// Restore our canonical rendering state.
	GFX->setAlphaBlendEnable(false);
	GFX->setLightingEnable(false);
	
	GFX->disableShaders();		// this fixes editor issue, or does it....


}
