//--------------------------------------------------------------
void ofApp::setup(){
	
	//---------------------------
	// app properties
	ofSetVerticalSync(false);
	bMousePressed   = false;
	bCenterChanged  = false;
	bPlayerPaused   = false;
	bAngularOffsetChanged = false;
	bMousePressedInPlayer = false;
	bMousepressedInUnwarped = false;
	bSavingOutVideo = false;
	bSaveAudioToo   = false;
	nWrittenFrames  = 0;
	handyString = new char[128];
	outputFileName = "output.mov";
	
	//---------------------------
	// Load settings file
	if( XML.loadFile("UnwarperSettings.xml") ){
		printf("UnwarperSettings.xml loaded!\n");
	} else{
		printf("Unable to load UnwarperSettings.xml!\nPlease check 'data' folder.\n");
	}
	
	maxR_factor   = XML.getValue("MAXR_FACTOR", 0.96);
	minR_factor   = XML.getValue("MINR_FACTOR", 0.16);
	angularOffset = XML.getValue("ROTATION_DEGREES", 0.0);
	
	
//	int loadedQuality  = XML.getValue("CODEC_QUALITY", 3);
//	loadedQuality = MIN(5, MAX(0, loadedQuality));
//	int codecQualities[] = {
//		OF_QT_SAVER_CODEC_QUALITY_MIN,
//		OF_QT_SAVER_CODEC_QUALITY_LOW,
//		OF_QT_SAVER_CODEC_QUALITY_NORMAL,
//		OF_QT_SAVER_CODEC_QUALITY_HIGH,
//		OF_QT_SAVER_CODEC_QUALITY_MAX,
//		OF_QT_SAVER_CODEC_QUALITY_LOSSLESS
//	};
//	codecQuality = codecQualities[loadedQuality];

	
	player.load(ofToDataPath(XML.getValue("INPUT_FILENAME", "input.mov")));
    player.setUseTexture(true);
	
	unwarpedW = (int) XML.getValue("OUTPUT_W", 1280);
	unwarpedH = (int) XML.getValue("OUTPUT_H", 256);
	
	//if the XML element doesn't exist, create it.
	XML.setValue("OUTPUT_W", (int) unwarpedW);
	XML.setValue("OUTPUT_H", (int) unwarpedH);
	
	
	// Interpolation method:
	// 0 = CV_INTER_NN, 1 = CV_INTER_LINEAR, 2 = CV_INTER_CUBIC.
	interpMethod = (int) XML.getValue("INTERP_METHOD", 1);
	XML.setValue("INTERP_METHOD", (int) interpMethod);
	
	int bSaveAud = (int) XML.getValue("INCLUDE_AUDIO", 0);
	bSaveAudioToo = (bSaveAud != 0);
	
	/*
	 // straight rectilinearization
	 yWarpA = -0.2047;
	 yWarpB =  0.8632;
	 yWarpC =  0.3578;
	 yWarpA = XML.getValue("R_WARP_A", -0.2047);
	 yWarpB = XML.getValue("R_WARP_B",  0.8632);
	 yWarpC = XML.getValue("R_WARP_C",  0.3578);
	 */
	
	yWarpA =   0.1850;
	yWarpB =   0.8184;
	yWarpC =  -0.0028;
	yWarpA = XML.getValue("R_WARP_A",  0.1850);
	yWarpB = XML.getValue("R_WARP_B",  0.8184);
	yWarpC = XML.getValue("R_WARP_C", -0.0028);
	
	
	//======================================
	// create data structures for unwarping
	blackOpenCV = cvScalarAll(0);
	
	// The "warped" original source video produced by the Bloggie.
	warpedW = player.getWidth();
	warpedH = player.getHeight();
	int nWarpedBytes = warpedW * warpedH * 3;
	printf("warpedW = %d, warpedH = %d\n", warpedW, warpedH);
	
	warpedImageOpenCV.allocate(warpedW, warpedH);
	warpedPixels = new unsigned char[nWarpedBytes];
	warpedIplImage = warpedImageOpenCV.getCvImage();
	cvSetImageROI(warpedIplImage, cvRect(0, 0, warpedW, warpedH));
	
	int nUnwarpedPixels = unwarpedW * unwarpedH;
	int nUnwarpedBytes  = unwarpedW * unwarpedH * 3;
	unwarpedImage.allocate(unwarpedW, unwarpedH, OF_IMAGE_COLOR);
	unwarpedPixels = new unsigned char[nUnwarpedBytes];
	unwarpedTexture.allocate(unwarpedW, unwarpedH,GL_RGB);
	
	unwarpedImageOpenCV.allocate(unwarpedW, unwarpedH);
	unwarpedImageOpenCV.setROI(0, 0, unwarpedW, unwarpedH);
	unwarpedIplImage = unwarpedImageOpenCV.getCvImage();
	
	srcxArrayOpenCV.allocate(unwarpedW, unwarpedH);
	srcyArrayOpenCV.allocate(unwarpedW, unwarpedH);
	srcxArrayOpenCV.setROI(0, 0, unwarpedW, unwarpedH);
	srcyArrayOpenCV.setROI(0, 0, unwarpedW, unwarpedH);
	
	xocvdata = (float*) srcxArrayOpenCV.getCvImage()->imageData;
	yocvdata = (float*) srcyArrayOpenCV.getCvImage()->imageData;
	
	playerScaleFactor = (float)(ofGetHeight() - unwarpedH)/(float)warpedH;
	savedWarpedCx = warpedCx = XML.getValue("CENTERX", warpedW / 2.0);
	savedWarpedCy = warpedCy = XML.getValue("CENTERY", warpedH / 2.0);
	savedAngularOffset = angularOffset;
	
	//if the XML element doesn't exist, create it.
	XML.setValue("CENTERX", warpedCx);
	XML.setValue("CENTERY", warpedCy);
	XML.setValue("ROTATION_DEGREES", angularOffset);
	
	
	//---------------------------
	// cylinder vizualization properties
	cylinderRes = 90;
	cylinderWedgeAngle = 360.0 / (cylinderRes-1);
	cylinderX = new float[cylinderRes];
	cylinderY = new float[cylinderRes];
	for (int i = 0; i < cylinderRes; i++) {
		cylinderX[i] = cos(ofDegToRad((float)i * cylinderWedgeAngle));
		cylinderY[i] = sin(ofDegToRad((float)i * cylinderWedgeAngle));
	}
	blurredMouseY = 0;
	blurredMouseX = 0;
	
	/*
	videoRecorder = new ofxQtVideoSaver();
	currentCodecId = 16;
	videoRecorder->setCodecType (currentCodecId);
	videoRecorder->setCodecQualityLevel (codecQuality);
	 */
	


	
	//---------------------------
	// start it up
	computePanoramaProperties();
	computeInversePolarTransform();
	player.play();
	
	
	
}
//--------------------------------------------------------------
void ofApp::update(){
	
	// computePanoramaProperties();    // NOT NECESSARY
	// computeInversePolarTransform(); // NOT NECESSARY
			player.update();
    if (player.isFrameNew() || (bPlayerPaused && !player.isFrameNew())){
        
        if (bCenterChanged || bAngularOffsetChanged){
            XML.setValue("CENTERX", warpedCx);
            XML.setValue("CENTERY", warpedCy);
            XML.setValue("ROTATION_DEGREES", angularOffset);
            
            computePanoramaProperties();
            computeInversePolarTransform();
            
            bAngularOffsetChanged = false;
            bCenterChanged = false;
        }
        
        unsigned char* thePlayerPixels = player.getPixels().getData();
        memcpy(warpedPixels, thePlayerPixels, warpedW*warpedH*3);
        
        int nPlayerPixels = warpedW*warpedH*3;
        for (int i=0; i<nPlayerPixels; i++){
            warpedPixels[i] = (unsigned char) i%255;
        }
        
        warpedIplImage->imageData = (char*) warpedPixels;
        
        cvRemap(warpedIplImage,  unwarpedIplImage,
                srcxArrayOpenCV.getCvImage(),
                srcyArrayOpenCV.getCvImage(),
                interpMethod | CV_WARP_FILL_OUTLIERS, blackOpenCV );
        
        unwarpedPixels = (unsigned char*) unwarpedIplImage->imageData;
        unwarpedImage.setFromPixels(unwarpedPixels, unwarpedW, unwarpedH, OF_IMAGE_COLOR, true);
        unwarpedTexture.loadData(unwarpedPixels, unwarpedW, unwarpedH, GL_RGB);
        unwarpedImage.update();
    }
	if (bSavingOutVideo){


		// do the unwarping into the unwarpedPixels.
		memcpy(warpedPixels, player.getPixels().getData(), warpedW*warpedH*3);
		warpedIplImage->imageData = (char*) warpedPixels;
		cvRemap(warpedIplImage,  unwarpedIplImage,
				srcxArrayOpenCV.getCvImage(),
				srcyArrayOpenCV.getCvImage(),
				interpMethod | CV_WARP_FILL_OUTLIERS, blackOpenCV );
		unwarpedPixels = (unsigned char*) unwarpedIplImage->imageData;
		unwarpedImage.setFromPixels(unwarpedPixels, unwarpedW, unwarpedH, OF_IMAGE_COLOR, true);
		unwarpedTexture.loadData(unwarpedPixels, unwarpedW, unwarpedH, GL_RGB);
		
		
		if (recorder.isRecording()){
            recorder.addFrame(unwarpedImage.getPixels());
			nWrittenFrames++;
		}
		
		
		// assure that we are stepping through one frame at a time.
		float currF = (float) player.getCurrentFrame();
		float nF    = (float) player.getTotalNumFrames();
		float pct = (currF + 1.0)/nF;
		player.setPosition(pct);
		
		// update our state machine, stopping the recording if necessary.
		if (player.getIsMovieDone()){
			player.setLoopState(OF_LOOP_NORMAL);
			
			
			if (recorder.isRecording()){
				

                bSavingOutVideo = false;
                recorder.close();
				printf("Finished exporting movie!\n");
			}
			
			
			bSavingOutVideo = false;
			player.setPaused(false);
			
		}
		
	}
}
//--------------------------------------------------------------
void ofApp::keyPressed  (int key){
	
	/*
	 <!-- // Press Space to toggle movie play.                      -->
	 <!-- // Press 's' to save the geometry settings.               -->
	 <!-- // Press 'r' to reload the previously saved settings.     -->
	 <!-- // Use the +/- keys to change the export codec.           -->
	 <!-- // Press 'v' to export the unwarped video.                -->
	 <!-- // Use the arrow keys to nudge the center point.          -->
	 <!-- // Drag the unwarped video left or right to shift it.     -->
	 */
	
	/*
	int nCodecs = videoRecorder->getNCodecs();
	*/
	
	switch (key){
			
			
		case '0':
		case '1':
		case '2':
			interpMethod = key - '0';
			break;
			
		case 356: // arrow left
			warpedCx -= 0.25;
			bCenterChanged = true;
			break;
		case 358: // arrow right
			warpedCx += 0.25;
			bCenterChanged = true;
			break;
		case 357: // arrow up
			warpedCy -= 0.25;
			bCenterChanged = true;
			break;
		case 359: // arrow down
			warpedCy += 0.25;
			bCenterChanged = true;
			break;
			
		case ' ':
			bPlayerPaused = !bPlayerPaused;
			player.setPaused(bPlayerPaused);
			break;
			
		case 'r':
		case 'R':
			warpedCx = savedWarpedCx;
			warpedCy = savedWarpedCy;
			angularOffset = savedAngularOffset;
			computeInversePolarTransform();
			break;
			
		case 's':
		case 'S':
			XML.setValue("CENTERX", warpedCx);
			XML.setValue("CENTERY", warpedCy);
			XML.setValue("ROTATION_DEGREES", angularOffset);
			XML.saveFile("UnwarperSettings.xml");
			savedWarpedCx      = warpedCx;
			savedWarpedCy      = warpedCy;
			savedAngularOffset = angularOffset;
			printf("Saved settings to UnwarperSettings.xml.");
			break;
		
		/*
		case '-':
		case '_':
			currentCodecId = MAX(0, currentCodecId-1);
			videoRecorder->setCodecType(currentCodecId);
			break;
		case '+':
		case '=':
			currentCodecId = MIN(nCodecs-1, currentCodecId+1);
			videoRecorder->setCodecType(currentCodecId);
			break;
		*/
			
		
		case 'v':
		case 'V':
			if (bSavingOutVideo == false){
				player.setLoopState(OF_LOOP_NONE); //OF_LOOP_NORMAL
				player.setPosition(0.0);
				
				string inName = XML.getValue("INPUT_FILENAME", "input.mov");
				int inNameLastDotIndex = inName.find_last_of('.');
				if (inNameLastDotIndex > 1){
					inName = inName.substr (0, inNameLastDotIndex);
				}
				sprintf(handyString, "%2d%2d%2d.mov", ofGetHours(), ofGetMinutes(), ofGetSeconds());
				inName += "_out_" + ofToString(ofGetHours()) + ofToString(ofGetMinutes()) + ofToString(ofGetSeconds()) + ".mov";
				outputFileName = inName;
				
                recorder.setup(outputFileName, unwarpedW, unwarpedH, 30);
                recorder.start();
				if (recorder.isRecording()){
					bSavingOutVideo = true;
					nWrittenFrames = 0;
				}
			}
			break;
		 
			
	}
	
}
//--------------------------------------------------------------
void threesixtyUnwarp::update(){
	
	// computePanoramaProperties();    // NOT NECESSARY
	// computeInversePolarTransform(); // NOT NECESSARY

	if (bSavingOutVideo == false){
		
		ofSetVerticalSync(false);
		player.update();
		if (player.isFrameNew()  || (bPlayerPaused && !player.isFrameNew())){
				
			if (bCenterChanged || bAngularOffsetChanged){
				XML.setValue("CENTERX", warpedCx);
				XML.setValue("CENTERY", warpedCy);
				XML.setValue("ROTATION_DEGREES", angularOffset); 
				
				computePanoramaProperties();
				computeInversePolarTransform();
				
				bAngularOffsetChanged = false;
				bCenterChanged = false;
			}
			
			memcpy(warpedPixels, player.getPixels(), warpedW*warpedH*3);
			warpedIplImage->imageData = (char*) warpedPixels; 
			
			cvRemap(warpedIplImage,  unwarpedIplImage, 
					srcxArrayOpenCV.getCvImage(), 
					srcyArrayOpenCV.getCvImage(), 
					interpMethod | CV_WARP_FILL_OUTLIERS, blackOpenCV );
			 
			unwarpedPixels = (unsigned char*) unwarpedIplImage->imageData;
			unwarpedImage.setFromPixels(unwarpedPixels, unwarpedW, unwarpedH, OF_IMAGE_COLOR, true);
			unwarpedTexture.loadData(unwarpedPixels, unwarpedW, unwarpedH, GL_RGB);
		}
		
	//---------------------------------------------------
	// Else if we are saving out video..
	} else {
		
		ofSetVerticalSync(true);
		
		
		// do the unwarping into the unwarpedPixels.
		memcpy(warpedPixels, player.getPixels(), warpedW*warpedH*3);
		warpedIplImage->imageData = (char*) warpedPixels; 
		cvRemap(warpedIplImage,  unwarpedIplImage, 
				srcxArrayOpenCV.getCvImage(), 
				srcyArrayOpenCV.getCvImage(), 
				interpMethod | CV_WARP_FILL_OUTLIERS, blackOpenCV );
		unwarpedPixels = (unsigned char*) unwarpedIplImage->imageData;
		unwarpedImage.setFromPixels(unwarpedPixels, unwarpedW, unwarpedH, OF_IMAGE_COLOR, true);
		unwarpedTexture.loadData(unwarpedPixels, unwarpedW, unwarpedH, GL_RGB);
		
		if (videoRecorder->bAmSetupForRecording()){
			videoRecorder->addFrame(unwarpedPixels, (1.0f/30.00f));//29.97003f));
			nWrittenFrames++;
		}
		
		// assure that we are stepping through one frame at a time.
		float currF = (float) player.getCurrentFrame();
		float nF    = (float) player.getTotalNumFrames();
		float pct = (currF + 1.0)/nF;
		player.setPosition(pct);
			
		// update our state machine, stopping the recording if necessary.
		if (player.getIsMovieDone()){
			player.setLoopState(OF_LOOP_NORMAL);
			if (videoRecorder->bAmSetupForRecording()){
				
				if (bSaveAudioToo){
					// Strip audio from the original input movie; add it.
					string audioPath = XML.getValue("INPUT_FILENAME", "input.mov");
					videoRecorder->addAudioTrack(audioPath);
				}
				videoRecorder->finishMovie();
				printf("Finished exporting movie!\n"); 
			}
			bSavingOutVideo = false;
			player.setPaused(false); 
		} 
		
	}
}