//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------

#include "T3D/gameBase/moveManager.h"
#include "core/stream/bitStream.h"
#include "core/module.h"
#include "console/consoleTypes.h"
#include "core/strings/stringFunctions.h"
#include "math/mConstants.h"


MODULE_BEGIN( MoveManager )

   MODULE_INIT
   {
      MoveManager::init();
   }

MODULE_END;


bool MoveManager::mDeviceIsKeyboardMouse = false;
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;

F32 MoveManager::mXAxis_L = 0;
F32 MoveManager::mYAxis_L = 0;
F32 MoveManager::mXAxis_R = 0;
F32 MoveManager::mYAxis_R = 0;

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

const Move NullMove;

void MoveManager::init()
{
   Con::addVariable("mvForwardAction", TypeF32, &mForwardAction, 
      "Forwards movement speed for the active player.\n"
	   "@ingroup Game");
   Con::addVariable("mvBackwardAction", TypeF32, &mBackwardAction, 
      "Backwards movement speed for the active player.\n"
	   "@ingroup Game");
   Con::addVariable("mvUpAction", TypeF32, &mUpAction, 
      "Upwards movement speed for the active player.\n"
	   "@ingroup Game");
   Con::addVariable("mvDownAction", TypeF32, &mDownAction, 
      "Downwards movement speed for the active player.\n"
	   "@ingroup Game");
   Con::addVariable("mvLeftAction", TypeF32, &mLeftAction, 
      "Left movement speed for the active player.\n"
	   "@ingroup Game");
   Con::addVariable("mvRightAction", TypeF32, &mRightAction, 
      "Right movement speed for the active player.\n"
	   "@ingroup Game");

   Con::addVariable("mvFreeLook", TypeBool, &mFreeLook, 
      "Boolean state for if freelook is active or not.\n"
	   "@ingroup Game");
   Con::addVariable("mvDeviceIsKeyboardMouse", TypeBool, &mDeviceIsKeyboardMouse, 
      "Boolean state for it the system is using a keyboard and mouse or not.\n"
	   "@ingroup Game");
   Con::addVariable("mvPitch", TypeF32, &mPitch, 
      "Current pitch value, typically applied through input devices, such as a mouse.\n"
	   "@ingroup Game");
   Con::addVariable("mvYaw", TypeF32, &mYaw, 
      "Current yaw value, typically applied through input devices, such as a mouse.\n"
	   "@ingroup Game");
   Con::addVariable("mvRoll", TypeF32, &mRoll, 
      "Current roll value, typically applied through input devices, such as a mouse.\n"
	   "@ingroup Game");
   Con::addVariable("mvPitchUpSpeed", TypeF32, &mPitchUpSpeed, 
      "Upwards pitch speed.\n"
	   "@ingroup Game");
   Con::addVariable("mvPitchDownSpeed", TypeF32, &mPitchDownSpeed, 
      "Downwards pitch speed.\n"
	   "@ingroup Game");
   Con::addVariable("mvYawLeftSpeed", TypeF32, &mYawLeftSpeed, 
      "Left Yaw speed.\n"
	   "@ingroup Game");
   Con::addVariable("mvYawRightSpeed", TypeF32, &mYawRightSpeed, 
      "Right Yaw speed.\n"
	   "@ingroup Game");
   Con::addVariable("mvRollLeftSpeed", TypeF32, &mRollLeftSpeed, 
      "Left roll speed.\n"
	   "@ingroup Game");
   Con::addVariable("mvRollRightSpeed", TypeF32, &mRollRightSpeed, 
      "Right roll speed.\n"
	   "@ingroup Game");

   // Dual-analog
   Con::addVariable( "mvXAxis_L", TypeF32, &mXAxis_L, 
      "Left thumbstick X axis position on a dual-analog gamepad.\n"
	   "@ingroup Game" );
   Con::addVariable( "mvYAxis_L", TypeF32, &mYAxis_L, 
      "Left thumbstick Y axis position on a dual-analog gamepad.\n"
	   "@ingroup Game" );

   Con::addVariable( "mvXAxis_R", TypeF32, &mXAxis_R, 
      "Right thumbstick X axis position on a dual-analog gamepad.\n"
	   "@ingroup Game" );
   Con::addVariable( "mvYAxis_R", TypeF32, &mYAxis_R, 
      "Right thumbstick Y axis position on a dual-analog gamepad.\n"
	   "@ingroup Game");

   for(U32 i = 0; i < MaxTriggerKeys; i++)
   {
      char varName[256];
      dSprintf(varName, sizeof(varName), "mvTriggerCount%d", i);
      Con::addVariable(varName, TypeS32, &mTriggerCount[i], 
         "Used to determine the trigger counts of buttons. Namely used for input actions such as jumping and weapons firing.\n"
	      "@ingroup Game");
   }
}

Move::Move()
{
   px=16; py=16; pz=16;
   pyaw=0; ppitch=0; proll=0;
   x=0; y=0; z=0;
   yaw=0; pitch=0; roll=0;
   id=0;
   sendCount=0;

   checksum = false;
   deviceIsKeyboardMouse = false;
   freeLook = false;
   trigger[0] = false;
   trigger[1] = false;
   trigger[2] = false;
   trigger[3] = false;
   trigger[4] = false;
   trigger[5] = false;
}

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

static inline S32 clampRangeClamp(F32 val)
{
   if(val < -1)
      return 0;
   if(val > 1)
      return 32;
            
   // 0.5 / 16 = 0.03125 ... this forces a round up to
   // make the precision near zero equal in the negative
   // and positive directions.  See...
   //
   // http://www.garagegames.com/community/forums/viewthread/49714
   
   return (S32)((val + 1.03125) * 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);
}

static inline F32 clampAngleClamp( F32 angle )
{
  const F32 limit = ( M_PI_F / 180.0f ) * 179.999f;
  if ( angle < -limit )
     return -limit;
  if ( angle > limit )
     return limit;

  return angle;
}

void Move::clamp()
{
   // If yaw/pitch/roll goes equal or greater than -PI/+PI it 
   // flips the direction of the rotation... we protect against
   // that by clamping before the conversion.
            
   yaw   = clampAngleClamp( yaw );
   pitch = clampAngleClamp( pitch );
   roll  = clampAngleClamp( roll );

   // 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, const Move * basemove)
{
   bool alwaysWriteAll = basemove!=NULL;
   if (!basemove)
      basemove = &NullMove;

   packMove(stream, basemove, alwaysWriteAll);
}

bool Move::packMove(BitStream *stream, const Move* basemove, bool alwaysWriteAll)
{
   S32 i;
   bool triggerDifferent = false;
   for (i=0; i < MaxTriggerKeys; i++)
      if (trigger[i] != basemove->trigger[i])
         triggerDifferent = true;
   bool somethingDifferent = (pyaw!=basemove->pyaw)     ||
                             (ppitch!=basemove->ppitch) ||
                             (proll!=basemove->proll)   ||
                             (px!=basemove->px)         ||
                             (py!=basemove->py)         ||
                             (pz!=basemove->pz)         ||
                             (deviceIsKeyboardMouse!=basemove->deviceIsKeyboardMouse) ||
                             (freeLook!=basemove->freeLook) ||
                             triggerDifferent;
   
   if (alwaysWriteAll || stream->writeFlag(somethingDifferent))
   {
      if(stream->writeFlag(pyaw != basemove->pyaw))
      stream->writeInt(pyaw, 16);
      if(stream->writeFlag(ppitch != basemove->ppitch))
      stream->writeInt(ppitch, 16);
      if(stream->writeFlag(proll != basemove->proll))
      stream->writeInt(proll, 16);

      if (stream->writeFlag(px != basemove->px))
         stream->writeInt(px, 6);
      if (stream->writeFlag(py != basemove->py))
         stream->writeInt(py, 6);
      if (stream->writeFlag(pz != basemove->pz))
         stream->writeInt(pz, 6);
      stream->writeFlag(freeLook);
      stream->writeFlag(deviceIsKeyboardMouse);

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

   return (triggerDifferent || somethingDifferent);
}

void Move::unpack(BitStream *stream, const Move * basemove)
{
   bool alwaysReadAll = basemove!=NULL;
   if (!basemove)
      basemove=&NullMove;

   bool readMove = unpackMove(stream, basemove, alwaysReadAll);
   if(!readMove)
      *this = *basemove;
}

bool Move::unpackMove(BitStream *stream, const Move* basemove, bool alwaysReadAll)
{
   bool readMove = alwaysReadAll;
   if(!readMove)
   {
      readMove = stream->readFlag();
   }

   if (readMove)
   {
      pyaw = stream->readFlag() ? stream->readInt(16) : basemove->pyaw;
      ppitch = stream->readFlag() ? stream->readInt(16) : basemove->ppitch;
      proll = stream->readFlag() ? stream->readInt(16) : basemove->proll;

      px = stream->readFlag() ? stream->readInt(6) : basemove->px;
      py = stream->readFlag() ? stream->readInt(6) : basemove->py;
      pz = stream->readFlag() ? stream->readInt(6) : basemove->pz;
      freeLook = stream->readFlag();
      deviceIsKeyboardMouse = stream->readFlag();

      bool triggersDiffer = stream->readFlag();
      for (S32 i = 0; i< MaxTriggerKeys; i++)
         trigger[i] = triggersDiffer ? stream->readFlag() : basemove->trigger[i];
      unclamp();
   }

   return readMove;
}
