#include "cvCardAnalysis.h"



// sorry this is ghetto thrown in : 

#define MIN3(x,y,z)  ((y) <= (z) ? \
((x) <= (y) ? (x) : (y)) \
: \
((x) <= (z) ? (x) : (z)))

#define MAX3(x,y,z)  ((y) >= (z) ? \
((x) >= (y) ? (x) : (y)) \
: \
((x) >= (z) ? (x) : (z)))


struct rgb_color {
    unsigned char r, g, b;    /* Channel intensities between 0 and 255 */
};

struct hsv_color {
    unsigned char hue;        /* Hue degree between 0 and 255 */
    unsigned char sat;        /* Saturation between 0 (gray) and 255 */
    unsigned char val;        /* Value between 0 (black) and 255 */
};


struct hsv_color rgb_to_hsv(struct rgb_color rgb) {
    struct hsv_color hsv;
    unsigned char rgb_min, rgb_max;
    rgb_min = MIN3(rgb.r, rgb.g, rgb.b);
    rgb_max = MAX3(rgb.r, rgb.g, rgb.b);
	
    hsv.val = rgb_max;
    if (hsv.val == 0) {
        hsv.hue = hsv.sat = 0;
        return hsv;
    }
	
    hsv.sat = 255*(rgb_max - rgb_min)/hsv.val;
    if (hsv.sat == 0) {
        hsv.hue = 0;
        return hsv;
    }
	
    /* Compute hue */
    if (rgb_max == rgb.r) {
        hsv.hue = 0 + 43*(rgb.g - rgb.b)/(rgb_max - rgb_min);
    } else if (rgb_max == rgb.g) {
        hsv.hue = 85 + 43*(rgb.b - rgb.r)/(rgb_max - rgb_min);
    } else /* rgb_max == rgb.b */ {
        hsv.hue = 171 + 43*(rgb.r - rgb.g)/(rgb_max - rgb_min);
    }
	
    return hsv;
}

//' end ghetto....



//-----------------------------
void cvCardAnalysis::setup(int w, int h){

	cardWarpW = 64;
	cardWarpH = 80;

	cardImg.allocate(cardWarpW, cardWarpH);
	thresholdedFrame.allocate(cardWarpW, cardWarpH);
	cardImgColor.allocate(cardWarpW, cardWarpH);
	thresholdedFrameDilated.allocate(cardWarpW, cardWarpH);
	threshPixels		= new unsigned char[cardWarpW * cardWarpH * 1];
	cornerPixels		= new unsigned char[cardWarpW * cardWarpH * 1];
	cornerMaskedImage.allocate(cardWarpW, cardWarpH);
	cornerImage.allocate(cardWarpW, cardWarpH);
	cannyImage.allocate(cardWarpW, cardWarpH);
	
	cardImgColorHighRes.allocate(CARD_CV_HIGHRES_W, CARD_CV_HIGHRES_H);
	cornerMaskedImageHighRes.allocate(CARD_CV_HIGHRES_W, CARD_CV_HIGHRES_H);
	cardImgGrayscaleHighRes.allocate(CARD_CV_HIGHRES_W, CARD_CV_HIGHRES_H);
	cardImgGrayscaleHighResBlobs.allocate(CARD_CV_HIGHRES_W, CARD_CV_HIGHRES_H);
	cardImgColorHighResBlobs.allocate(CARD_CV_HIGHRES_W, CARD_CV_HIGHRES_H);	
	cardImgColorHighResResult.allocate(CARD_CV_HIGHRES_W, CARD_CV_HIGHRES_H);
	
	cornerAsColorTest	= new unsigned char[cardWarpW * cardWarpH * 3];
	cornerAsColorTex.allocate(cardWarpW, cardWarpH, GL_RGB);
	
	// TODO: MAKE THIS MUCH BETTER, more accurate, etc
	
	ofRectangle topCorner,bottomCorner;

	topCorner.x				= 1;
	topCorner.y				= 1;
	topCorner.width			= 8;
	topCorner.height		= 20;

	bottomCorner.x			= CARD_CV_W - 2 - 6;
	bottomCorner.y			= CARD_CV_H - 2 - 20;
	bottomCorner.width		= 6;
	bottomCorner.height		= 20;
	
	for (int i = 0; i < cardWarpW; i++){
		for (int j = 0; j < cardWarpH; j++){
			
			bool bCloseEnough = false;
			
			if (i >= topCorner.x && i <= (topCorner.x+topCorner.width)){
				if (j >= topCorner.y && j <= (topCorner.y+topCorner.height)){
					bCloseEnough = true;
				}
			}
			
			
			if (i >= bottomCorner.x && i <= (bottomCorner.x+bottomCorner.width)){
				if (j >= bottomCorner.y && j <= (bottomCorner.y+bottomCorner.height)){
					bCloseEnough = true;
				}
			}
			
			if (bCloseEnough){
				cornerPixels[j * cardWarpW + i] = 255;
			} else {
				cornerPixels[j * cardWarpW + i] = 0;
			}
		}
	}
	
	cornerMaskedImage.setFromPixels(cornerPixels, cardWarpW, cardWarpH);
	cornerMaskedImageHighRes.scaleIntoMe(cornerMaskedImage, CV_INTER_CUBIC);
	
}

//------------------------------------------
void cvCardAnalysis::getCardAsCvImage(card &theCard, ofxCvGrayscaleImage &videoFrame){
		
		int sx = theCard.boundingRect.x;
		int sy = theCard.boundingRect.y;
		int sw = theCard.boundingRect.width;
		int sh = theCard.boundingRect.height;
			
		ofxPoint2f tmpQuad[4];
		ofxPoint2f tmpDst[4];
				
		tmpDst[0].x = 0;
		tmpDst[0].y = 0;
		tmpDst[1].x = cardWarpW;
		tmpDst[1].y = 0;
		tmpDst[2].x = cardWarpW;
		tmpDst[2].y = cardWarpH;
		tmpDst[3].x = 0;
		tmpDst[3].y = cardWarpH;
		
		int pta = theCard.pta;
		int ptb = theCard.ptb;
		int ptc = theCard.ptc;
		int ptd = theCard.ptd;

		tmpQuad[0].x = theCard.c[pta].x;
		tmpQuad[0].y = theCard.c[pta].y;
		tmpQuad[1].x = theCard.c[ptb].x;
		tmpQuad[1].y = theCard.c[ptb].y; 										
		tmpQuad[2].x = theCard.c[ptc].x;
		tmpQuad[2].y = theCard.c[ptc].y;
		tmpQuad[3].x = theCard.c[ptd].x;
		tmpQuad[3].y = theCard.c[ptd].y; 
		
		cardImg.warpIntoMe(videoFrame, tmpQuad, tmpDst);
						
}


//------------------------------------------
void cvCardAnalysis::getCardAsCvImage(card &theCard, ofxCvGrayscaleImage &videoFrame, ofxCvColorImage &videoFrameColor){
	
	int sx = theCard.boundingRect.x;
	int sy = theCard.boundingRect.y;
	int sw = theCard.boundingRect.width;
	int sh = theCard.boundingRect.height;
	
	ofxPoint2f tmpQuad[4];
	ofxPoint2f tmpDst[4];
	
	tmpDst[0].x = 0;
	tmpDst[0].y = 0;
	tmpDst[1].x = cardWarpW;
	tmpDst[1].y = 0;
	tmpDst[2].x = cardWarpW;
	tmpDst[2].y = cardWarpH;
	tmpDst[3].x = 0;
	tmpDst[3].y = cardWarpH;
	
	int pta = theCard.pta;
	int ptb = theCard.ptb;
	int ptc = theCard.ptc;
	int ptd = theCard.ptd;
	
	tmpQuad[0].x = theCard.c[pta].x;
	tmpQuad[0].y = theCard.c[pta].y;
	tmpQuad[1].x = theCard.c[ptb].x;
	tmpQuad[1].y = theCard.c[ptb].y; 										
	tmpQuad[2].x = theCard.c[ptc].x;
	tmpQuad[2].y = theCard.c[ptc].y;
	tmpQuad[3].x = theCard.c[ptd].x;
	tmpQuad[3].y = theCard.c[ptd].y; 
	
	cardImg.warpIntoMe(videoFrame, tmpQuad, tmpDst);
	cardImgColor.warpIntoMe(videoFrameColor, tmpQuad, tmpDst);  // trouble getting this to compile?
																// change ofxCvImage warp into me, to make color work, like: 
																// void ofxCvImage::warpIntoMe( const ofxCvImage& mom, ofPoint src[4], ofPoint dst[4] )
	
	// this is gonna be slow ??
	// don't do every frame ??
	
	tmpDst[0].x = 0;
	tmpDst[0].y = 0;
	tmpDst[1].x = CARD_CV_HIGHRES_W;
	tmpDst[1].y = 0;
	tmpDst[2].x = CARD_CV_HIGHRES_W;
	tmpDst[2].y = CARD_CV_HIGHRES_H;
	tmpDst[3].x = 0;
	tmpDst[3].y = CARD_CV_HIGHRES_H;
	
	cardImgColorHighRes.warpIntoMe(videoFrameColor, tmpQuad, tmpDst);
	cardImgGrayscaleHighRes = cardImgColorHighRes;
	
	cardImgGrayscaleHighRes.invert();
	cardImgGrayscaleHighRes -= cornerMaskedImageHighRes;
	cardImgGrayscaleHighRes.threshold (127); // kind of ARBITRARY // TODO: fix this
}


//------------------------------------------
void cvCardAnalysis::dynamicThreshold(int threshold){
	
	unsigned char * pixels = cardImg.getPixels();
	int totalPixels = cardWarpW * cardWarpH;
	
	int darkest = 255;
	int brightest = 0;
	
	//first find the brightest and darkest pixels
	unsigned char pix;		
	for(int i = 0; i < totalPixels; i++){
		pix = pixels[i];
		if(pix > brightest) brightest = pix;
		if(pix < darkest) darkest = pix;
	}	
	
	//then do the threshold
	float pct = 0.0;
	float range = (float)(brightest - darkest);
	
	if(range > 0){
		for(int i = 0; i < totalPixels; i++){
			pct = 255.0  * ( (float)(pixels[i] - darkest)/range );
			
			if(pct > threshold) threshPixels[i] = 0;
			else threshPixels[i] = 255;									
		}
	}else{
		for(int i = 0; i < totalPixels; i++)threshPixels[i] = 0;
	}
	
	
	/*thresholdedFrame = cardImg; //
	 thresholdedFrame.contrastStretch();
	 thresholdedFrame.threshold(127);
	 */
	thresholdedFrame.setFromPixels(threshPixels, cardWarpW, cardWarpH);
	
	//thresholdedFrame.invert();
}

void cvCardAnalysis::copyCardDataIntoCardImage(cardImageSet & cardToUpdate){
	
	// assume card images are initialized.
	// otherwise we are in trouble
	cardToUpdate.cardImage					.setFromPixels(cardImg.getPixels(), CARD_CV_W, CARD_CV_H, OF_IMAGE_GRAYSCALE);
	cardImg.mirror(true, true);
	cardToUpdate.cardImageFlipped			.setFromPixels(cardImg.getPixels(), CARD_CV_W, CARD_CV_H, OF_IMAGE_GRAYSCALE);
	cardImg.mirror(true, true); // back to normal
	
	cardToUpdate.thresholdedImage			.setFromPixels(thresholdedFrame.getPixels(), CARD_CV_W, CARD_CV_H, OF_IMAGE_GRAYSCALE);
	thresholdedFrame.mirror(true, true);
	cardToUpdate.thresholdedImageFlipped	.setFromPixels(thresholdedFrame.getPixels(), CARD_CV_W, CARD_CV_H, OF_IMAGE_GRAYSCALE);
	thresholdedFrame.mirror(true, true);
	
	cardToUpdate.colorImageHighRes			.setFromPixels(cardImgColorHighRes.getPixels(), CARD_CV_HIGHRES_W, CARD_CV_HIGHRES_H, OF_IMAGE_COLOR);
	
	
	//cardImgGrayscaleHighRes
	contourFinder.findContours(cardImgGrayscaleHighRes, 10, (CARD_CV_HIGHRES_W*CARD_CV_HIGHRES_H)/2, 10, false);
	int nfound = contourFinder.nBlobs;


	cardToUpdate.nPlanes = 0;
	
	for (int i = 0; i < nfound; i++){
		
		if (i > 0){
			float areaPct = contourFinder.blobs[i].area > contourFinder.blobs[i-1].area ?   (contourFinder.blobs[i-1].area / contourFinder.blobs[i  ].area) : (contourFinder.blobs[i  ].area / contourFinder.blobs[i-1].area);
			if (areaPct < 0.5f) break;
		}
		
		
		cardImgColorHighResResult = cardImgColorHighRes;
		
		cardImgGrayscaleHighResBlobs.set(0);
		cardImgGrayscaleHighResBlobs.drawBlobIntoMe(contourFinder.blobs[i], 255);
		cardImgGrayscaleHighResBlobs.dilate_3x3();
		cardImgGrayscaleHighResBlobs.dilate_3x3();
		cardImgGrayscaleHighResBlobs.blur(3);
		cardImgGrayscaleHighResBlobs.invert();
		cardImgColorHighResBlobs = cardImgGrayscaleHighResBlobs;
		
		cardImgColorHighResResult += cardImgColorHighResBlobs;
		
		cardToUpdate.colorImagesPlanes[cardToUpdate.nPlanes].setFromPixels(cardImgColorHighResResult.getPixels(), CARD_CV_HIGHRES_W, CARD_CV_HIGHRES_H, OF_IMAGE_COLOR);
																		   
		cardToUpdate.boundingRectanges[cardToUpdate.nPlanes] = contourFinder.blobs[i].boundingRect;
		cardToUpdate.nPlanes++;
	}
												
	
	
	
	
}

//------------------------------------------
void cvCardAnalysis::calculateHuMoments(card &theCard, bool bUseThresholdedFrame){
	
	if( bUseThresholdedFrame ){
		cvMoments(thresholdedFrame.getCvImage(), &theCard.Moments);
	}else{
		cvMoments(cardImg.getCvImage(), &theCard.Moments);	
	}
	cvGetHuMoments(&theCard.Moments, &theCard.HuMoments);
	
}


//------------------------------------------
void cvCardAnalysis::calculateCardProperties(card &theCard){
	
	// ---------------------------------------------- pct pixel white, not so good
	// pct pixels, not really useful 
	int totalPixels = 64*80;
	float whitePix = (float) thresholdedFrame.countNonZeroInRegion(0,0,64,80);
	float pct = whitePix / (64.0f * 80.0f);
	theCard.pctWhitePixels = pct;
	
	
	// ---------------------------------------------- pct of white edge pixels
	// very good, but when the image is blurred, this messes up 
	cvCanny(thresholdedFrame.getCvImage(), cannyImage.getCvImage(), 0.5, 0.5, 3); 
	int nWhitePixels = cannyImage.countNonZeroInRegion(0,0,cardWarpW, cardWarpH);
	theCard.pctEdgeDetectionWhite = (float)(nWhitePixels) / (float)(cardWarpW*cardWarpH);
	if (theCard.pctEdgeDetectionWhite > 0.13f){  // seems to be > 13% for edgeDetection...
		theCard.bFaceCard = true;
	} else {
		theCard.bFaceCard = false;
	}
	
	// (alternative) -------------------------------- biggest conntour
	// because of the blurry cards, we are not using this alternative. 
	
	// biggest contour of the thresholded image, **much** better
	// acutally, diamonds looks really different, etc.
	// also, this biggest blob is useful, as it would be suit on non face cards
	contourFinder.findContours(thresholdedFrame, 0, totalPixels, 1, false);
	if (contourFinder.nBlobs == 1){
		
		float boundindBoxArea = contourFinder.blobs[0].boundingRect.width * contourFinder.blobs[0].boundingRect.height;
		float sizeMe =  boundindBoxArea / (float)totalPixels;
		// seems like about 10% is good...
		// printf("---------> sizeme : %f \n", sizeMe, contourFinder.blobs[0].boundingRect);
		if (sizeMe > 0.10){
			theCard.bFaceCard = true;
		} else {
			theCard.bFaceCard = false;
			// do something cool here: 
			
			/*vector <ofxVec2f> pts;
			float xOffset = contourFinder.blobs[0].boundingRect.x;
			float yOffset = contourFinder.blobs[0].boundingRect.y;
			for (int i = 0; i < contourFinder.blobs[0].nPts; i++){
				pts.push_back(ofxVec2f(contourFinder.blobs[0].pts[i].x -xOffset,contourFinder.blobs[0].pts[i].y - yOffset));
			}
			CA.resampleContour(pts, 256);
			for (int i = 0; i < 256; i++){
				theCard.suitContour[i].set(pts[i].x, pts[i].y);
			}*/
			
			// get em into a sub image?
			// and do recognition off this?
			
		}
	} else {
		// TODO: something this card is WACK
	}
	
	
	if (theCard.bFaceCard == false){
		cornerImage = thresholdedFrame;// -= cornerMaskedImage;
		cornerImage -= cornerMaskedImage;
		
		cornerImage.erode_3x3(); // needed for blurry images ugh.

		float minSize = 1; //0.0 * totalPixels;
		contourFinder.findContours(cornerImage, 0, totalPixels/2, 10, false);
		int nfound = contourFinder.nBlobs;
		
		int nReallyGood = nfound;
		for (int i = 0; i < nfound; i++){
			if (i > 0){
				float areaPct = contourFinder.blobs[i].area > contourFinder.blobs[i-1].area ?   (contourFinder.blobs[i-1].area / contourFinder.blobs[i  ].area) :
																								(contourFinder.blobs[i  ].area / contourFinder.blobs[i-1].area);
				// this is too extreme.  maybe something smaller?  
				// the back have troubles... 
				/*if (areaPct < 0.9f){
					//printf("--------------> skipping a blob \n");
					nReallyGood = i;
					break;
					// get out of there...
				}*/
			}
			
			// GREAT 
		}
		
		//printf("-------------- found %i blobs \n", nReallyGood);
		theCard.nonFaceCardBlobCount = nReallyGood;
		
	}
	
	
	cornerImage = thresholdedFrame;// -= cornerMaskedImage;
	if (theCard.bFaceCard == true) cornerImage *= cornerMaskedImage;
	
	// let's average all the color pixels that are dark, like	
	float r, g, b;
	r = 0;
	g = 0;
	b = 0;
	
	memset(cornerAsColorTest, 0, cardWarpW * cardWarpH * 3);
	
	//cornerImage.erode_3x3();
	int countPixGood = 0;
	unsigned char * grayPixels		= cornerImage.getPixels();
	unsigned char * colorPixels		= cardImgColor.getPixels();
	int darkestRed = 255;
	for (int i = 0; i < 64*80; i++){
		if (grayPixels[i] > 0){
			r += colorPixels[i*3];
			g += colorPixels[i*3+1];
			b += colorPixels[i*3+2];
			
			cornerAsColorTest[i*3] = colorPixels[i*3];
			cornerAsColorTest[i*3+1] = colorPixels[i*3+1];
			cornerAsColorTest[i*3+2] = colorPixels[i*3+2];
			
			countPixGood ++;
		}
	}
	
	//cornerAsColorTest
	cornerAsColorTex.loadData(cornerImage.getPixels(), cardWarpW, cardWarpH, GL_LUMINANCE);

	if (countPixGood > 0){
		r /= (float)countPixGood;
		g /=  (float)countPixGood;
		b /=  (float)countPixGood;
	}
	 
		struct rgb_color rgb;
    	struct hsv_color hsv;
    	rgb.r = r;
		rgb.g = g;
    	rgb.b = b;
    	hsv = rgb_to_hsv(rgb);
	
	printf ("hue %i sat %i val %i \n", hsv.hue, hsv.sat, hsv.val);
	
	// assume false, use hue + others for true: 
	theCard.bRedSuit = false;
	if (hsv.hue > 230 || hsv.hue < 20){
		if (hsv.sat > 50){
			// please parametize this
			theCard.bRedSuit = true;
		}
	} 
	
	
	theCard.avgColor.x = hsv.hue;
	theCard.avgColor.y = hsv.sat;
	theCard.avgColor.z = hsv.val;
	
	
	
	//theCard.bRedSuit = theCard.avgColor.x > 45;   // mad ghetto - this should be based on hsv??
	// http://en.literateprograms.org/Special:Downloadcode/RGB_to_HSV_color_space_conversion_%28C%29
	// TODO: rgb to hsv...
	
	// just to see, messes up detection...	
	//thresholdedFrame = cornerImage;
	
	//	just to see the edge detection...
	//theCard.myCardImageThresholded.setFromPixels(thresholdedFrame.getPixels(), 64, 80, OF_IMAGE_GRAYSCALE);
	//thresholdedFrame.mirror(true, true);
	//theCard.myCardImageThresholdedFlipped.setFromPixels(thresholdedFrame.getPixels(), 64, 80, OF_IMAGE_GRAYSCALE);
	
	
}


/*
 0 : hue 81 sat 70 val 36 
 1 : hue 89 sat 79 val 32 
 2 : hue 92 sat 95 val 32 
 3 : hue 89 sat 76 val 30 
 4 : hue 89 sat 91 val 28 
 5 : hue 92 sat 105 val 29 
 6 : hue 78 sat 76 val 20 
 7 : hue 85 sat 102 val 30 
 8 : hue 80 sat 85 val 24 
 9 : hue 85 sat 88 val 23 
 
 10 : hue 83 sat 87 val 61 
 11 : hue 99 sat 99 val 59 
 12 : hue 93 sat 75 val 54 
 
 
 
 13 : hue 9 sat 78 val 75 
 14 : hue 3 sat 74 val 82 
 15 : hue 3 sat 75 val 84 
 16 : hue 249 sat 102 val 95 
 17 : hue 249 sat 100 val 84 
 18 : hue 248 sat 103 val 86 
 19 : hue 247 sat 106 val 89 
 20 : hue 243 sat 108 val 87 
 21 : hue 244 sat 113 val 92 
 22 : hue 242 sat 126 val 89 
 23 : hue 31 sat 74 val 65 
 24 : hue 8 sat 62 val 65 
 25 : hue 249 sat 51 val 60
 
 
 26 : hue 83 sat 97 val 47 
 27 : hue 82 sat 115 val 31 
 28 : hue 88 sat 119 val 30 
 29 : hue 85 sat 114 val 29 
 30 : hue 83 sat 131 val 29 
 31 : hue 85 sat 136 val 30 
 32 : hue 82 sat 137 val 26 
 33 : hue 81 sat 159 val 32 
 34 : hue 78 sat 172 val 34 
 35 : hue 79 sat 137 val 26 
 36 : hue 70 sat 92 val 47 
 37 : hue 77 sat 92 val 55 
 38 : hue 78 sat 121 val 59 
 
 39 : hue 245 sat 93 val 93 
 40 : hue 243 sat 94 val 94 
 41 : hue 240 sat 106 val 91 
 42 : hue 240 sat 119 val 94 
 43 : hue 242 sat 103 val 96 
 44 : hue 246 sat 100 val 91 
 45 : hue 250 sat 78 val 91 
 46 : hue 4 sat 65 val 82 
 47 : hue 17 sat 69 val 62 
 48 : hue 17 sat 64 val 79 
 49 : hue 69 sat 97 val 63 
 50 : hue 37 sat 72 val 56 
 51 : hue 45 sat 62 val 65 
 
 
 */







