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

#include "platform/platform.h"
#include "core/dnet.h"
#include "console/consoleTypes.h"
#include "console/simBase.h"
#include "game/gameConnection.h"
#include "game/shapeBase.h"
#include "core/bitStream.h"
#include "sim/pathManager.h"
#include "audio/audio.h"
#include "game/game.h"
#include "sceneGraph/sceneGraph.h"
#include "game/gameConnectionEvents.h"

F32 MoveManager::mForwardAction = 0;
F32 MoveManager::mBackwardAction = 0;
F32 MoveManager::mUpAction = 0;
F32 MoveManager::mDownAction = 0;
F32 MoveManager::mLeftAction = 0;
F32 MoveManager::mRightAction = 0;

bool MoveManager::mFreeLook = false;
F32 MoveManager::mPitch = 0;
F32 MoveManager::mYaw = 0;
F32 MoveManager::mRoll = 0;

F32 MoveManager::mPitchUpSpeed = 0;
F32 MoveManager::mPitchDownSpeed = 0;
F32 MoveManager::mYawLeftSpeed = 0;
F32 MoveManager::mYawRightSpeed = 0;
F32 MoveManager::mRollLeftSpeed = 0;
F32 MoveManager::mRollRightSpeed = 0;

U32 MoveManager::mTriggerCount[MaxTriggerKeys] = { 0, };
U32 MoveManager::mPrevTriggerCount[MaxTriggerKeys] = { 0, };

#define MAX_MOVE_PACKET_SENDS 4

const Move NullMove =
{
   16,16,16,
   0,0,0,
   0,0,0,   // x,y,z
   0,0,0,   // Yaw, pitch, roll,
   0,0,

   false,
   false,false,false,false,false,false
};

void MoveManager::init()
{
   Con::addVariable("mvForwardAction", TypeF32, &mForwardAction);
   Con::addVariable("mvBackwardAction", TypeF32, &mBackwardAction);
   Con::addVariable("mvUpAction", TypeF32, &mUpAction);
   Con::addVariable("mvDownAction", TypeF32, &mDownAction);
   Con::addVariable("mvLeftAction", TypeF32, &mLeftAction);
   Con::addVariable("mvRightAction", TypeF32, &mRightAction);

   Con::addVariable("mvFreeLook", TypeBool, &mFreeLook);
   Con::addVariable("mvPitch", TypeF32, &mPitch);
   Con::addVariable("mvYaw", TypeF32, &mYaw);
   Con::addVariable("mvRoll", TypeF32, &mRoll);
   Con::addVariable("mvPitchUpSpeed", TypeF32, &mPitchUpSpeed);
   Con::addVariable("mvPitchDownSpeed", TypeF32, &mPitchDownSpeed);
   Con::addVariable("mvYawLeftSpeed", TypeF32, &mYawLeftSpeed);
   Con::addVariable("mvYawRightSpeed", TypeF32, &mYawRightSpeed);
   Con::addVariable("mvRollLeftSpeed", TypeF32, &mRollLeftSpeed);
   Con::addVariable("mvRollRightSpeed", TypeF32, &mRollRightSpeed);

   for(U32 i = 0; i < MaxTriggerKeys; i++)
   {
      char varName[256];
      dSprintf(varName, sizeof(varName), "mvTriggerCount%d", i);
      Con::addVariable(varName, TypeS32, &mTriggerCount[i]);
   }
}

static inline F32 clampFloatWrap(F32 val)
{
   return val - F32(S32(val));
}

static F32 clampFloatClamp(F32 val, U32 bits)
{
   if(val < 0)
      val = 0;
   else if(val > 1)
      val = 1;
   F32 mask = (1 << bits);
   return U32(val * mask) / F32(mask);
}

static inline S32 clampRangeClamp(F32 val)
{
   if(val < -1)
      return 0;
   if(val > 1)
      return 32;
   return (S32)((val + 1) * 16);
}


#define FANG2IANG(x)   ((U32)((S16)((F32(0x10000) / M_2PI) * x)) & 0xFFFF)
#define IANG2FANG(x)   (F32)((M_2PI / F32(0x10000)) * (F32)((S16)x))

void Move::unclamp()
{
   yaw = IANG2FANG(pyaw);
   pitch = IANG2FANG(ppitch);
   roll = IANG2FANG(proll);

   x = (px - 16) / F32(16);
   y = (py - 16) / F32(16);
   z = (pz - 16) / F32(16);
}

void Move::clamp()
{
   // angles are all 16 bit.
   pyaw = FANG2IANG(yaw);
   ppitch = FANG2IANG(pitch);
   proll = FANG2IANG(roll);

   px = clampRangeClamp(x);
   py = clampRangeClamp(y);
   pz = clampRangeClamp(z);
   unclamp();
}

void Move::pack(BitStream *stream)
{
   if(stream->writeFlag(pyaw != 0))
      stream->writeInt(pyaw, 16);
   if(stream->writeFlag(ppitch != 0))
      stream->writeInt(ppitch, 16);
   if(stream->writeFlag(proll != 0))
      stream->writeInt(proll, 16);

   stream->writeInt(px, 6);
   stream->writeInt(py, 6);
   stream->writeInt(pz, 6);
   stream->writeFlag(freeLook);

   for(U32 i = 0; i < MaxTriggerKeys; i++)
      stream->writeFlag(trigger[i]);
}

void Move::unpack(BitStream *stream)
{
   if(stream->readFlag())
      pyaw = stream->readInt(16);
   else
      pyaw = 0;
   if(stream->readFlag())
      ppitch = stream->readInt(16);
   else
      ppitch = 0;
   if(stream->readFlag())
      proll = stream->readInt(16);
   else
      proll = 0;
   px = stream->readInt(6);
   py = stream->readInt(6);
   pz = stream->readInt(6);

   unclamp();
   freeLook = stream->readFlag();

   for(U32 i = 0; i < MaxTriggerKeys; i++)
      trigger[i] = stream->readFlag();
}

bool GameConnection::getNextMove(Move &curMove)
{
   if(mMoveList.size() > MaxMoveQueueSize)
      return false;

   F32 pitchAdd = MoveManager::mPitchUpSpeed - MoveManager::mPitchDownSpeed;
   F32 yawAdd = MoveManager::mYawLeftSpeed - MoveManager::mYawRightSpeed;
   F32 rollAdd = MoveManager::mRollRightSpeed - MoveManager::mRollLeftSpeed;

   curMove.pitch = MoveManager::mPitch + pitchAdd;
   curMove.yaw = MoveManager::mYaw + yawAdd;
   curMove.roll = MoveManager::mRoll + rollAdd;

   MoveManager::mPitch = 0;
   MoveManager::mYaw = 0;
   MoveManager::mRoll = 0;

   curMove.x = MoveManager::mRightAction - MoveManager::mLeftAction;
   curMove.y = MoveManager::mForwardAction - MoveManager::mBackwardAction;
   curMove.z = MoveManager::mUpAction - MoveManager::mDownAction;

   curMove.freeLook = MoveManager::mFreeLook;

   for(U32 i = 0; i < MaxTriggerKeys; i++)
   {
      curMove.trigger[i] = false;
      if(MoveManager::mTriggerCount[i] & 1)
         curMove.trigger[i] = true;
      else if(!(MoveManager::mPrevTriggerCount[i] & 1) && MoveManager::mPrevTriggerCount[i] != MoveManager::mTriggerCount[i])
         curMove.trigger[i] = true;
      MoveManager::mPrevTriggerCount[i] = MoveManager::mTriggerCount[i];
   }
   curMove.clamp();  // clamp for net traffic
   return true;
}

void GameConnection::pushMove(const Move &mv)
{
   U32 id = mFirstMoveIndex + mMoveList.size();
   U32 sz = mMoveList.size();
   mMoveList.push_back(mv);
   mMoveList[sz].id = id;
   mMoveList[sz].sendCount = 0;
}

void GameConnection::getMoveList(Move** movePtr,U32* numMoves)
{
   if (isConnectionToServer())
   {
      // give back moves starting at the last client move...

      AssertFatal(mLastClientMove >= mFirstMoveIndex, "Bad move request");
      AssertFatal(mLastClientMove - mFirstMoveIndex <= mMoveList.size(), "Desynched first and last move.");
      *numMoves = mMoveList.size() - mLastClientMove + mFirstMoveIndex;
      *movePtr = mMoveList.address() + mLastClientMove - mFirstMoveIndex;
   }
   else
   {
      // On the server we keep our own move list.
      *numMoves = (mMoveList.size() < mMoveCredit)?
         mMoveList.size(): mMoveCredit;
      *movePtr = mMoveList.begin();
      //
      mMoveCredit -= *numMoves;
      mMoveList.setSize(*numMoves);
   }
}

void GameConnection::collectMove(U32 time)
{
   Move mv;
   if(!isPlayingBack() && getNextMove(mv))
   {
      pushMove(mv);
      recordBlock(BlockTypeMove, sizeof(Move), &mv);
   }
}

void GameConnection::clearMoves(U32 count)
{
   if (isConnectionToServer()) {
      mLastClientMove += count;
   }
   else {
      AssertFatal(count <= mMoveList.size(),"GameConnection: Clearing too many moves");
      if (count == mMoveList.size())
         mMoveList.clear();
      else
         while (count--)
            mMoveList.pop_front();
   }
}

void GameConnection::incMoveCredit(U32 ticks)
{
   AssertFatal(!isConnectionToServer(), "Cannot inc move credit on the client.");
   // Game tick increment
   if ((mMoveCredit += ticks) > MaxMoveCount)
      mMoveCredit = MaxMoveCount;

   // Clear pending moves for the elapsed time if there
   // is no control object.
   if (mControlObject.isNull())
      mMoveList.clear();
}

bool GameConnection::areMovesPending()
{
   return isConnectionToServer() ?
      mMoveList.size() - mLastClientMove + mFirstMoveIndex :
      mMoveList.size();
}

bool GameConnection::isBacklogged()
{
   // If there are no pending moves and the input queue is full,
   // then the connection to the server must be clogged.
   if(!isConnectionToServer())
      return false;
   return mLastClientMove - mFirstMoveIndex == mMoveList.size() &&
      mMoveList.size() >= MaxMoveCount;
}


void GameConnection::moveWritePacket(BitStream *bstream)
{
   Move* move;
   U32 count;
   AssertFatal(mLastMoveAck == mFirstMoveIndex, "Invalid move index.");
   count = mMoveList.size();
   move = mMoveList.address();
   U32 start = mLastMoveAck;
   U32 offset;
   for(offset = 0; offset < count; offset++)
      if(move[offset].sendCount < MAX_MOVE_PACKET_SENDS)
         break;
   if(offset == count && count != 0)
      offset--;

   start += offset;
   count -= offset;

   if (count > MaxMoveCount)
      count = MaxMoveCount;
   bstream->writeInt(start,32);
   bstream->writeInt(count,MoveCountBits);
   for (int i = 0; i < count; i++)
   {
      move[offset + i].sendCount++;
      move[offset + i].pack(bstream);
   }
}

void GameConnection::moveReadPacket(BitStream *bstream)
{
   // Server side packet read.
   U32 start = bstream->readInt(32);
   U32 count = bstream->readInt(MoveCountBits);

   // Skip forward (must be starting up), or over the moves
   // we already have.
   int skip = mLastMoveAck - start;
   if (skip < 0) {
      mLastMoveAck = start;
      //mMoveList.clear();
   }
   else {
      Move tmp;
      if (skip > count)
         skip = count;
      for (int i = 0; i < skip; i++)
         tmp.unpack(bstream);
      start += skip;
      count = count - skip;
   }

   // Put the rest on the move list.
   int index = mMoveList.size();
   mMoveList.increment(count);
   while (index < mMoveList.size())
   {
      mMoveList[index].unpack(bstream);
      mMoveList[index].id = start++;
      index ++;
   }

   mLastMoveAck += count;
}


