/*
 Copyright (C) 2002 Andreas Thiede ( a.thiede@berlin.de )

 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include <IO/Input/QTInput.hpp>
#include <IO/Manager/IOManager.hpp>
#include <IO/Manager/IOBuffer.hpp>
#include <IO/Manager/IOStream.hpp>
#include <IO/Manager/IOInput.hpp>
#include <IO/Manager/IOModel.hpp>

#include <QuickTime/Movies.h>

#import <ctype.h>
#import <unistd.h>
#import <stdlib.h>
#import <time.h>
#import <sys/time.h>

#import <lib/Preferences.h>

#import <util/file.h>
#import <util/str.h>
#import <util/Loader.h>

extern "C" OSErr FSSpecToNativePathName( FSSpecPtr theFile, char * pathname, UInt16 pathnameMaxBufferSize, long flags );
extern "C" OSErr NativePathNameToFSSpec( const char * pathname, FSSpecPtr theFile, long flags );

#define MAX_BUFFERS 10

static IOBuffer *  videoRingBuffer[MAX_BUFFERS];
static int         currentVideoBuffer = 0;
static QTInput  * theQTInput = 0;

static void initVideoRingBuffer()
{
    for( int i = 0; i < MAX_BUFFERS; i++ ) {

        int size = 720*576*4;
        
        char * buf = ( char *) malloc( size  );

        IOBuffer * buffer = IOBuffer::initWithConstPointer( buf, size, i );

        videoRingBuffer[i] = buffer;
    }
}

static IOBuffer * nextVideoBuffer()
{
    IOBuffer * b = videoRingBuffer[ currentVideoBuffer++ ];
    
    if( currentVideoBuffer >= MAX_BUFFERS ) {
        currentVideoBuffer = 0;
    }
    return b;
}


//-----------------------------------------------------------------------------------------------------------------------

/* static */ pascal OSErr QTInput::movieDrawProc( Movie movie, long refcon)
{
    #pragma unused (movie, refcon)
    
    theQTInput->update();
    
    return noErr;
}

static void Swap( unsigned * dst, const unsigned * src, int width, int height ) 
{
    register int n;
    
    for( n = 0; n < width*height; n++ ) {

        register char * s = (char*) & src[n];
        register char * d = (char*) & dst[n];
        register char b0,b1,b2,b3;
        
        b0 = s[0];
        b1 = s[1];
        b2 = s[2];
        b3 = s[3];
        
        d[0] = b3;
        d[1] = b2;
        d[2] = b1;
        d[3] = b0;
        
    }
}

void QTInput::update()
{
    if( mStarted ) {
    
        lock();

        //puts("QTInput::update");

#if 1
        IOBuffer * b = nextVideoBuffer();
    
        if( b ) {
    
            Swap( (unsigned*) b->getData(), (unsigned*)  mVideoBuffer, mMovieBox.right,  mMovieBox.bottom );
        
            push( b );
        }
        
        if(  isMovieDone() ) {
        
            IOModel movieDone( "Player.MovieDone" );
         
            movieDone.setValue( "1" );
        }
#else
        IOBuffer * b = IOBuffer::initWithConstPointer( (char*) mVideoBuffer, mMovieBox.right *  mMovieBox.bottom * 4, 
                                                        getNextBlockNumber() );
    
        if( b ) {
    
            //Swap( (unsigned*) b->getData(), (unsigned*)  mVideoBuffer, mMovieBox.right,  mMovieBox.bottom );
        
            push( b );
            
            b->release();
        }
#endif
        unlock();
        
    }
}

bool QTInput::isMovieDone()
{
    if( mStarted && mQTMovie ) {
    
        int      done = IsMovieDone( mQTMovie );
        int      err  =  GetMoviesError();
        unsigned len  = getMovieDuration();
        unsigned now  = getCurrentMovieTime();
        
        //printf("QTInput::isMovieDone done(%d) error(%d) len(%d) now(%d)\n", done, err, len, now );

        return( done || err != noErr || now >= len );
    }
    return true;
}
 
// -----------

bool QTInput::openMovie()
{
    //EnterMovies();
    
    FSSpec  fsspecMovie;
    OSErr   err = noErr;
    Boolean wasChanged;        
    Str255  movieName;

    printf("QTInput::openMovie(%s)\n", mFileName.c_str() );
    
    NativePathNameToFSSpec( mFileName.c_str(), &fsspecMovie, 0 );
    
    err = OpenMovieFile( &fsspecMovie, &mResFile, fsRdPerm );

    if( err == noErr) {
            
        err = NewMovieFromFile( &mQTMovie, mResFile, &mResRefNum, movieName, newMovieActive, &wasChanged );

        CloseMovieFile( mResFile );
                
        if( noErr != err) {
            printf ("Could not get movie from file cause %d\n", err );
        }
        else {

            GDHandle origDevice;
            CGrafPtr origPort;
                        
            GetGWorld( &origPort, &origDevice );

            GetMovieBox( mQTMovie, &mMovieBox );
	
            //OffsetRect( &mMovieBox,  -gMovieRect.left,  -gMovieRect.top);
	
            //SetMovieBox( mQTMovie, &mMovieBox); 
	
            mMovieDuration = GetMovieDuration( mQTMovie );
        
            mMovieDrawUPP = NewMovieDrawingCompleteUPP( movieDrawProc );
	
            SetMovieDrawingCompleteProc( mQTMovie, movieDrawingCallWhenChanged, mMovieDrawUPP,0);    
            
            //err = NewGWorldFromPtr( &mMovieGWorld, 32, &mMovieBox, 0, 0, 0,  mVideoBuffer, mMovieBox.right * 4 );
            err = QTNewGWorldFromPtr( &mMovieGWorld, k32ARGBPixelFormat, &mMovieBox, NULL, NULL, 0, mVideoBuffer, mMovieBox.right * 4 );

            if( err == noErr ) {
            
                SetGWorld( mMovieGWorld, NULL);
            
                SetMovieGWorld( mQTMovie, (CGrafPtr)mMovieGWorld, NULL);
            }
            SetGWorld( origPort, origDevice);
            
            mStepping = false;
        }
    }
    else {
        printf ("Could not open file cause %d\n", err );
    }
    
    return err == noErr;
}

void QTInput::closeMovie()
{
    lock();
    
    if( mQTMovie != 0 ) {
    
        StopMovie( mQTMovie ); 

        DisposeMovie( mQTMovie );
    
        DisposeGWorld( mMovieGWorld );

        DisposeMovieDrawingCompleteUPP( mMovieDrawUPP );

        mQTMovie = 0;
    }
    
    unlock();
}

QTInput::QTInput( const string & aName, IODeviceID anID )
 : IOInput( aName, anID )
{
    init();
}

QTInput::~QTInput()
{
    finit();
}

bool QTInput::isPlaying()
{
    TimeRecord r;
    
    if( mStarted && mQTMovie ) {
    
        if( GetMovieTime( mQTMovie, & r)  >= mMovieDuration )  {
            puts("QTInput::isPlaying implicit stop");
            //stop();
        }
    }
    
    return ( mStarted && mQTMovie != 0 );
}

OSErr QTInput::playAt( TimeValue atTime )
{
    OSErr anErr = noErr;
    
    if( mStarted && mQTMovie ) {
	
        if (atTime == 0L) {
            GoToBeginningOfMovie( mQTMovie ); 
        }
        else if (atTime > mMovieDuration)  {
            SetMovieTimeValue( mQTMovie, mMovieDuration );
        }
        else {
            SetMovieTimeValue( mQTMovie, atTime ); 
        }
    
        anErr = GetMoviesError();
    
        if (noErr == anErr) {
            anErr = UpdateMovie( mQTMovie );
        }
    
        if( noErr == anErr ) {
            MoviesTask( mQTMovie, 0L ); 
            anErr = GetMoviesError();
        }
    }
    return anErr;
}

bool QTInput::start() 
{ 
    if( mInited && ! mStarted ) {

        if( mQTMovie != 0 ) {
        
            IOModel movieDone( "Player.MovieDone" );
         
            movieDone.setValue( "0" );
    
#if 0
            unsigned flags = 0;
        
            flags |= hintsOffscreen | hintsAllowBlacklining | hintsDontDraw | hintsAllowInterlace | hintsDontUseVideoOverlaySurface;
            flags |= hintsPlayingEveryFrame;
        
            SetMoviePlayHints( mQTMovie, flags, -1 );
#endif        
            PrePrerollMovie( mQTMovie, 0, GetMoviePreferredRate( mQTMovie ), 0, 0 );

            PrerollMovie( mQTMovie, 0, GetMoviePreferredRate( mQTMovie ) );
        
            //StartMovie( mQTMovie );
        
            SetMovieRate( mQTMovie, GetMoviePreferredRate( mQTMovie ) );
        
            mCurrentRate = 1.0;
        
            mStarted = true;

            playAt( 0 );
        }
    }
        
    return mStarted;

}

bool QTInput::stop() 
{ 
    mStarted = false;

    closeMovie();
    
    return mStarted;
}

size_t QTInput::frameSize()
{
    return 360*288*4;
}

void QTInput::init()
{
    theQTInput = this;
    
    mVideoBuffer = new char[ 1280 * 1024 * 4 ];

    mInited = true;

    SetRect( &mMovieBox, 0, 0, 360, 288 );

    mQTMovie = 0;
}

void QTInput::finit()
{
    stop();
}

unsigned QTInput::getCurrentMovieTime()
{
    TimeValue result = 0;

    if( mQTMovie ) {
    
        TimeRecord r;
        
        result = GetMovieTime( mQTMovie, & r ) / GetMovieTimeScale( mQTMovie );
    }
    
    return result;
}

unsigned QTInput::getMovieDuration()
{
    TimeValue result = 0;

    if( mQTMovie ) {
        result = GetMovieDuration( mQTMovie ) / GetMovieTimeScale( mQTMovie );
    }
    return result;
}

bool QTInput::command( const string & command, 
                       const string & param1, const string & param2, const string & param3, const string & param4 )
{
    if( command == "toggle" ) {
        
        if( isPlaying() ) {
            stop();
        }
        else {
            start();
        }
    }
    else if( command == "setVideoStream" ) {

        IOStream * videoStream = IOManager::instance().openStream( param1 );
        
        return ( videoStream != 0 && attachTo( videoStream, false ) );
    }
    else if( command == "setFilename" ) {

        mFileName           = param1;
        string shouldStart  = param2;
                
        openMovie();

    }
    else if( command == "forward" || command == "backward" ) {

        if( isPlaying() ) {

            string val = param1;

            TimeScale timeScale = GetMovieTimeScale( mQTMovie );
        
            int m = 0;
        
            switch( val.c_str()[0] ) {
            case 'h':
            case 'H':
                m = 60 * 60;
                break;
            case 'm':
            case 'M':
                m = 60;
                break;
            case 's':
            case 'S':
                m = 1;
                break;
            }
            
            TimeRecord r;
            
            TimeValue currentTime = GetMovieTime( mQTMovie, & r );
            TimeValue newTime = currentTime;
            
            if( command == "forward" ) {
                
                newTime = currentTime + m * timeScale ;
                
                if( newTime >= mMovieDuration ) {
                    newTime = mMovieDuration;
                }
            }
            else {

                newTime = currentTime - m * timeScale ;

                if( newTime < 0 ) {
                    newTime = 0;
                }
            }
            
            printf("%s: was %ld new %ld\n", command.c_str(), currentTime, newTime );
            
            playAt( newTime );
        }
    }
    else if( command == "rate" ) {

        if( isPlaying() ) {

            float val = atof( param1.c_str() );
        
            //mCurrentRate *= val;
            
            Fixed currentRate = GetMovieRate( mQTMovie );
            
            short r = FixRound( currentRate );
            
            float newRate = val * r;
            
            if( newRate > 0 ) {
                SetMovieRate( mQTMovie, X2Fix( (double) newRate ) );
            }
        }
    }
    else if( command == "step" ) {

        if( isPlaying() ) {

            TimeScale timeScale = GetMovieTimeScale( mQTMovie );

            TimeRecord r;

            TimeValue currentTime = GetMovieTime( mQTMovie, & r );

            TimeValue newTime;
            
            if( param1 == "forward" ) {
            
                    newTime = currentTime + TimeValue( ( 1.0 * (float) timeScale));
            }
            else if( param1 == "backward" ) {
            
                newTime = currentTime + TimeValue( ( -1.0 * (float) timeScale));
            }
            else if( param1 == "fastforward" ) {
            
                    newTime = currentTime + TimeValue(  0.01 * mMovieDuration );
            }
            else if( param1 == "fastbackward" ) {
            
                newTime = currentTime + TimeValue( -0.01 * mMovieDuration );
            }

            if( newTime < 0 )
                newTime = 0;
                
            if( newTime > mMovieDuration )
                newTime = ( mMovieDuration - timeScale );
                
            playAt( newTime );
        }
    }
    else if( command == "togglePause" ) {

        if( isPlaying() ) {
        
            if( mStepping ) {
                mStepping = false;
                StartMovie( mQTMovie );
            }
            else {
                mStepping = true;
                StopMovie( mQTMovie );
            }
        }
    }
    else if( command == "goto" ) {

        if( isPlaying() ) {

            float val = atof( param1.c_str() );

            if( val < 0 )
                val = 0;
            
            if( val > 1.0 )
                val = 1.0;
                
            TimeValue time = TimeValue( val * mMovieDuration );

            if( time < 0 )
                time = 0;
                
            if( time > mMovieDuration )
                time = ( mMovieDuration - GetMovieTimeScale( mQTMovie ) );
            
            playAt( time );
        }
    }
    
    return true;
}

string QTInput::getValue( const string & valueName )
{
    char buffer[256];

    strcpy( buffer, "0" );
    
    if( valueName == "frameWidth" ) {
        sprintf( buffer, "%d", mMovieBox.right );
    }
    else if( valueName == "frameHeight" ) {
        sprintf( buffer, "%d", mMovieBox.bottom );
    }
    printf( "%s = %s\n", valueName.c_str(), buffer );
    
    return buffer;
}

extern "C" int initQTPlayerPlugin( int argc, const char ** argv )
{
    QTInput * qt = new QTInput( "QTPlayer", 0 );
    
    IOManager::instance().registerNode ( qt );

    initVideoRingBuffer();

    return true;
}
