#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, newMovieActive | newMovieAsyncOK, (Boolean *) 0);


	
	
	//----------------------------- I don't know, it seems 
	//----------------------------- we never get an async load
	//-----------------------------
	long loadState; //= 	GetMovieLoadState(*movie);
	do
	{
	long newLoadState;
	// Get new load state to see if there was a change in
	// state.
	newLoadState = GetMovieLoadState(*movie);
	if (newLoadState != loadState) {
	loadState = newLoadState;
	if (loadState < 0) {
	}

	if (loadState < kMovieLoadStatePlayable) {
	printf("here 0\n");
	MoviesTask(*movie, 0);
	
	}

	if (loadState < kMovieLoadStateComplete) {
	printf("here 1 \n");
	}

	if (loadState > kMovieLoadStateComplete) {
	printf("here 2\n");
	}
	}
	} 
	while ((loadState > kMovieLoadStateError) && (loadState <
	kMovieLoadStateComplete));
	
	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){
		printf("uploading even %i %i \n", vd->pixels[0], vd->pixels[1]);
		vd->tex->loadData(vd->pixels, vd->w, vd->h, GL_RGB);
	}

	
	return noErr;
}

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

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

//---------------------------------------------------------------------------
bool ofVideoPlayer::isFrameNew(){
	if (bLoaded == 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
    // 
    // or - use glut timer function!
    // that's an option, really. 
    
    if (bLoaded == true){
    	MoviesTask(vpd->moviePtr,0);
    }
}

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




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

	OSErr result = 0;
	short resRefNum = -1;
	bLoaded = false;
	Rect bounds;
	
	bStarted = false;
	
	
    //----------------- callback method
    MovieDrawingCompleteUPP myDrawCompleteProc; 
    myDrawCompleteProc = NewMovieDrawingCompleteUPP (DrawCompleteProc);

	initializeQuicktime();
	
	closeMovie();
	
	
	
	vpd->bHavePixelsChanged = false;
    Movie * moviePtr = &(vpd->moviePtr);
	createMovieFromPath(name, moviePtr);
	
	
	bool bDoWeAlreadyHaveAGworld = false;
	if (vpd->w != 0 && vpd->h != 0){
		bDoWeAlreadyHaveAGworld = true;
	}
	
	
	//----------------------------------movie box
	GetMovieBox(vpd->moviePtr, &(vpd->movieRect));
	width 						= vpd->movieRect.right;
	height 						= vpd->movieRect.bottom;
	
	if (bDoWeAlreadyHaveAGworld){
		// is the gworld the same size, then lets not de-allocate and reallocate:
		if (vpd->w == vpd->movieRect.right && 
			vpd->h == vpd->movieRect.bottom){
			
			// we are the same size, do nothing, just point the movie at the gworld...
			// yay! 
			
			SetMovieGWorld (vpd->moviePtr, vpd->offscreenGWorld, nil);

	
		} else {
		
			vpd->w 	= vpd->movieRect.right;
			vpd->h 	= vpd->movieRect.bottom;
			
			//---------------------------------- buffer allocation
			
			// (a) delete the old
			delete(vpd->pixels);
			delete(vpd->offscreenGWorldPixels);
			if ((vpd->offscreenGWorld)) DisposeGWorld((vpd->offscreenGWorld));
			
			// (b) make new
			// 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 	= new unsigned char[4 * width * height + 32];
			vpd->pixels					= new unsigned char[width*height*3];
			
			
			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);

			//------------------------------------ texture stuff:
			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);
			}
			
		}
	} else {
		
		vpd->w 	= vpd->movieRect.right;
		vpd->h 	= vpd->movieRect.bottom;
			
		// (a) everything is new
		//---------------------------------- 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 	= new unsigned char[4 * width * height + 32];
		vpd->pixels					= new unsigned char[width*height*3];
		
		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);
		

		//------------------------------------ texture stuff:
		vpd->bUseTexture = bUseTexture;
		vpd->tex = &tex;

		if (bUseTexture){
			printf("here texture allocation %i  %i \n", width, height);
			// 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);
		}
		
	}

	//----------------- 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;
	}

	bStarted 		= false;
	bLoaded 		= true;
	bPlaying 		= false;
	speed 			= 1;
	
	
	
	return true;
}

void ofVideoPlayer::start(){

	if (bLoaded == true && bStarted == false){
		SetMovieActive(vpd->moviePtr, true);
		
		//------------------ 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);
		
		PrerollMovie(vpd->moviePtr, timeNow, X2Fix(speed));
		SetMovieRate(vpd->moviePtr,  X2Fix(speed));
		setLoopState(OF_QT_LOOP_NORMAL);
	
		// get some pixels in there right away.
		MoviesTask(vpd->moviePtr,0);
		convertPixels(vpd->offscreenGWorldPixels, vpd->pixels, vpd->w, vpd->h);
		bStarted = true;
		bPlaying = true;
	}
}

//--------------------------------------------------------
void ofVideoPlayer::play(){
	if (!bStarted){
	 	start();
	}else {
		// ------------ lower level "startMovie"
		// ------------ use desired speed & time (-1,1,etc) to Preroll...
		bPlaying = true;
		TimeValue timeNow;
	   	timeNow = GetMovieTime(vpd->moviePtr, nil);
		PrerollMovie(vpd->moviePtr, timeNow, X2Fix(speed));
		SetMovieRate(vpd->moviePtr,  X2Fix(speed));
		MoviesTask(vpd->moviePtr, 0);
	}
} 

//--------------------------------------------------------
void ofVideoPlayer::stop(){
	StopMovie (vpd->moviePtr);
	bPlaying = false;
	// ? useful ? SetMovieActive(vpd->moviePtr, false);
}

//--------------------------------------------------------
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;
	float speedNow 		= Fix2X(GetMovieRate (moviePtr ));
	speed 				= _speed;
	
	if (bPlaying == true){
		//setMovieRate actually plays, so let's call it only when we are playing
		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 (bPlaying == true){
		if (bPause == true) 	SetMovieRate(vpd->moviePtr, X2Fix(0));
		else 					SetMovieRate(vpd->moviePtr, X2Fix(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);
}
	


