#include "ofVideoPlayer.h"


//--------------------------------------------------------------
bool  	createMovieFromPath(char * path, Movie * movie);
bool 	createMovieFromPath(char * path, Movie * movie){
	
	Boolean 	isdir			= false;
	OSErr 		result 			= 0;
	FSSpec 		theFSSpec;
    FSRef 		fsref;
	short 		resRefNum 		= -1;	
	short 		actualResId 	= DoTheRightThing;

	#ifdef TARGET_WIN32
		result = NativePathNameToFSSpec (path, &theFSSpec, 0);
		if (result) { printf("NativePathNameToFSSpec failed %d\n", result); printf("ERROR LOADINGmovie \n"); return false; }
	#endif
	#ifdef TARGET_OSX
		result = FSPathMakeRef((const UInt8*)path, &fsref, &isdir);
		if (result) { printf("FSPathMakeRef failed %d\n", result); printf("ERROR LOADINGmovie \n"); return false; }
		result = FSGetCatalogInfo(&fsref, kFSCatInfoNone, NULL, NULL, &theFSSpec, NULL);
		if (result) { printf("FSGetCatalogInfo failed %d\n", result); printf("ERROR LOADINGmovie \n"); return false; }
	#endif

	result = OpenMovieFile (&theFSSpec, &resRefNum, 0);
	if (result) { printf("OpenMovieFile failed %d\n", result); printf("ERROR LOADINGmovie \n"); return false; }
	result = NewMovieFromFile (movie, resRefNum, &actualResId, (unsigned char *) 0, 0, (Boolean *) 0);
	if (result) { printf("NewMovieFromFile failed %d\n", result); printf("ERROR LOADINGmovie \n"); return false; }
	if (resRefNum != -1) CloseMovieFile (resRefNum);
	
	return true;
}		



//--------------------------------------------------------------
OSErr 	DrawCompleteProc(Movie theMovie, long refCon);
OSErr 	DrawCompleteProc(Movie theMovie, long refCon){
	
	videoPlayerData * vd = (videoPlayerData *)refCon;
	convertPixels(vd->offscreenGWorldPixels, vd->pixels, vd->w, vd->h);
	vd->bHavePixelsChanged = true;	
	// now if the pixels have changed AND we are 
	// using a texture, let's update the texture
	if (vd->bUseTexture == true){
		vd->tex->loadData(vd->pixels, vd->w, vd->h, GL_RGB);
	}

	
	return noErr;
}

//---------------------------------------------------------------------------
ofVideoPlayer::ofVideoPlayer (){
	bLoadedMovie 				= false;	
	width 						= 0;
	height						= 0;
	vpd 						= new videoPlayerData();
	vpd->moviePtr	 			= NULL;
	vpd->w						= 0;
	vpd->h 						= 0;
	bUseTexture					= true;
}

//---------------------------------------------------------------------------
unsigned char * ofVideoPlayer::getPixels(){
	return vpd->pixels;
}

//---------------------------------------------------------------------------
bool ofVideoPlayer::isFrameNew(){
	if (bLoadedMovie == true){
		bool bChanged = vpd->bHavePixelsChanged;
		if (vpd->bHavePixelsChanged == true) {
			vpd->bHavePixelsChanged = false;
		}
		return bChanged;
	} else {
		return false;
	}
}

//---------------------------------------------------------------------------
void ofVideoPlayer::idleMovie(){
    
    // [zach] my question is how often to call this?
    // I don't want to call it too often, and up cpu usage
    // 
    // shockingly,(or not) on a mac, it seems it's not even necessary to call it
    // the callback just works...
    
    // google " site:lists.apple.com MoviesTask windows mac
    
    // some code I've seen uses a thread to call this "JUCE"
    // maybe I could use ofElapsedTimeMillis()
    // or a system clock call (not glut call)
    // and make this less frequent
   
    if (bLoadedMovie == true){
    	MoviesTask(vpd->moviePtr,0);
    }
}

//---------------------------------------------------------------------------
void ofVideoPlayer::closeMovie(){
    Movie moviePtr = vpd->moviePtr;
    DisposeMovie (vpd->moviePtr);
    #ifdef TARGET_WIN32
		DisposeMovieDrawingCompleteUPP (myDrawCompleteProc);
    #endif
	ExitMovies ();
}




//---------------------------------------------------------------------------
bool ofVideoPlayer::loadMovie(char * name){

	OSErr result = 0;
	short resRefNum = -1;
	bLoadedMovie = false;
	Rect bounds;

    //----------------- callback method
    MovieDrawingCompleteUPP myDrawCompleteProc; 
    myDrawCompleteProc = NewMovieDrawingCompleteUPP (DrawCompleteProc);

	initializeQuicktime();
	
	vpd->bHavePixelsChanged = false;
    Movie * moviePtr = &(vpd->moviePtr);
	createMovieFromPath(name, moviePtr);
	
	//----------------------------------movie box
	GetMovieBox(vpd->moviePtr, &(vpd->movieRect));
	width 						= vpd->movieRect.right;
	height 						= vpd->movieRect.bottom;
	vpd->w 	= vpd->movieRect.right;
	vpd->h 	= vpd->movieRect.bottom;
	
	//---------------------------------- buffer allocation
	// Create a buffer big enough to hold the video data, 
	// make sure the pointer is 32-byte aligned.
	// also the rgb image that people will grab
	vpd->offscreenGWorldPixels 	= (unsigned char*)malloc(4 * width * height + 32);
	vpd->pixels					= new unsigned char[width*height*3];
	//---------------------------------- gworld allocation
	
	
	QTNewGWorldFromPtr (&(vpd->offscreenGWorld), k32ARGBPixelFormat, &(vpd->movieRect), NULL, NULL, 0, (vpd->offscreenGWorldPixels), 4 * width);
	LockPixels(GetGWorldPixMap(vpd->offscreenGWorld));
	SetGWorld (vpd->offscreenGWorld, NULL);
	SetMovieGWorld (vpd->moviePtr, vpd->offscreenGWorld, nil);
	
	//----------------- callback method
	SetMovieDrawingCompleteProc (vpd->moviePtr, movieDrawingCallWhenChanged, 
    myDrawCompleteProc, (long)vpd); 

	//---------------------------------- reset themovie, get ready to roll
	GoToBeginningOfMovie(vpd->moviePtr);
	SetMovieActiveSegment(vpd->moviePtr, -1,-1);
	
	if (vpd->moviePtr == NULL){
		return false;
	}

	SetMovieActive(vpd->moviePtr, true);
	StartMovie(vpd->moviePtr);
	
	
	//------------------ set the movie rate to default 
	//------------------ and preroll, so the first frames come correct
	
	TimeValue timeNow;
	Fixed playRate;
	timeNow = GetMovieTime(vpd->moviePtr, 0);
	playRate =GetMoviePreferredRate(vpd->moviePtr);
	PrePrerollMovie(vpd->moviePtr, timeNow, playRate, 0, 0);
	PrerollMovie(vpd->moviePtr, timeNow, playRate);
	SetMovieRate(vpd->moviePtr, playRate);
	
	setLoopState(OF_QT_LOOP_NORMAL);
	bLoadedMovie = true;
	
	// get some pixels in there right away.
	MoviesTask(vpd->moviePtr,0);
	convertPixels(vpd->offscreenGWorldPixels, vpd->pixels, vpd->w, vpd->h);
	
	
	vpd->bUseTexture = bUseTexture;
	vpd->tex = &tex;
	
	if (bUseTexture){
		// create the texture, set the pixels to black and 
		// upload them to the texture (so at least we see nothing black the callback)
		tex.allocate(width,height,GL_RGB);
		memset(vpd->pixels, 0, width*height*3);
		vpd->tex->loadData(vpd->pixels, vpd->w, vpd->h, GL_RGB);
	}
	
	return true;
}

//--------------------------------------------------------
void ofVideoPlayer::setVolume(int volume){
	Movie moviePtr = vpd->moviePtr;
	SetMovieVolume(moviePtr, volume);
}

//--------------------------------------------------------
void ofVideoPlayer::setLoopState(int state){
	
	Movie moviePtr = vpd->moviePtr;	
	TimeBase myTimeBase;
	long myFlags = 0L;
	myTimeBase = GetMovieTimeBase(moviePtr);
	myFlags = GetTimeBaseFlags(myTimeBase);

	switch (state) {

		case OF_QT_LOOP_NORMAL:
			myFlags |= loopTimeBase;
			myFlags &= ~palindromeLoopTimeBase;
			SetMoviePlayHints(moviePtr, hintsLoop, hintsLoop);
			SetMoviePlayHints(moviePtr, 0L, hintsPalindrome);
			break;

		case OF_QT_LOOP_PALINDROME:
			myFlags |= loopTimeBase;
			myFlags |= palindromeLoopTimeBase;
			SetMoviePlayHints(moviePtr, hintsLoop, hintsLoop);
			SetMoviePlayHints(moviePtr, hintsPalindrome, hintsPalindrome);
			break;
		case OF_QT_LOOP_NONE:
			default:
			myFlags &= ~loopTimeBase;
			myFlags &= ~palindromeLoopTimeBase;
			SetMoviePlayHints(moviePtr, 0L, hintsLoop | 
			hintsPalindrome);
			break;
	}

	SetTimeBaseFlags(myTimeBase, myFlags);


}


//---------------------------------------------------------------------------
void ofVideoPlayer::setPosition(float pct){
 	TimeRecord tr;
 	Movie moviePtr 	= vpd->moviePtr;
	tr.base 		= GetMovieTimeBase(moviePtr);
	long total 		= GetMovieDuration(moviePtr );
	long newPos 	= (long)((float)total * pct);	
	SetMovieTimeValue(moviePtr, newPos);	
}	

//---------------------------------------------------------------------------
float ofVideoPlayer::getDuration(){
	Movie moviePtr = vpd->moviePtr;
	return GetMovieDuration (moviePtr) / (double) GetMovieTimeScale (moviePtr);	
}

//---------------------------------------------------------------------------
float ofVideoPlayer::getPosition(){
	Movie moviePtr 	= vpd->moviePtr;
	long total 		= GetMovieDuration(moviePtr);
	long current 	= GetMovieTime(moviePtr, nil);
	float pct 		= ((float)current/(float)total);
	return pct;
}

//---------------------------------------------------------------------------
void ofVideoPlayer::setSpeed(float _speed){
	Movie moviePtr 	= vpd->moviePtr;
	speed 			= _speed;
	SetMovieRate(moviePtr, X2Fix(speed));
}

//---------------------------------------------------------------------------
float ofVideoPlayer::getSpeed(){
	return speed;
}

//---------------------------------------------------------------------------
void ofVideoPlayer::setPaused(bool bPause){
	// there might be a more "quicktime-ish" way (or smarter way)
	// to do this for now, to pause, just set the movie's speed to zero, 
	// on un-pause, set the movie's speed to "speed" 
	// (and hope that speed != 0...)
	if (bPause == true) 	SetMovieRate(vpd->moviePtr, X2Fix(0));
	else 					setSpeed(speed);
}
		
//------------------------------------
void ofVideoPlayer::setUseTexture(bool bUse){
	bUseTexture = bUse;
	vpd->bUseTexture = bUseTexture;
	vpd->tex = &tex;
}
				
//------------------------------------
void ofVideoPlayer::draw(float _x, float _y, float _w, float _h){
	if (bUseTexture){
		tex.draw(_x, _y, _w, _h);
	}
}

//------------------------------------
void ofVideoPlayer::draw(float _x, float _y){
	draw(_x, _y, width, height);
	
}
	


