/*****************************************************************************
 * $LastChangedDate: 2011-04-23 21:07:07 -0400 (Sat, 23 Apr 2011) $
 * @file
 * @author  Jim E. Brooks  http://www.palomino3d.org
 * @brief   Replay animation of an Aircraft (play mode implementation of Replay class).
 *//*
 * LEGAL:   COPYRIGHT (C) 2008 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE VERSION 2 (GPL 2).
 *****************************************************************************/

#define CONTROL_REPLAY_PLAYER_CC 1
#include "base/module.hh"
#include "base/timer.hh"
#include "base/stream.hh"
#include "base/stream_zlib.hh"
using namespace base;
#include "math/module.hh"
#include "math/funcs.hh"
#include "math/funcs_vector.hh"
using namespace math;
#include "object/module.hh"
#include "object/aircraft.hh"
using namespace object;
#include "world/module.hh"
using namespace world;
#include "control/module.hh"
#include "control/defs.hh"
#include "control/command.hh"
#include "control/joystick.hh"
#include "control/replay.hh"
#include "control/replay_recorder.hh"
#include "control/replay_player.hh"

namespace control {

////////////////////////////////////////////////////////////////////////////////
//////////////////////////////  ReplayPlayer  //////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * ctor/dtor.
 *****************************************************************************/
ReplayPlayer::ReplayPlayer( shptr<Aircraft> aircraft,
                            const Milliseconds freq,
                            const Command::ExecuteMode commandExecuteMode )
:   mAircraft(aircraft),
    mFreq(freq),
    mCommandExecuteMode(commandExecuteMode),
    mRegistered(false),
    mTickFunctor(new TickFunctor(*this)),
    mPath(new ReplayPath),
    mStartTime(INVALID_TIME),
    mPlaying(false),
    mJoystickEnabled(GET_CONTROL_JOYSTICK().IfEnabled())
{
CHECK_TYPESIG(aircraft,TYPESIG_AIRCRAFT);

  //Register();  // delayed until playing starts
}

ReplayPlayer::~ReplayPlayer()
{
    // Unregister() doesn't need to be matched Register().
    Unregister();

    // Restore joystick.
    RestoreJoystickIfPlayer();
}

/*****************************************************************************
 * Register listeners/functors.
 * Either is safe to call at anytime or be unmatched.
 *****************************************************************************/
void
ReplayPlayer::Register( void )
{
    if ( not mRegistered )
    {
        mRegistered = true;

        // Register listeners/functors.
        GET_TIMER().Register( mTickFunctor, mFreq );
    }
}

void
ReplayPlayer::Unregister( void )
{
    if ( mRegistered )
    {
        mRegistered = false;

        // Unregister listeners/functors.
        GET_TIMER().Unregister( mTickFunctor );
    }
}

/*****************************************************************************
 * Start replaying.
 *****************************************************************************/
bool
ReplayPlayer::Start( void )
{
    // Data?
    if ( not mPath->empty() )
    {
        mPlaying = true;

        // Rewind data to beginning.
        mPath->Rewind();

        // Start the clock.
        mStartTime = GET_TIMER().GetElapsedTime();

        // Reset Craft.
        mAircraft->Reset();

        // Prevent the player and physics interfering with replay.
        mAircraft->GetPhysics().Enable( false );
        DisableJoystickIfPlayer();

        // Last step: enable listeners/timer-ticks.
        Register();

        return true;
    }
    else
    {
        // No replay data.
        mPlaying = false;
        return false;
    }
}

/*****************************************************************************
 * Stop playing.
 *****************************************************************************/
void
ReplayPlayer::Stop( void )
{
    mPlaying = false;

    // Stop listeners/timer-ticks.
    Unregister();
}

/*****************************************************************************
 * @return True if playing.
 *****************************************************************************/
bool
ReplayPlayer::IfPlaying( void )
{
    return mPlaying;
}

/*****************************************************************************
 * Load replay data from a file.
 * THROWS EXCEPTION.
 *****************************************************************************/
bool
ReplayPlayer::Load( const string& filename )
{
    // Clear replay data.
    mPath->clear();

    // Open stream for reading.
    igzstream igz;  // zlib compression
    const string pathname = ReplayPath::GetPathnameForLoading( filename );
    igz.open( pathname.c_str(), std::ios::in | std::ios::binary );
    if ( UX( not igz.good() ) )
        throw Exception( string("Replay::Load: failed to read from ") + pathname );

    // Try to read version info (first line).
    const char obsoleteReplayText[] = "Format of saved replay is obsolete, record a new one.";
    string versionKeyword; igz >> versionKeyword;
    if ( versionKeyword == "version" )
    {
        // Next field is the version.
        uint version; igz >> version;
        if ( version < control::defs::REPLAY_VERSION_NUMBER )
        {
            CLOG << obsoleteReplayText << std::endl;
            return false;
        }
    }
    else
    {
        // The first implementation of Replay lacked a version line.
        CLOG << obsoleteReplayText << std::endl;
        return false;
    }

    // Read Craft name (not used currently).
    string aircraftKeyword, aircraftName;
    igz >> aircraftKeyword;
    igz >> aircraftName;
    if ( UX( aircraftKeyword != "craft" ) )  // keep the name "craft"
        throw Exception( "Replay::Load: misparsed craft field" );

    // Read world info.
    // Historical: worldName used to be worldType which was assigned "flat" or "ossim".
    // GET_WORLD().GetName() returns those same strings, so worldName is a compatible replacement.
    string worldKeyword, worldName;
    igz >> worldKeyword;
    igz >> worldName;
    if ( UX( worldKeyword != "world" ) )
        throw Exception( "Replay::Load: misparsed world field" );

    // Do not replay in a different world than was recorded.
    if ( worldName != GET_WORLD().GetName() )
    {
        CLOG << "Replay data was recorded in different world " << worldName << " vs " << GET_WORLD().GetName() << std::endl;
        return false;
    }

    // Replaying data for a different Aircraft won't produce the same flight path.
    if ( aircraftName != mAircraft->GetName() )
        CLOG << "Replay data was recorded for " << aircraftName << " but current aircraft is " << mAircraft->GetName() << '.' << std::endl;

    // Read each step in path from C++ stream.
    while ( true )
    {
        ReplayPath::Step step;
        try
        {
            igz >> step;
        }
        catch ( const control::ExceptionCommand& )
        {
            // NOP
        }

        if ( EX( igz.good() ) )  // EOF?
            mPath->push_back( step );
        else
            break;  // EOF (probably)
    }

    // Success if replay data isn't empty.
    return mPath->size() > 0;
}

/*****************************************************************************
 * Prepare to replay the last recording in memory.
 *****************************************************************************/
void
ReplayPlayer::UseRecording( shptr<ReplayRecorder> recorder )
{
    Stop();                         // stop playing the outgoing path
    mPath = recorder->GetPath();    // shptr = shptr
}

/*****************************************************************************
 * Timer-tick functor.  This does the replay step-by-step.
 *****************************************************************************/
void
ReplayPlayer::Tick( const Milliseconds elapsedTime )
{
    // ReplayPath::Advance() moves the cursor forward.
    // ReplayPath::IfEnd() indicates when cursor reaches end of the animation path.
    ReplayPath::Cursor& cursor = mPath->GetCursor();
    while ( not cursor.IfEnd() )
    {
        // Get current Step in animation path.
        ReplayPath::Step step = cursor.GetCurrentStep();
        CHECK_TYPESIG(&step,TYPESIG_REPLAY_STEP);

        // Time to execute this Step?
        ASSERT( mStartTime.INT() != INVALID_TIME );
        if ( elapsedTime - mStartTime >= step.mMilliseconds )
        {
            // Execute this Step/Command.
            step.mCommand->Execute( mAircraft, mCommandExecuteMode );

            // Advance to next Step in animation path.
            cursor.Advance();
        }
        else
        {
            // Too soon to execute current Step.
            // For smoothness, interpolate.
            TryInterpolatingPositions();
            TryInterpolatingQuaternions();

            break;
        }
    }

    // Has Replay finished?
    if ( cursor.IfEnd() )
    {
        // Stop playing.
        Stop();

        // Disable physics.  Aircraft will stop in mid-air.
        mAircraft->GetPhysics().Enable( false );

        // Restore joystick.
        RestoreJoystickIfPlayer();
    }
}

void
ReplayPlayer::TickFunctor::Tick( const Milliseconds milliseconds )
{
    // Reflect.
    mPlayer.Tick( milliseconds );
}

/*****************************************************************************
 * Macro for interpolation position or rotation.
 *****************************************************************************/
#define TRY_INTERPOLATING( IF_HAVE_STEPS,                                           \
                           COMMAND_CLASS, COMMAND_TYPE_NUM,                         \
                           STEPS_TYPEDEF, GET_STEPS,                                \
                           POS_OR_QUAT_CLASS, GET_POS_OR_QUAT, SET_POS_OR_QUAT )    \
{                                                                                   \
    /* If cursor is across two positions. */                                        \
    if ( mPath->GetCursor().IF_HAVE_STEPS() )                                       \
    {                                                                               \
        ReplayPath::Cursor::STEPS_TYPEDEF steps = mPath->GetCursor().GET_STEPS();   \
        ReplayPath::Step& prevStep = steps.first;                                   \
        ReplayPath::Step& nextStep = steps.second;                                  \
                                                                                    \
    CHECK_TYPESIG(prevStep.mCommand,TYPESIG_COMMAND);                               \
    CHECK_TYPESIG(nextStep.mCommand,TYPESIG_COMMAND);                               \
    ASSERT( prevStep.mCommand->GetType() == COMMAND_TYPE_NUM );                     \
    ASSERT( nextStep.mCommand->GetType() == COMMAND_TYPE_NUM );                     \
                                                                                    \
        const POS_OR_QUAT_CLASS prev = static_cast<COMMAND_CLASS*>(prevStep.mCommand.PTR())->GET_POS_OR_QUAT(); \
        const POS_OR_QUAT_CLASS next = static_cast<COMMAND_CLASS*>(nextStep.mCommand.PTR())->GET_POS_OR_QUAT(); \
        const fp            fraction = ComputeInterpolationFraction( prevStep, nextStep );                      \
        const POS_OR_QUAT_CLASS pos = Interpolate( prev, next, fraction );                                      \
        mAircraft->SET_POS_OR_QUAT( pos );                                                                          \
    }                                                                                                           \
}

/*****************************************************************************
 * Interpolate between two positions.
 *****************************************************************************/
void
ReplayPlayer::TryInterpolatingPositions( void )
{
    TRY_INTERPOLATING( IfHavePositionSteps,
                       CommandPosition, Command::TYPE_POSITION,
                       PositionSteps, GetPositionSteps,
                       WorldVertex, GetWorldVertex, SetPosition );
}

/*****************************************************************************
 * Interpolate between two rotations.
 *****************************************************************************/
void
ReplayPlayer::TryInterpolatingQuaternions( void )
{
    TRY_INTERPOLATING( IfHaveQuaternionSteps,
                       CommandQuaternion, Command::TYPE_QUATERNION,
                       QuaternionSteps, GetQuaternionSteps,
                       Quaternion, GetQuaternion, SetQuaternion );
}

/*****************************************************************************
 * Compute fraction to be passed to a math::Interpolate() function.
 *****************************************************************************/
fp
ReplayPlayer::ComputeInterpolationFraction( const ReplayPath::Step& prevStep,
                                            const ReplayPath::Step& nextStep )
{
CHECK_TYPESIG(&prevStep,TYPESIG_REPLAY_STEP);

    // Time between previous and next Step.
    const Milliseconds timeBetweenSteps = nextStep.mMilliseconds - prevStep.mMilliseconds;
    ASSERT( timeBetweenSteps >= Milliseconds(0) );

    // Time between now and previous step.
    const Milliseconds relativeTime = GET_TIMER().GetElapsedTime() - mStartTime;
    const Milliseconds timeBetweenNowAndPrevStep = relativeTime - prevStep.mMilliseconds;
    ASSERT( timeBetweenNowAndPrevStep >= Milliseconds(0) );

    // Compute fraction.
    const fpx fraction = timeBetweenNowAndPrevStep.FPX() / timeBetweenSteps.FPX();
    ASSERT( FP_GE(fraction,0.0) and FP_LE(fraction,1.0) );

    return fraction;
}                                             

/*****************************************************************************
 * If Replay is controlling the player, disable joystick input to prevent interference.
 *****************************************************************************/
void
ReplayPlayer::DisableJoystickIfPlayer( void )
{
    if ( mAircraft.PTR() == GET_CURRENT_CRAFT().PTR() )
    {
        mJoystickEnabled = GET_CONTROL_JOYSTICK().IfEnabled();  // save
        GET_CONTROL_JOYSTICK().Enable( false );                 // disable
    }
}

void
ReplayPlayer::RestoreJoystickIfPlayer( void )
{
    if ( mAircraft.PTR() == GET_CURRENT_CRAFT().PTR() )
        GET_CONTROL_JOYSTICK().Enable( mJoystickEnabled );
}

} // namespace control
