/*****************************************************************************
 * $LastChangedDate: 2011-04-23 21:07:07 -0400 (Sat, 23 Apr 2011) $
 * @file
 * @author  Jim E. Brooks  http://www.palomino3d.org
 * @brief   Replay data class (animation path).
 *//*
 * 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_PATH_CC 1
#include <utility>
#include "base/module.hh"
#include "base/conf.hh"
#include "base/file.hh"
#include "base/stream.hh"
using namespace base;
#include "glue/module.hh"
#include "glue/conf.hh"
using namespace glue;
#include "control/module.hh"
#include "control/defs.hh"
#include "control/command.hh"
#include "control/replay.hh"
#include "control/replay_path.hh"

namespace control {

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////  functions  ///////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * Write replay Step to stream.
 *****************************************************************************/
std::ostream& operator<<( std::ostream& strm, const ReplayPath::Step& step )
{
CHECK_TYPESIG(&step,TYPESIG_REPLAY_STEP);
CHECK_TYPESIG(step.mCommand,TYPESIG_COMMAND);

    strm << step.mMilliseconds
         << ' '
         << *step.mCommand;
    return strm;
}

/*****************************************************************************
 * Read replay Replay::Step from stream.
 *****************************************************************************/
std::istream& operator>>( std::istream& strm, ReplayPath::Step& step )
{
CHECK_TYPESIG(&step,TYPESIG_REPLAY_STEP);

    strm >> step.mMilliseconds;

    // Command::Read(stream) is a factory method
    // that creates appropriate derivative from stream.
    step.mCommand = Command::Read( strm );  // shptr = shptr
    CHECK_TYPESIG(step.mCommand,TYPESIG_COMMAND);

    return strm;
}

////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////  ReplayPath  /////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * ctor/dtor.
 *****************************************************************************/
ReplayPath::ReplayPath( void )
:   mPath(),
    mCursor(mPath)
{
    // NOP
}

ReplayPath::~ReplayPath()
{
    // NOP
}

/*****************************************************************************
 * Clear data.
 *****************************************************************************/
void
ReplayPath::Clear( void )
{
    // Rewind cursor.
    Rewind();

    // Clear underlying STL container.
    mPath.clear();
}

/*****************************************************************************
 * Rewind to beginning.
 *****************************************************************************/
void
ReplayPath::Rewind( void )
{
    mCursor.Rewind();
}

/*****************************************************************************
 * Append step to animation path.
 * To save space, this won't consecutively repeat the same kinds of Command.
 *****************************************************************************/
void
ReplayPath::Append( const Step& step )
{
CHECK_TYPESIG(&step,TYPESIG_REPLAY_STEP);
CHECK_TYPESIG(step.mCommand,TYPESIG_COMMAND);

    // To save space, this won't consecutively repeat the same kinds of Command.
    if ( not IfSameAsPrevCommand( *step.mCommand ) )
    {
        mPath.push_back( step );
    }
}

/*****************************************************************************
 * @return True if this Command is the same as the previous.
 * Used to save memory by not storing the same consecutive Commands.
 *****************************************************************************/
bool
ReplayPath::IfSameAsPrevCommand( Command& command )
{
CHECK_TYPESIG(&command,TYPESIG_COMMAND);

    if ( not mPath.empty() )
    {
        Command& prevCommand = *mPath.back().mCommand; CHECK_TYPESIG(&prevCommand,TYPESIG_COMMAND);
        return prevCommand.GetType() == command.GetType();
    }
    else
    {
        return false;
    }
}

/*****************************************************************************
 * @return Full pathname to file containing replay data.
 * Data files are stored in the configuration directory.
 *****************************************************************************/
string
ReplayPath::GetPathnameForLoading( const string& filename )
{
ASSERT( not filename.empty() );

#define REPLAY_PATHNAME_USER_CONF_DIR( FILENAME ) \
    (glue::GetConfDirectoryName() + DIRECTORY_SEPARATOR_STRING + (FILENAME))

    // Try user-created replay file (~/.palomino directory).
    string pathname = REPLAY_PATHNAME_USER_CONF_DIR( filename );
    if ( IfFileExists( pathname ) )
    {
        return pathname;
    }
    else
    {
        // Try program's data directory.
        pathname = base::GetDataDir() + DIRECTORY_SEPARATOR_STRING + filename;
        if ( UX( not IfFileExists( pathname ) ) )
            throw Exception( string("ERROR: cannot find replay " ) + filename );
        return pathname;
    }
}

string
ReplayPath::GetPathnameForSaving( const string& filename )
{
ASSERT( not filename.empty() );

    // For saving, note that file might not exist yet.
    // File will be stored in ~/.palomino.
    return REPLAY_PATHNAME_USER_CONF_DIR( filename );
}

////////////////////////////////////////////////////////////////////////////////
//////////////////////////////  ReplayPath::Step  //////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * ctor/dtor.
 * @param   relativeTime
 *          Time since the beginning of recording/playing.
 * @param   command
 *          Command that controls a Craft.
 *****************************************************************************/
ReplayPath::Step::Step( const Milliseconds relativeTime, shptr<Command> command )
:   mMilliseconds(relativeTime),
    mCommand(command)
{
CHECK_TYPESIG(command,TYPESIG_COMMAND);

    SET_TYPESIG(this,TYPESIG_REPLAY_STEP);
}

ReplayPath::Step::Step( void )
:   mMilliseconds(0),
    mCommand(new CommandNop)
{
    SET_TYPESIG(this,TYPESIG_REPLAY_STEP);
}

ReplayPath::Step::~Step()
{
    INVALIDATE_TYPESIG(this,TYPESIG_REPLAY_STEP);
}

////////////////////////////////////////////////////////////////////////////////
/////////////////////////////  ReplayPath::Cursor  /////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * ctor/dtor.
 *****************************************************************************/
ReplayPath::Cursor::Cursor( ReplayPath::Path& path )
:   mPath(path),
    mPathIter(path.begin()),
    mPrevQuaternionStep(),  // default ctor makes NOP Commands
    mNextQuaternionStep(),
    mPrevPositionStep(),
    mNextPositionStep()
{
    // NOP
}

ReplayPath::Cursor::~Cursor()
{
    // NOP
}

/*****************************************************************************
 * Rewind cursor.
 *****************************************************************************/
void
ReplayPath::Cursor::Rewind( void )
{
    // Reset iterator.
    mPathIter = mPath.begin();

    // Forget interpolation steps (invalidate by assigning Steps having CommandNops).
    mPrevQuaternionStep = \
    mNextQuaternionStep = \
    mPrevPositionStep   = \
    mNextPositionStep   = Step();
}

/*****************************************************************************
 * Advance cursor to next Step in path.
 * @pre Cursor::IfEnd() was called first and it returned false.
 * THROWS EXCEPTION.
 *****************************************************************************/
void
ReplayPath::Cursor::Advance( void )
{
// Error if Advance() is called when cursor is at end.
ASSERT( mPathIter != mPath.end() );

    // Remember.
    const Path::iterator prevIter = mPathIter;

    // Increment iterator.
    ++mPathIter;

    // End?
    if ( EX( mPathIter != mPath.end() ) )
    {
        // Track pairs of CommandPosition and CommandQuaternion for interpolation.
        // This is the purpose of the Cursor class.
        const Step& prevStep = *prevIter; CHECK_TYPESIG(&prevStep,TYPESIG_REPLAY_STEP);
        switch ( prevStep.mCommand->GetType() )
        {
            case Command::TYPE_QUATERNION:
            {
                Advance_FindNextStep( mPrevQuaternionStep, mNextQuaternionStep, prevIter, Command::TYPE_QUATERNION );
            }
            break;

            case Command::TYPE_POSITION:
            {
                Advance_FindNextStep( mPrevPositionStep, mNextPositionStep, prevIter, Command::TYPE_POSITION );
            }
            break;

            default:  // NOP
            break;
        }
    }
}

// prevIter points to the previous Step (before Advance() incremented the iterator).
void
ReplayPath::Cursor::Advance_FindNextStep( ReplayPath::Step& prevStep,
                                          ReplayPath::Step& nextStep,
                                          const ReplayPath::Path::iterator prevIter,
                                          const Command::TypeNum commandType )
{
ASSERT( prevIter != mPath.end() );
ASSERT( prevIter->mCommand->GetType() == commandType );

    // The previous iterator is pointing to the first pair of Steps to be interpolated.
    // Find the next Step of the same type.
    ReplayPath::Path::iterator iter = prevIter;
    ++iter;
    for ( ;  // NOP
          iter != mPath.end();
          ++iter )
    {
    CHECK_TYPESIG(iter->mCommand,TYPESIG_COMMAND);

        // Same type of Command?
        if ( iter->mCommand->GetType() == commandType )
        {
            // Found pair of Commands to interpolate.
            prevStep = *prevIter;
            nextStep = *iter;
            return;
        }
    }

    // Not found.  Invalidate the interpolation pair.
    nextStep = prevStep = Step();
}

/*****************************************************************************
 * @return True if cursor is across two quaternions in animation path.
 *****************************************************************************/
bool
ReplayPath::Cursor::IfHaveQuaternionSteps( void )
{
    return mPrevQuaternionStep.mCommand->GetType() == Command::TYPE_QUATERNION
       and mNextQuaternionStep.mCommand->GetType() == Command::TYPE_QUATERNION;
}

/*****************************************************************************
 * @return True if cursor is across two 3D positions in animation path.
 *****************************************************************************/
bool
ReplayPath::Cursor::IfHavePositionSteps( void )
{
    return mPrevPositionStep.mCommand->GetType() == Command::TYPE_POSITION
       and mNextPositionStep.mCommand->GetType() == Command::TYPE_POSITION;
}

/*****************************************************************************
 * @return Two quaternion steps that the cursor is across.
 * @pre Caller must query IfHaveQuaternionSteps() before calling this.
 * THROWS EXCEPTION.
 *****************************************************************************/
ReplayPath::Cursor::QuaternionSteps
ReplayPath::Cursor::GetQuaternionSteps( void )
{
    if ( UX( not IfHaveQuaternionSteps() ) )
        throw Exception( "replay cursor isn't across two quaternions" );
    return std::make_pair<>( mPrevQuaternionStep, mNextQuaternionStep );
}

/*****************************************************************************
 * @return Two position steps that the cursor is across.
 * @pre Caller must query IfHavePositionSteps() before calling this.
 * THROWS EXCEPTION.
 *****************************************************************************/
ReplayPath::Cursor::PositionSteps
ReplayPath::Cursor::GetPositionSteps( void )
{
    if ( UX( not IfHavePositionSteps() ) )
        throw Exception( "replay cursor isn't across two positions" );
    return std::make_pair<>( mPrevPositionStep, mNextPositionStep );
}

} // namespace control
