/******************************************************************
  
  Module:  motioncontroller_service.cpp
  
  Author: Sean Craig
  
  Copyright 2005 Sony Online Entertainment.  All rights reserved.
  
*******************************************************************/

//-------------------------------------------------------- Includes
#include "motioncontroller_service.h"
#include "registry.h"
#include "gameobj.h"
#include "stats.h"
#include "animcontroller.h"
#include "titan.h"
//---------------------------------------------- Class Declarations

cNameID cMotionController_Service::mNameID("MOTIONCONTROLLER");
//----------------------------------------- Functions Declarations

cMotionController_Service::~cMotionController_Service()
{
  mEntries.DeleteAll();
}

void 
cMotionController_Service::Reset() // once on level change
{
  mEntries.DeleteAll();
}

void 
cMotionController_Service::Perform( float time )  // performs the service
{
  cMotionController_Entry *cur = mEntries.First();
  while ( cur != NULL )
  {
    if ( !cur->mPaused
      && !_Move( time, cur ) )
    {
      cMotionController_Entry *next = mEntries.Next(cur);
      mEntries.Remove(cur);
      delete cur;
      cur = next;
    }
    else
    {
      cur = mEntries.Next(cur);
    }
  }
}

void 
cMotionController_Service::Add( const cGameObject& object, const cGameObject& marker, float speed, MotionControl::Option moveOption )
{
  cMotionController_Entry *entry = new cMotionController_Entry;

  entry->mObject = object.GetID();
  entry->mMoveOption = moveOption;
  entry->mTime = 0.0f;
  entry->mSpeed = speed;
  entry->mPaused = false;

  // set the first key to be the object's current position!
  float lasttime = 0.0f;
  float lastheading = object.GetHeading();

  const SyVect3& location = object.GetLocation();
  entry->mCurveX.AddKey( lasttime, location.X );
  entry->mCurveY.AddKey( lasttime, location.Y );
  entry->mCurveZ.AddKey( lasttime, location.Z );
  entry->mCurveHeading.AddKey( lasttime, lastheading );

  cGameObjectRegistry *pRegistry = mpTitan->GetRegistry();

#if _DEBUG
  SySet< tGameID > pathNodes;
#endif

  tGameID markerID = marker.GetID();
  while (markerID != 0)
  {
    // find locator
    cGameObject *marker = pRegistry->Fetch(markerID);
    if (marker == NULL)
    {
      SyAssertf(0,"Bad Marker Object in Move Function");
      break;
    }

    cStatsMarker *stats = prop_cast< cStatsMarker* >(marker->GetStats());
    if (stats == NULL)
    {
      SyAssertf(0,"Bad Marker Object in Move Function");
      break;
    }

#if _DEBUG
    if ( pathNodes.Find( markerID ) != pathNodes.End() )
    {
      SyAssertf( false, "'%s' marker creates an infinite loop!", marker->GetName() );
      break;
    }

    pathNodes.Insert( markerID );
#endif

    float newtime = stats->GetTime();

    if (newtime <= lasttime) newtime = lasttime + 1.0f;

    const SyVect3& cur = marker->GetLocation();
    entry->mCurveX.AddKey(newtime,cur.X); 
    entry->mCurveY.AddKey(newtime,cur.Y); 
    entry->mCurveZ.AddKey(newtime,cur.Z); 
    float heading = marker->GetHeading();
    float delta = AngleDifference(heading,lastheading);
    float newheading = heading + delta;
    lastheading = newheading;
    entry->mCurveHeading.AddKey(newtime,newheading);

    lasttime = newtime;
    markerID = stats->GetNext();
  }

  int numKeys = entry->mCurveX.GetNumKeys();
  if ( numKeys > 2 )
  {
    if ( entry->mMoveOption == MotionControl::MOVE_LOOP )
    {
      // duplicate first + last key so curve looks ok.
      float duration = entry->mCurveX.GetKeyTime( numKeys - 1 );
      float key1 = entry->mCurveX.GetKeyTime(1);

      entry->mCurveX.AddKey( duration + key1, entry->mCurveX.GetKeyValue( 1 ) );
      entry->mCurveY.AddKey( duration + key1, entry->mCurveY.GetKeyValue( 1 ) );
      entry->mCurveZ.AddKey( duration + key1, entry->mCurveZ.GetKeyValue( 1 ) );
      entry->mCurveHeading.AddKey( duration + key1, entry->mCurveHeading.GetKeyValue( 1 ) );

      // add starting entry to ensure smooth transition
      // BH - this is incompatible with the change to first move the object onto the path

      //float start_time = -(duration - entry->mCurveX.GetKeyTime(numKeys-2));
      //entry->mCurveX.AddKey(start_time,entry->mCurveX.GetKeyValue(numKeys-2));
      //entry->mCurveY.AddKey(start_time,entry->mCurveY.GetKeyValue(numKeys-2));
      //entry->mCurveZ.AddKey(start_time,entry->mCurveZ.GetKeyValue(numKeys-2));
      //entry->mCurveHeading.AddKey(start_time,entry->mCurveHeading.GetKeyValue(numKeys-2));
    }
    else if ( entry->mMoveOption == MotionControl::MOVE_PINGPONG )
    {
      float run_time = entry->mCurveX.GetKeyTime( numKeys - 1 );

      for ( int iKey = numKeys - 1; iKey > 0; --iKey )
      {
        run_time += entry->mCurveX.GetKeyTime( iKey ) - entry->mCurveX.GetKeyTime( iKey - 1 );

        entry->mCurveX.AddKey( run_time, entry->mCurveX.GetKeyValue( iKey ) );
        entry->mCurveY.AddKey( run_time, entry->mCurveY.GetKeyValue( iKey ) );
        entry->mCurveZ.AddKey( run_time, entry->mCurveZ.GetKeyValue( iKey ) );
        entry->mCurveHeading.AddKey( run_time, entry->mCurveHeading.GetKeyValue( iKey ) );
      }

      // duplicate last key to smooth out the loop:
//      int numKeys = entry->mCurveX.GetNumKeys();
      run_time += entry->mCurveX.GetKeyTime( 1 );
      entry->mCurveX.AddKey( run_time, entry->mCurveX.GetKeyValue( 1 ) );
      entry->mCurveY.AddKey( run_time, entry->mCurveY.GetKeyValue( 1 ) );
      entry->mCurveZ.AddKey( run_time, entry->mCurveZ.GetKeyValue( 1 ) );
      entry->mCurveHeading.AddKey( run_time, entry->mCurveHeading.GetKeyValue( 1 ) );
    }
  }

  entry->mCurveX.Init(SyCurve::CURVE_CUBIC_SPLINE);
  entry->mCurveY.Init(SyCurve::CURVE_CUBIC_SPLINE);
  entry->mCurveZ.Init(SyCurve::CURVE_CUBIC_SPLINE);
  entry->mCurveHeading.Init(SyCurve::CURVE_CUBIC_SPLINE);

  mEntries.InsertTail(entry);
}

bool
cMotionController_Service::Stop( const cGameObject& object )
{
  bool ret = false;

  cMotionController_Entry *cur = mEntries.First();
  while (cur)
  {
    if (cur->mObject == object.GetID())
    {
      cMotionController_Entry *next = mEntries.Next(cur);
      mEntries.Remove(cur);
      delete cur;
      cur = next;
      ret = true;
    }
    else
    {
      cur = mEntries.Next(cur);
    }
  }

  return ret;
}

bool cMotionController_Service::Pause( const cGameObject& object )
{
  bool ret = false;

  cMotionController_Entry *cur = mEntries.First();
  while ( cur )
  {
    if ( cur->mObject == object.GetID()
      && !cur->mPaused )
    {
      cur->mPaused = true;
      ret = true;
    }

    cur = mEntries.Next( cur );
  }

  return ret;
}

bool cMotionController_Service::Resume( const cGameObject& object )
{
  bool ret = false;

  cMotionController_Entry *cur = mEntries.First();
  while ( cur )
  {
    if ( cur->mObject == object.GetID()
      && cur->mPaused )
    {
      cur->mPaused = false;
      ret = true;
    }

    cur = mEntries.Next( cur );
  }

  return ret;
}

bool
cMotionController_Service::_Move( float time, cMotionController_Entry *entry )
{
  float duration; 
  int numKeys = entry->mCurveX.GetNumKeys();

  if ( entry->mMoveOption != MotionControl::MOVE_ONCE )
  {
    duration = entry->mCurveX.GetKeyTime(numKeys-2);
  }
  else
  {
    duration = entry->mCurveX.GetKeyTime(numKeys-1);
  }

  entry->mTime += time * entry->mSpeed;

  if ( entry->mTime < 0 )
  {
    if ( entry->mMoveOption != MotionControl::MOVE_ONCE )
    {
      entry->mTime = duration + entry->mTime;
      entry->mTime = SY_FMOD(entry->mTime,duration); 
    }
    else 
    {
      return false;
    }
  }

  if ( entry->mTime > duration )
  {
    if ( MotionControl::MOVE_ONCE == entry->mMoveOption )
    {
      return false;
    }
    else
    {
      entry->mTime = entry->mCurveX.GetKeyTime( 1 ) + SY_FMOD(entry->mTime,duration); 
    }
  }

  cGameObjectRegistry *pRegistry = mpTitan->GetRegistry();
  cGameObject *obj = pRegistry->Fetch(entry->mObject);

  if (obj == NULL)
  {
    return false;
  }

  SyVect3 loc;
  loc.X = entry->mCurveX.GetValue(entry->mTime);
  loc.Y = entry->mCurveY.GetValue(entry->mTime);
  loc.Z = entry->mCurveZ.GetValue(entry->mTime);

  float heading = entry->mCurveHeading.GetValue(entry->mTime);
  
  obj->SetLocation(loc);
  obj->SetHeading(AngleNormalize(heading));

  return true;
}

//------------------------------------ Member Functions Definitions


// EOF
