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

#include "console/consoleTypes.h"
#include "core/bitStream.h"
#include "game/fx/particleEmitter.h"
#include "game/fx/particleEngine.h"
#include "math/mathIO.h"

IMPLEMENT_CO_DATABLOCK_V1(ParticleEmitterNodeData);
IMPLEMENT_CO_NETOBJECT_V1(ParticleEmitterNode);

//--------------------------------------------------------------------------
//--------------------------------------
//
ParticleEmitterNodeData::ParticleEmitterNodeData()
{
   timeMultiple = 1.0;
}

ParticleEmitterNodeData::~ParticleEmitterNodeData()
{

}


//--------------------------------------------------------------------------
void ParticleEmitterNodeData::initPersistFields()
{
   Parent::initPersistFields();

   addField("timeMultiple", TypeF32, Offset(timeMultiple, ParticleEmitterNodeData));
}


//--------------------------------------------------------------------------
bool ParticleEmitterNodeData::onAdd()
{
   if(!Parent::onAdd())
      return false;

   if (timeMultiple < 0.01 || timeMultiple > 100) {
      Con::warnf("ParticleEmitterNodeData::onAdd(%s): timeMultiple must be between 0.01 and 100", getName());
      timeMultiple = timeMultiple < 0.01 ? 0.01 : 100;
   }

   return true;
}


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

   return true;
}


//--------------------------------------------------------------------------
void ParticleEmitterNodeData::packData(BitStream* stream)
{
   Parent::packData(stream);

   stream->write(timeMultiple);
}

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

   stream->read(&timeMultiple);
}


//--------------------------------------------------------------------------
//--------------------------------------
//
ParticleEmitterNode::ParticleEmitterNode()
{
   // Todo: ScopeAlways?
   mNetFlags.set(Ghostable);
   mTypeMask |= EnvironmentObjectType;

   mEmitterDatablock   = NULL;
   mEmitterDatablockId = 0;
   mEmitter            = NULL;
   mVelocity           = 1.0;
}

ParticleEmitterNode::~ParticleEmitterNode()
{
   //
}

//--------------------------------------------------------------------------
void ParticleEmitterNode::initPersistFields()
{
   Parent::initPersistFields();
   addField("emitter",  TypeParticleEmitterDataPtr, Offset(mEmitterDatablock, ParticleEmitterNode));
   addField("velocity", TypeF32,                    Offset(mVelocity,         ParticleEmitterNode));
}

//--------------------------------------------------------------------------
bool ParticleEmitterNode::onAdd()
{
   if(!Parent::onAdd())
      return false;

   if (!mEmitterDatablock && mEmitterDatablockId != 0)
   {
      if (Sim::findObject(mEmitterDatablockId, mEmitterDatablock) == false)
         Con::errorf(ConsoleLogEntry::General, "ParticleEmitterNode::onAdd: Invalid packet, bad datablockId(mEmitterDatablock): %d", mEmitterDatablockId);
   }

   if (mEmitterDatablock == NULL)
      return false;

   if (isClientObject())
   {
      ParticleEmitter* pEmitter = new ParticleEmitter;
      pEmitter->onNewDataBlock(mEmitterDatablock);
      if (pEmitter->registerObject() == false)
      {
         Con::warnf(ConsoleLogEntry::General, "Could not register base emitter for particle of class: %s", mDataBlock->getName());
         delete pEmitter;
         return false;
      }
      mEmitter = pEmitter;
   }

   mObjBox.min.set(-0.5, -0.5, -0.5);
   mObjBox.max.set( 0.5,  0.5,  0.5);
   resetWorldBox();
   addToScene();

   return true;
}


void ParticleEmitterNode::onRemove()
{
   removeFromScene();

   if (isClientObject())
   {
      mEmitter->deleteWhenEmpty();
      mEmitter = NULL;
   }

   Parent::onRemove();
}

void ParticleEmitterNode::onGroupAdd()
{
   Parent::onGroupAdd();

   // To make sure that we don't orphan our emitter on mission end,
   // let's add it to our parent group, so it will get cleaned up.
   SimGroup* myGroup = getGroup();
   if (myGroup && mEmitter)
      myGroup->addObject(mEmitter);
}

bool ParticleEmitterNode::onNewDataBlock(GameBaseData* dptr)
{
   mDataBlock = dynamic_cast<ParticleEmitterNodeData*>(dptr);
   if (!mDataBlock || !Parent::onNewDataBlock(dptr))
      return false;

   // Todo: Uncomment if this is a "leaf" class
   scriptOnNewDataBlock();
   return true;
}


//--------------------------------------------------------------------------
void ParticleEmitterNode::advanceTime(F32 dt)
{
   Parent::advanceTime(dt);

   Point3F emitPoint, emitVelocity;
   Point3F emitAxis(0, 0, 1);
   getTransform().mulV(emitAxis);
   getTransform().getColumn(3, &emitPoint);
   emitVelocity = emitAxis * mVelocity;

   mEmitter->emitParticles(emitPoint, emitPoint,
                           emitAxis,
                           emitVelocity, (U32)(dt * mDataBlock->timeMultiple * 1000.0f));
}


//--------------------------------------------------------------------------
U32 ParticleEmitterNode::packUpdate(NetConnection* con, U32 mask, BitStream* stream)
{
   U32 retMask = Parent::packUpdate(con, mask, stream);

   mathWrite(*stream, getTransform());
   mathWrite(*stream, getScale());
   if (stream->writeFlag(mEmitterDatablock != NULL)) {
      stream->writeRangedU32(mEmitterDatablock->getId(), DataBlockObjectIdFirst,
                                                         DataBlockObjectIdLast);
   }

   return retMask;
}

void ParticleEmitterNode::unpackUpdate(NetConnection* con, BitStream* stream)
{
   Parent::unpackUpdate(con, stream);

   MatrixF temp;
   Point3F tempScale;
   mathRead(*stream, &temp);
   mathRead(*stream, &tempScale);

   if (stream->readFlag()) {
      mEmitterDatablockId = stream->readRangedU32(DataBlockObjectIdFirst,
                                                  DataBlockObjectIdLast);
   } else {
      mEmitterDatablockId = 0;
   }

   setScale(tempScale);
   setTransform(temp);
}

void ParticleEmitterNode::setEmitterDataBlock(ParticleEmitterData* data)
{
   if (!data)
      return;

   if (mEmitter)
   {
      mEmitterDatablock = data;
      ParticleEmitter* pEmitter = new ParticleEmitter;
      pEmitter->onNewDataBlock(mEmitterDatablock);
      if (pEmitter->registerObject() == false) {
         Con::warnf(ConsoleLogEntry::General, "Could not register base emitter for particle of class: %s", mDataBlock->getName());
         delete pEmitter;
         pEmitter = NULL;
      }
      if (pEmitter)
      {
         mEmitter->deleteWhenEmpty();
         mEmitter = pEmitter;
      }
   }
}

ConsoleMethod(ParticleEmitterNode, setEmitterDataBlock, void, 3, 3, "(data)")
{
   ParticleEmitterData* data = dynamic_cast<ParticleEmitterData*>(Sim::findObject(dAtoi(argv[2])));
   if (!data)
      data = dynamic_cast<ParticleEmitterData*>(Sim::findObject(argv[2]));

   if (data)
      object->setEmitterDataBlock(data);
}