/*****************************************************************************
 * $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 (record-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_RECORDER_CC 1
#include "base/module.hh"
#include "base/timer.hh"
#include "base/stream.hh"
#include "base/stream_zlib.hh"
#include "base/conf.hh"
using namespace base;
#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/replay_path.hh"
#include "control/replay_recorder.hh"

namespace control {

////////////////////////////////////////////////////////////////////////////////
//////////////////////////////  ReplayRecorder  ////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * ctor/dtor.
 *****************************************************************************/
ReplayRecorder::ReplayRecorder( shptr<Aircraft> aircraft,
                                const Milliseconds freq )
:   mAircraft(aircraft),
    mFreq(freq),
    mRegistered(false),
    mTickFunctor(new TickFunctor(*this)),
    mCommandListener(new CommandListener(*this)),
    mPath(new ReplayPath),
    mStartTime(INVALID_TIME)
{
CHECK_TYPESIG(aircraft,TYPESIG_AIRCRAFT);

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

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

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

        // Register listeners/functors.
        GET_TIMER().Register( mTickFunctor, mFreq );
        EVENT_CONTROL_COMMAND.Listen( mCommandListener.PTR() );
    }
}

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

        // Unregister listeners/functors.
        GET_TIMER().Unregister( mTickFunctor );
        EVENT_CONTROL_COMMAND.Unlisten( mCommandListener.PTR() );
    }
}

/*****************************************************************************
 * Start recording replay data.
 *****************************************************************************/
bool
ReplayRecorder::Start( void )
{
CHECK_TYPESIG(mAircraft,TYPESIG_AIRCRAFT);

    // Reset.
    mPath->clear();

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

    // To start replay correctly, save the current state of the Aircraft.
    AircraftPhysics& physics = mAircraft->GetPhysics();
    const Milliseconds relativeTime( 0 );
    mPath->push_back( ReplayPath::Step( relativeTime, new CommandPosition(    world::conv::WorldVertex2GeoVertex(mAircraft->GetPosition() ) ) ) );
    mPath->push_back( ReplayPath::Step( relativeTime, new CommandQuaternion(  mAircraft->GetQuaternion() ) ) );
    mPath->push_back( ReplayPath::Step( relativeTime, new CommandThrottle(    mAircraft->GetThrottle() ) ) );
    mPath->push_back( ReplayPath::Step( relativeTime, new CommandSpeed(       physics.ComputeSpeed() ) ) );
    mPath->push_back( ReplayPath::Step( relativeTime, new CommandLandingGear( mAircraft->IfLandingGear() ) ) );
    mPath->push_back( ReplayPath::Step( relativeTime, new CommandBrakes(      mAircraft->IfBrakes() ) ) );

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

    return true;
}

/*****************************************************************************
 * Stop recording.
 *****************************************************************************/
void
ReplayRecorder::Stop( void )
{
    // Stop listeners/timer-ticks.
    Unregister();
}

/*****************************************************************************
 * Discard recording.
 *****************************************************************************/
void
ReplayRecorder::Clear( void )
{
    Stop();             // stop timer-ticks
    mPath->Clear();     // clear STL container
}

/*****************************************************************************
 * Save replay data to a file.
 * THROWS EXCEPTION.
 *****************************************************************************/
bool
ReplayRecorder::Save( const string& filename )
{
    // Any data?
    if ( mPath->empty() )
        return false;

    // Open stream for writing.
    ogzstream ogz;  // zlib compression
    const string pathname = ReplayPath::GetPathnameForSaving( filename );
    ogz.open( pathname.c_str(), std::ios::out | std::ios::binary | std::ios::trunc );
    if ( UX( not ogz.good() ) )
        throw Exception( string("Replay::Save: failed to write to ") + pathname );

    // Write version info.
    ogz << "version" << ' ' << control::defs::REPLAY_VERSION_NUMBER << '\n';

    // Write name of Aircraft.  Keep name "craft" for compability.
    ogz << "craft" << ' ' << mAircraft->GetName() << '\n';

    // Write world/planet info.
    if ( GET_WORLD().IfFlat() )
        ogz << "world flat" << '\n';
    else
        ogz << "world ossim" << '\n';

    // Write each step in path to C++ stream.
    for ( ReplayPath::Path::iterator iter = mPath->begin();
          iter != mPath->end();
          ++iter )
    {
        ReplayPath::Step& step = *iter;
        CHECK_TYPESIG(&step,TYPESIG_REPLAY_STEP);
        ogz << step << '\n';  // whitespace to separate
    }

    return ogz.good();
}

/*****************************************************************************
 * Functor that listens for Commands broadcast from Aircrafts.
 *****************************************************************************/
void
ReplayRecorder::CommandListener::operator()( CommandEvent commandEvent )
{
CHECK_TYPESIG(commandEvent.mAircraft,TYPESIG_AIRCRAFT);
CHECK_TYPESIG(mRecorder.mAircraft,TYPESIG_AIRCRAFT);

    // Ignore Commands from other Craft.
    if ( commandEvent.mAircraft == mRecorder.mAircraft )
    {
        const Milliseconds relativeTime = GET_TIMER().GetElapsedTime() - mRecorder.mStartTime;
        ASSERT( relativeTime >= Milliseconds(0) );

        // Convert Command into a Step.
        ReplayPath::Step step( relativeTime, commandEvent.mCommand );

        // Append Step to Path.
        // ReplayPath is responsible for discarding redundant Commands.
        mRecorder.mPath->push_back( step );
    }
}

/*****************************************************************************
 * Some Commands are not broadcast, so poll them using a timer-tick.
 * ReplayPath is responsible for discarding redundant Commands.
 *****************************************************************************/
void
ReplayRecorder::TickFunctor::Tick( const Milliseconds milliseconds )
{
    shptr<Aircraft> aircraft = mRecorder.mAircraft;
    const Milliseconds relativeTime = milliseconds - mRecorder.mStartTime;
ASSERT( relativeTime >= Milliseconds(0) );

    // Poll Aircraft's speed.
    const Speed speed = aircraft->GetPhysics().ComputeSpeed();
    const ReplayPath::Step stepSpeed( relativeTime, new CommandSpeed( speed ) );
    mRecorder.mPath->push_back( stepSpeed );
}

} // namespace control
