#include "ofVideoGrabber.h"



//--------------------------------------------------------------------
ofVideoGrabber::ofVideoGrabber(){
	
	//---------------------------------
	#ifdef OF_VIDEO_CAPTURE_QUICKTIME
	//---------------------------------  
	
		initializeQuicktime();
		vgd 				= new videoGrabberData();
		vgd->bSgInited		= false;
		
	//---------------------------------  
	#endif 
	//---------------------------------
	
	
	//---------------------------------
	#ifdef OF_VIDEO_CAPTURE_DIRECTSHOW
	//---------------------------------  
	
		bVerbose 			= false;
		bFrameNew 			= false;
		
	//---------------------------------  	
	#endif 
	//---------------------------------
	
	// common
	bVerbose 				= false;
	bSgGrabberInited 		= false;
	bUseTexture				= true;
	bChooseDevice			= false;
	deviceID				= 0;
	width 					= 320;
	height 					= 240;
	
}




//--------------------------------------------------------------------
void ofVideoGrabber::listDevices(){
	
	//--------------------------------- 
	#ifdef OF_VIDEO_CAPTURE_QUICKTIME
	//---------------------------------  
	
		initGrabbingComponent(vgd, true);		// if necessary, start the sg object
		
		if (!vgd->bSgInited){
			printf("error in list devices, couldn't allocate grabbing component\n");
			return;	
		}
		printf("-------------------------------------\n");
		SGDeviceList deviceList;
		SGGetChannelDeviceList (vgd->gVideoChannel, sgDeviceListIncludeInputs, &deviceList);
		unsigned char pascalName[64];
		if(true)
		{
			printf("listing available capture devices\n");
			for(int i = 0 ; i < (*deviceList)->count ; ++i)
			{
				SGDeviceName nameRec;
				nameRec = (*deviceList)->entry[i];
				memcpy(pascalName, (*deviceList)->entry[i].name, sizeof(char) * 64);
				if(nameRec.flags != sgDeviceNameFlagDeviceUnavailable) printf("device[%i] %s \n",  i, p2cstr(pascalName) );
				else printf("(unavailable) device[%i] %s \n",  i, p2cstr(pascalName) );
			
			}
		}
		printf("-------------------------------------\n");
	
	//---------------------------------  
	#endif 
	//---------------------------------
	
	
	//---------------------------------
	#ifdef OF_VIDEO_CAPTURE_DIRECTSHOW
	//---------------------------------  
		printf("---\n");
		VI.listDevices();
		printf("---\n");
	
	//---------------------------------  	
	#endif 
	//---------------------------------
	
	
}

//--------------------------------------------------------------------
void ofVideoGrabber::setVerbose(bool bTalkToMe){
	bVerbose = bTalkToMe;
}


//--------------------------------------------------------------------
void ofVideoGrabber::setDeviceID(int _deviceID){
	deviceID		= _deviceID;
	bChooseDevice	= true;
}

//---------------------------------------------------------------------------
unsigned char * ofVideoGrabber::getPixels(){
	
	//---------------------------------  
	#ifdef OF_VIDEO_CAPTURE_QUICKTIME
	//---------------------------------  
	
		return vgd->pixels;
	
	//---------------------------------  
	#endif
	//---------------------------------  
	
	//---------------------------------  
	#ifdef OF_VIDEO_CAPTURE_DIRECTSHOW
	//---------------------------------  
	
		return pixels;
	
	//---------------------------------  
	#endif
	//---------------------------------  
	
}

//---------------------------------------------------------------------------
bool  ofVideoGrabber::isFrameNew(){
	
	//---------------------------------  
	#ifdef OF_VIDEO_CAPTURE_QUICKTIME
	//---------------------------------  
	
		if (bSgGrabberInited == true){
			bool bNew = false;
			if (vgd->bHavePixelsChanged == true){
				bNew = true;
				vgd->bHavePixelsChanged = false;
			}
			return bNew;
		} else {
			return false;
		}
		
	//---------------------------------  	
	#endif
	//---------------------------------  
	
	
	//---------------------------------  
	#ifdef OF_VIDEO_CAPTURE_DIRECTSHOW
	//---------------------------------  
		
		if (bFrameNew == true){
			bFrameNew = false;
			return true;
		} else {
			return false;
		}
	//---------------------------------  	
	#endif 
	//---------------------------------  
		
}

//--------------------------------------------------------------------
void ofVideoGrabber::grabFrame(){
	
	//--------------------------------- 
	#ifdef OF_VIDEO_CAPTURE_QUICKTIME
	//---------------------------------  
		
		if (bSgGrabberInited == true){
			SGIdle(vgd->gSeqGrabber);
			// set the top pixel alpha = 0, so we can know if it 
			// was a new frame or not..
			// or else we will process way more than necessary
			// (ie opengl is running at 60fps +, capture at 30fps)
			if (vgd->offscreenGWorldPixels[0] != 0x00){
				vgd->offscreenGWorldPixels[0] = 0x00;
				vgd->bHavePixelsChanged = true;
				convertPixels(vgd->offscreenGWorldPixels, vgd->pixels, vgd->w, vgd->h);
				if (bUseTexture){
					tex.loadData(vgd->pixels, vgd->w, vgd->h, GL_RGB);
				}
			} 
		}
	
	//---------------------------------  
	#endif 
	//---------------------------------  
	
	//---------------------------------  
	#ifdef OF_VIDEO_CAPTURE_DIRECTSHOW
	//---------------------------------  
	
		if (bSgGrabberInited == true){
			if (VI.isFrameNew(device)){
				bFrameNew = true;
				// VI.getPixels(device, true) // true = flip BGR to RGB
				// ok, if we are 0,0 top left, we need to flip this image:
				if (ofbOriginBottomLeft() == true){
					// do nothing, just copy
					memcpy(pixels, VI.getPixels(device, true), width*height*3);
				} else {
					// else, let's flip!
					unsigned char * viPixels = VI.getPixels(device, true);
					int widthOfLine = width * 3;
					for (int i = 0; i < height; i++){
						memcpy(pixels + (i * widthOfLine), viPixels + ((height - i - 1) * (widthOfLine)), widthOfLine);
					}
				}
				
				if (bUseTexture){
					tex.loadData(pixels, width, height, GL_RGB);
				}
			} 
		}
	
	//---------------------------------  	
	#endif 
	//---------------------------------  
}

//--------------------------------------------------------------------
void ofVideoGrabber::close(){
	
	//--------------------------------- 
	#ifdef OF_VIDEO_CAPTURE_QUICKTIME
	
		//thanks eloi!
		if (vgd->gSeqGrabber != 0L){
			CloseComponent (vgd->gSeqGrabber);
			vgd->gSeqGrabber = 0L;
		}
	
	#endif 
	
}

//--------------------------------------------------------------------
void ofVideoGrabber::videoSettings(void){
	
	//--------------------------------- 
	#ifdef OF_VIDEO_CAPTURE_QUICKTIME
	//---------------------------------  
		
		Rect	curBounds, curVideoRect;
		ComponentResult	err;

		// Get our current state
		err = SGGetChannelBounds (vgd->gVideoChannel, &curBounds);
		if (err != noErr){
			printf("Error in SGGetChannelBounds \n");
			return;
		}
		err = SGGetVideoRect (vgd->gVideoChannel, &curVideoRect);
		if (err != noErr){
			printf("Error in SGGetVideoRect \n");
			return;
		}

		// Pause
		err = SGPause (vgd->gSeqGrabber, true);
		if (err != noErr){
			printf("Error in SGPause\n");
			return;
		}
		
		#ifdef TARGET_OSX
			static SGModalFilterUPP gSeqGrabberModalFilterUPP = NewSGModalFilterUPP(SeqGrabberModalFilterUPP);
			ComponentResult result = SGSettingsDialog(vgd->gSeqGrabber,  vgd->gVideoChannel, 0, nil, seqGrabSettingsPreviewOnly, gSeqGrabberModalFilterUPP, nil);
			if (err != noErr){
				printf("error in  dialogue\n");
				return;
			}
		#else 
			SGSettingsDialog(vgd->gSeqGrabber, vgd->gVideoChannel, 0, nil, seqGrabSettingsPreviewOnly, NULL, 0);
		#endif

		SGSetChannelBounds(vgd->gVideoChannel, &vgd->videoRect);
		SGPause (vgd->gSeqGrabber, false);	
	
	//---------------------------------  
	#endif
	//---------------------------------  
	
	//---------------------------------  
	#ifdef OF_VIDEO_CAPTURE_DIRECTSHOW
	//---------------------------------  
		
		if (bSgGrabberInited == true) VI.showSettingsWindow(device);
	
	//---------------------------------  
	#endif
	//---------------------------------  
	
}


//--------------------------------------------------------------------
void ofVideoGrabber::initGrabber(int w, int h){
	
	//--------------------------------- 
	#ifdef OF_VIDEO_CAPTURE_QUICKTIME
	//---------------------------------  
	
		vgd->w = w;
		vgd->h = h;
		OSStatus err;
		initGrabbingComponent(vgd, true);
	
		if (!vgd->bSgInited){
			printf("error in init grabber, couldn't allocate grabbing component\n");
			return;	
		}
		
		
		MacSetRect(&(vgd->videoRect),0, 0, w, h);	
		width 		= w;
		height 		= h;
		
		//---------------------------------- 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
		
		vgd->offscreenGWorldPixels 	= (unsigned char*)malloc(4 * w * h + 32);
		vgd->pixels					= new unsigned char[w*h*3];
		QTNewGWorldFromPtr (&vgd->videogworld, k32ARGBPixelFormat, &vgd->videoRect, NULL, NULL, 0, vgd->offscreenGWorldPixels, 4 * w);
		LockPixels(GetGWorldPixMap(vgd->videogworld));
		SetGWorld (vgd->videogworld, NULL);
		SGSetGWorld(vgd->gSeqGrabber, vgd->videogworld, nil);
		
		
		//---------------------------------- choose a specific device to setup
		if(bChooseDevice)
		{
			//Generate a device list and enumerate 
			//all devices availble to the channel 
			SGDeviceList deviceList;
			SGGetChannelDeviceList (vgd->gVideoChannel, sgDeviceListIncludeInputs, &deviceList);
		
			unsigned char pascalName[64];
			//List all available devices
			if(bVerbose)
			{
				printf("listing available capture devices\n");
				for(int i = 0 ; i < (*deviceList)->count ; ++i){
					memcpy(pascalName, (*deviceList)->entry[i].name, sizeof(char) * 64);
					printf("device[%i] %s \n",  i, p2cstr(pascalName) );
				}
			}
			
			//check that the device requested exists
			if(deviceID+1 <= (*deviceList)->count) 
			{
				
				SGDeviceName nameRec;
				nameRec = (*deviceList)->entry[deviceID];
				if(nameRec.flags != sgDeviceNameFlagDeviceUnavailable) {
				
					unsigned char camStr[255];
					memcpy(camStr, (*deviceList)->entry[deviceID].name, sizeof(char) * 64);	
					memcpy(pascalName, (*deviceList)->entry[deviceID].name, sizeof(char) * 64);	
					p2cstr(camStr);
					
					if(bVerbose)printf("attempting to setup device[%i] (%s) \n",  deviceID, camStr);
					memcpy(pascalName, (*deviceList)->entry[deviceID].name, sizeof(char) * 64);	
					
					err = SGSetChannelDevice(vgd->gVideoChannel, pascalName);
					if ( err != noErr )
					{
						printf("***** Error in SGSetChannelDevice - setting device[%i] %s *****\n", deviceID, pascalName);
						goto bail;
					}
				} else {
					printf("device[%i] in use - using next default device\n", deviceID);
				
				}
			}
			else printf("device[%i] unavailable - using next default device\n", deviceID);
				
		}
		
	 	err = SGSetChannelUsage(vgd->gVideoChannel,seqGrabPreview);
		if ( err != noErr ) goto bail;
		
		err = SGSetChannelBounds(vgd->gVideoChannel, &vgd->videoRect);
		if ( err != noErr ) goto bail;
		
		err = SGPrepare(vgd->gSeqGrabber,  true, false); //theo swapped so preview is true and capture is false
		if ( err != noErr ) goto bail;
		
		err = SGStartPreview(vgd->gSeqGrabber);
		if ( err != noErr ) goto bail;
		
		
		if (bVerbose){
			printf ("end setup ofVideoGrabber\n");
			printf ("-------------------------------------\n");
			printf ("\n");
		}
		
		
		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(w,h,GL_RGB);
			memset(vgd->pixels, 0, w*h*3);
			tex.loadData(vgd->pixels, w, h, GL_RGB);
		}

		
		bSgGrabberInited = true;
		
		return;				// good catch eloi/theo
		
		//--------------------- (bail) something's wrong -----	
		bail:
			
			if (bVerbose){
				printf ("***** ofVideoGrabber error *****\n");
				printf ("-------------------------------------\n");
				printf ("\n");
			}
			
			bSgGrabberInited = false;
			return;
		
	//---------------------------------  	
	#endif 
	//---------------------------------
	
	
	//---------------------------------  
	#ifdef OF_VIDEO_CAPTURE_DIRECTSHOW
	//---------------------------------  

		if (bChooseDevice){
			device = deviceID;
			printf("choosing %i  \n", deviceID);
		} else {
			device = 0;
		}
		//device = 0;
		
		bool bOk = VI.setupDevice(device, w, h);
		
		if (bOk == true){
			bSgGrabberInited = true;
			width 	= VI.getWidth(device);
			height 	= VI.getHeight(device);
			
			pixels	= new unsigned char[width * height * 3];
			
			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(pixels, 0, width*height*3);
				tex.loadData(pixels, width, height, GL_RGB);
			}			  
		} else {
			printf("error allocating a video device  \nplease check your camera with AMCAP or other software\n");
		}
		
	//---------------------------------  
	#endif
	//---------------------------------  

	
	
}

//------------------------------------
void ofVideoGrabber::setUseTexture(bool bUse){
	bUseTexture = bUse;
}
		
		
//------------------------------------
void ofVideoGrabber::draw(float _x, float _y, float _w, float _h){
	if (bUseTexture){
		tex.draw(_x, _y, _w, _h);
	}
}

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

