#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);
	
	
	
	cornerAsColorTest	= new unsigned char[cardWarpW * cardWarpH * 3];
	cornerAsColorTex.allocate(cardWarpW, cardWarpH, GL_RGB);
	
	// TODO: MAKE THIS MUCH BETTER, more accurate, etc
	
	ofRectangle topCorner;
	topCorner.x			= 1;
	topCorner.y			= 1;
	topCorner.width		= 8;
	topCorner.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 (h >= topCorner.y && j <= (topCorner.y+topCorner.height)){
					bCloseEnough = true;
				}
			}
			
			if (bCloseEnough){
				cornerPixels[j * cardWarpW + i] = 255;
			} else {
				cornerPixels[j * cardWarpW + i] = 0;
			}
		}
	}
	
	cornerMaskedImage.setFromPixels(cornerPixels, cardWarpW, cardWarpH);
	
}

//------------------------------------------
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] )
	
	
}


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

//------------------------------------------
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 pixels, not really useful 
	float whitePix = (float) thresholdedFrame.countNonZeroInRegion(0,0,64,80);
	float pct = whitePix / (64.0f * 80.0f);
	theCard.pctWhitePixels = pct;
	
	// 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
	
	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;
	}
	
	if (theCard.bFaceCard == false){
		cornerImage = thresholdedFrame;// -= cornerMaskedImage;
		cornerImage -= cornerMaskedImage;
		int totalPixels = 64*80;
		float minSize = 1; //0.0 * totalPixels;
		contourFinder.findContours(cornerImage, 0, totalPixels/2, 10, false);
		int nfound = contourFinder.nBlobs;
		for (int i = 0; i < nfound; i++){
			//printf("blob %i -- area pct = %f \n", i, contourFinder.blobs[i].area / (float)totalPixels);
		}
		//printf("found %i blobs \n", nfound);
	}
	
	
	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;
	}
	 
	
	theCard.avgColor.x = r;
	theCard.avgColor.y = g;
	theCard.avgColor.z = b;
	
	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);
	
	if (hsv.hue > 210 || hsv.hue < 40){
		theCard.bRedSuit = true;
	} else {
		theCard.bRedSuit = false;
	}
	//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);
	
	
}







