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

#include "platform/platform.h"
#include "dgl/dgl.h"
#include "math/mMath.h"
#include "math/mathIO.h"
#include "console/simBase.h"
#include "console/console.h"
#include "console/consoleTypes.h"
#include "core/bitStream.h"
#include "core/dnet.h"
#include "sim/pathManager.h"
#include "game/game.h"
#include "game/gameConnection.h"
#include "editor/editor.h"

#include "game/pathCamera.h"


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

IMPLEMENT_CO_DATABLOCK_V1(PathCameraData);

void PathCameraData::consoleInit()
{
}

void PathCameraData::initPersistFields()
{
   Parent::initPersistFields();
}

void PathCameraData::packData(BitStream* stream)
{
   Parent::packData(stream);
}

void PathCameraData::unpackData(BitStream* stream)
{
   Parent::unpackData(stream);
}


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

IMPLEMENT_CO_NETOBJECT_V1(PathCamera);

PathCamera::PathCamera()
{
   mNetFlags.clear(Ghostable);
   mTypeMask |= CameraObjectType;
   delta.time = 0;
   delta.timeVec = 0;

   mDataBlock = 0;
   mState = Forward;
   mNodeBase = 0;
   mNodeCount = 0;
   mPosition = 0;
   mTarget = 0;
   mTargetSet = false;

   MatrixF mat(1);
   mat.setPosition(Point3F(0,0,700));
   Parent::setTransform(mat);
}

PathCamera::~PathCamera()
{
}


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

bool PathCamera::onAdd()
{
   if(!Parent::onAdd())
      return false;

   // Initialize from the current transform.
   if (!mNodeCount) {
      QuatF rot(getTransform());
      Point3F pos = getPosition();
      mSpline.removeAll();
      mSpline.push_back(new CameraSpline::Knot(pos,rot,1,
         CameraSpline::Knot::NORMAL, CameraSpline::Knot::SPLINE));
      mNodeCount = 1;
   }

   //
   mObjBox.max = mObjScale;
   mObjBox.min = mObjScale;
   mObjBox.min.neg();
   resetWorldBox();

   if (getContainer())
      getContainer()->addObject(this);

   return true;
}

void PathCamera::onRemove()
{
   if (getContainer())
      getContainer()->removeObject(this);

   Parent::onRemove();
}

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

   scriptOnNewDataBlock();
   return true;
}



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

void PathCamera::onEditorEnable()
{
   mNetFlags.set(Ghostable);
}

void PathCamera::onEditorDisable()
{
   mNetFlags.clear(Ghostable);
}


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

void PathCamera::initPersistFields()
{
   Parent::initPersistFields();
}

void PathCamera::consoleInit()
{
}


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

void PathCamera::processTick(const Move* move)
{
   // client and server
   Parent::processTick(move);

   // Move to new time
   advancePosition(TickMs);

   // Set new position
   MatrixF mat;
   interpolateMat(mPosition,&mat);
   Parent::setTransform(mat);
}

void PathCamera::interpolateTick(F32 dt)
{
   Parent::interpolateTick(dt);
   MatrixF mat;
   interpolateMat(delta.time + (delta.timeVec * dt),&mat);
   Parent::setRenderTransform(mat);
}

void PathCamera::interpolateMat(F32 pos,MatrixF* mat)
{
   CameraSpline::Knot knot;
   mSpline.value(pos - mNodeBase,&knot);
   knot.mRotation.setMatrix(mat);
   mat->setPosition(knot.mPosition);
}

void PathCamera::advancePosition(S32 ms)
{
   delta.timeVec = mPosition;

   // Advance according to current speed
   if (mState == Forward) {
      mPosition = mSpline.advanceTime(mPosition - mNodeBase,ms);
      if (mPosition > mNodeCount - 1)
         mPosition = mNodeCount - 1;
      mPosition += mNodeBase;
      if (mTargetSet && mPosition >= mTarget) {
         mTargetSet = false;
         mPosition = mTarget;
         mState = Stop;
      }
   }
   else
      if (mState == Backward) {
         mPosition = mSpline.advanceTime(mPosition - mNodeBase,-ms);
         if (mPosition < 0)
            mPosition = 0;
         mPosition += mNodeBase;
         if (mTargetSet && mPosition <= mTarget) {
            mTargetSet = false;
            mPosition = mTarget;
            mState = Stop;
         }
      }

   // Script callbacks
   if (int(mPosition) != int(delta.timeVec))
      onNode(int(mPosition));

   // Set frame interpolation
   delta.time = mPosition;
   delta.timeVec -= mPosition;
}


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

void PathCamera::getCameraTransform(F32* pos, MatrixF* mat)
{
   // Overide the ShapeBase method to skip all the first/third person support.
   getRenderEyeTransform(mat);
}


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

void PathCamera::setPosition(F32 pos)
{
   mPosition = mClampF(pos,mNodeBase,mNodeBase + mNodeCount - 1);
   MatrixF mat;
   interpolateMat(mPosition,&mat);
   Parent::setTransform(mat);
   setMaskBits(PositionMask);
}

void PathCamera::setTarget(F32 pos)
{
   mTarget = pos;
   mTargetSet = true;
   if (mTarget > mPosition)
      mState = Forward;
   else
      if (mTarget < mPosition)
         mState = Backward;
      else {
         mTargetSet = false;
         mState = Stop;
      }
   setMaskBits(TargetMask | StateMask);
}

void PathCamera::setState(State s)
{
   mState = s;
   setMaskBits(StateMask);
}


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

void PathCamera::reset(F32 speed)
{
   CameraSpline::Knot *knot = new CameraSpline::Knot;
   mSpline.value(mPosition - mNodeBase,knot);
   if (speed)
      knot->mSpeed = speed;
   mSpline.removeAll();
   mSpline.push_back(knot);

   mNodeBase = 0;
   mNodeCount = 1;
   mPosition = 0;
   mTargetSet = false;
   mState = Forward;
   setMaskBits(StateMask | PositionMask | WindowMask | TargetMask);
}

void PathCamera::pushBack(CameraSpline::Knot *knot)
{
   // Make room at the end
   if (mNodeCount == NodeWindow) {
      delete mSpline.remove(mSpline.getKnot(0));
      mNodeBase++;
   }
   else
      mNodeCount++;

   // Fill in the new node
   mSpline.push_back(knot);
   setMaskBits(WindowMask);

   // Make sure the position doesn't fall off
   if (mPosition < mNodeBase) {
      mPosition = mNodeBase;
      setMaskBits(PositionMask);
   }
}

void PathCamera::pushFront(CameraSpline::Knot *knot)
{
   // Make room at the front
   if (mNodeCount == NodeWindow)
      delete mSpline.remove(mSpline.getKnot(mNodeCount));
   else
      mNodeCount++;
   mNodeBase--;

   // Fill in the new node
   mSpline.push_front(knot);
   setMaskBits(WindowMask);

   // Make sure the position doesn't fall off
   if (mPosition > mNodeBase + (NodeWindow - 1)) {
      mPosition = mNodeBase + (NodeWindow - 1);
      setMaskBits(PositionMask);
   }
}

void PathCamera::popFront()
{
   if (mNodeCount < 2)
      return;

   // Remove the first node. Node base and position are unaffected.
   mNodeCount--;
   delete mSpline.remove(mSpline.getKnot(0));
}


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

void PathCamera::onNode(S32 node)
{
   if (!isGhost())
      Con::executef(mDataBlock,3,"onNode",scriptThis(), Con::getIntArg(node));
}


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

void PathCamera::renderImage(SceneState*, SceneRenderImage*)
{
   if (gEditingMission)
   {
      glPushMatrix();
      dglMultMatrix(&mObjToWorld);
      glScalef(mObjScale.x,mObjScale.y,mObjScale.z);
      wireCube(Point3F(1, 1, 1),Point3F(0,0,0));
      glPopMatrix();
   }
}


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

U32 PathCamera::packUpdate(NetConnection *con, U32 mask, BitStream *stream)
{
   Parent::packUpdate(con,mask,stream);

   if (stream->writeFlag(mask & StateMask))
      stream->writeInt(mState,StateBits);

   if (stream->writeFlag(mask & PositionMask))
      stream->write(mPosition);

   if (stream->writeFlag(mask & TargetMask))
      if (stream->writeFlag(mTargetSet))
         stream->write(mTarget);

   if (stream->writeFlag(mask & WindowMask)) {
      stream->write(mNodeBase);
      stream->write(mNodeCount);
      for (int i = 0; i < mNodeCount; i++) {
         CameraSpline::Knot *knot = mSpline.getKnot(i);
         mathWrite(*stream, knot->mPosition);
         mathWrite(*stream, knot->mRotation);
         stream->write(knot->mSpeed);
         stream->writeInt(knot->mType, CameraSpline::Knot::NUM_TYPE_BITS);
         stream->writeInt(knot->mPath, CameraSpline::Knot::NUM_PATH_BITS);
      }
   }

   // The rest of the data is part of the control object packet update.
   // If we're controlled by this client, we don't need to send it.
   if(stream->writeFlag(getControllingClient() == con && !(mask & InitialUpdateMask)))
      return 0;

   return 0;
}

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

   // StateMask
   if (stream->readFlag())
      mState = stream->readInt(StateBits);

   // PositionMask
   if (stream->readFlag()) {
      stream->read(&mPosition);
      delta.time = mPosition;
      delta.timeVec = 0;
   }

   // TargetMask
   if (stream->readFlag())
      if (mTargetSet = stream->readFlag())
         stream->read(&mTarget);

   // WindowMask
   if (stream->readFlag()) {
      mSpline.removeAll();
      stream->read(&mNodeBase);
      stream->read(&mNodeCount);
      for (int i = 0; i < mNodeCount; i++) {
         CameraSpline::Knot *knot = new CameraSpline::Knot();
         mathRead(*stream, &knot->mPosition);
         mathRead(*stream, &knot->mRotation);
         stream->read(&knot->mSpeed);
         knot->mType = (CameraSpline::Knot::Type)stream->readInt(CameraSpline::Knot::NUM_TYPE_BITS);
         knot->mPath = (CameraSpline::Knot::Path)stream->readInt(CameraSpline::Knot::NUM_PATH_BITS);
         mSpline.push_back(knot);
      }
   }

   // Controlled by the client?
   if (stream->readFlag())
      return;

}


//-----------------------------------------------------------------------------
// Console access methods
//-----------------------------------------------------------------------------

ConsoleMethod(PathCamera,setPosition,void,3, 3,"PathCamera.setPosition(pos);")
{
   object->setPosition(dAtof(argv[2]));
}

ConsoleMethod(PathCamera,setTarget,void,3, 3,"PathCamera.setTarget(pos);")
{
   object->setTarget(dAtof(argv[2]));
}

ConsoleMethod(PathCamera,setState,void,3, 3,"PathCamera.setState({forward,backward,stop});")
{
   if (!dStricmp(argv[2],"forward"))
      object->setState(PathCamera::Forward);
   else
      if (!dStricmp(argv[2],"backward"))
         object->setState(PathCamera::Backward);
      else
         object->setState(PathCamera::Stop);
}

ConsoleMethod(PathCamera,reset,void,2,3,"PathCamera.reset(speed=0);")
{
   object->reset((argc >= 3)? dAtof(argv[2]): 1);
}


static CameraSpline::Knot::Type resolveKnotType(const char *arg)
{
   if (dStricmp(arg, "Position Only") == 0) return CameraSpline::Knot::POSITION_ONLY;
   if (dStricmp(arg, "Kink") == 0) return CameraSpline::Knot::KINK;
   return CameraSpline::Knot::NORMAL;
}

static CameraSpline::Knot::Path resolveKnotPath(const char *arg)
{
   if (dStricmp(arg, "Linear") == 0) return CameraSpline::Knot::LINEAR;
   return CameraSpline::Knot::SPLINE;
}

ConsoleMethod(PathCamera,pushBack,void,6, 6,"PathCamera.pushBack(transform,speed,type,path);")
{
   Point3F pos;
   AngAxisF aa(Point3F(0,0,0),0);
   dSscanf(argv[2], "%g %g %g %g %g %g %g", &pos.x, &pos.y, &pos.z, &aa.axis.x, &aa.axis.y, &aa.axis.z, &aa.angle);
   QuatF rot(aa);
   object->pushBack( new CameraSpline::Knot(pos, rot, dAtof(argv[3]), resolveKnotType(argv[4]), resolveKnotPath(argv[5])) );
}

ConsoleMethod(PathCamera,pushFront,void,6, 6,"PathCamera.pushFront(transform,speed,type,path);")
{
   Point3F pos;
   AngAxisF aa(Point3F(0,0,0),0);
   dSscanf(argv[2], "%g %g %g %g %g %g %g", &pos.x, &pos.y, &pos.z, &aa.axis.x, &aa.axis.y, &aa.axis.z, &aa.angle);
   QuatF rot(aa);
   object->pushFront( new CameraSpline::Knot(pos, rot, dAtof(argv[3]), resolveKnotType(argv[4]), resolveKnotPath(argv[5])) );
}

ConsoleMethod(PathCamera,popFront,void,2, 2,"PathCamera.popFront();")
{
   object->popFront();
}



