/*
 * set your OS!
 *
 * in order to include the correct files...
 */

//#define WINDOWS_OS
#define MAC_OS

/*
 * include files for Windows
 */
#ifdef WINDOWS_OS

#include <stdlib.h>
#include <stdio.h>
#include <iostream>
#include <fstream>
#include <string>
#include <tchar.h>
#include <math.h>
#include <time.h>
#include <cv.h>
#include <cxcore.h>
#include <highgui.h>
#include "BlobResult.h"

#define _NO_MORE_WINSOCK_
#include "SmallSocket.h"
#undef _NO_MORE_WINSOCK_

#define ROOT_FOLDER "C:\\Documents and Settings\\me\\Desktop\\"
#define M_PI 3.1415926535

#endif /* WINDOWS_OS */

/*
 * include files for Mac OS X
 */
#ifdef MAC_OS

#include <OpenCV/OpenCV.h>
#include <stdio.h>
#include <ctype.h>
#include <math.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>       // for memset
#include <unistd.h>
#include <sys/types.h> 
#include <sys/socket.h>
#include <netinet/in.h>
#include <BlobResult.h>

#include <fstream>
#include <iostream>
using namespace std;

//#define ROOT_FOLDER "/Users/bfsnet/Desktop/SetWithOpenCV/"
//#define ROOT_FOLDER "/Users/dodds/Desktop/SetWithOpenCV/"
#define ROOT_FOLDER "/Users/jarthur/Desktop/SetWithOpenCV/"


#endif

#include "mainHelper.h"

/* 
 * global data for passing among 
 *   the main loop (in main)
 *   the mouse-event handler (in mouseEventHandler)
 *   the server (in runServerThread)
 */
IplImage *inputImage=0;  // shared between mouse events and main loop
int swapVerticalValues = 0;  // shared between mouse events and main loop

int drawingRectangle=0;  // shared between mouse events and main loop
CvPoint origin;          // more rectangle-selection data
CvRect selection;

int runAlgorithm = 0;    // shared between server and main loop
int timeToQuitNow = 0;   // shared between server and main loop

ColorDefinition currentColor;  // shared among mouse events, server, and main loop

double averageTargetX = 0;  // the x-coordinate of the robot's target

#include "serverStuff.h"


/*
 * Mouse-event handling routine
 */
void mouseEventHandler( int event, int x, int y, int flags, void* param )
{
  int r,g,b;
  double h,s,v;
	
	if ( !inputImage )  return;  // no image, do nothing
  if (swapVerticalValues)  y = inputImage->height - y;   // platform-dependent!
  
  if ( event == CV_EVENT_MOUSEMOVE && drawingRectangle ) // are we pulling the corner of a rectangle?
  {
    // handle the geometry of the rectangle:
    selection.x = MIN(x,origin.x);  selection.y = MIN(y,origin.y);
    selection.width = MIN( selection.x + CV_IABS(x - origin.x), inputImage->width ) - MAX( selection.x, 0 );
    selection.height = MIN( selection.y + CV_IABS(y - origin.y), inputImage->height ) - MAX( selection.y, 0 );
  } // end of rectangle-drawing
    
  if ( event == CV_EVENT_RBUTTONDOWN ) // did we right-click (or control-click?)
  {
    getrgb( inputImage, x, y, r, g, b );
    RGBtoHSV( r, g, b, h, s, v );
    printf("      right-click at (%d,%d): rgb=(%d,%d,%d)\n", x, y, r, g, b ) ;
    printf("         hsv=(%6.3f,%6.3f,%6.3f)\n", h, s, v );
    int N = 10;
    currentColor.expandToFit( r+N, g+N, b+N ); // box of side 2N: upper corner
    currentColor.expandToFit( r-N, g-N, b-N ); // and lower corner
    currentColor.print();
  }
      
  if ( event == CV_EVENT_LBUTTONDOWN ) // did we left-click?
  {
    if (!drawingRectangle) // if we weren't drawing, now we start drawing a rectangle
    {
      origin = cvPoint(x,y);
      selection = cvRect(x,y,1,1);
      drawingRectangle = 1;
    }
    else // if we were drawing a rectangle, now we're finished drawing it
    {
      for ( int col=selection.x ; col<=selection.x+selection.width ; ++col)
      {
        for ( int row=selection.y ; row<=selection.y+selection.height ; ++row) 
        {
          getrgb( inputImage, col, row, r, g, b );
          currentColor.expandToFit( r, g, b );
        }
      }
      currentColor.print();
      drawingRectangle = 0;
    }
  }
} // end of mouse event handler



void setify( IplImage* inputImage, IplImage* outputImage )
{
  IplImage* binaryImage = cvCreateImage( cvGetSize(inputImage), 8, 1 );
  double avred = 0.0, avgre = 0.0, avblu = 0.0;
  CBlobResult blobs;
  CBlob blobToShow;
  int numpix = 0;
  /*
   * OUR ALGORITHM/PROCESSING/VISUALIZATION
   */
  if (true)
  {
    int b,r,g;
    // loop over the input image
    for ( int col=0 ; col<inputImage->width ; ++col) 
    {
      for ( int row=0 ; row<inputImage->height ; ++row) 
      {
        getrgb( inputImage, col, row, r, g, b );
        if ( currentColor.pixelMatch( r, g, b ) ) 
        {
          avred += r; avgre += g; avblu += b; numpix++;
          setrgb( outputImage, col, row, 0, 255, 0 );
        }
      }
    } // end of for x=0 ...
    avred /= numpix; avgre/= numpix; avblu /= numpix;
    //printf(" argb: %f %f %f  ", avred, avgre, avblu );
    
    char* colorOfSetCard;
    if (avblu > 15.0) colorOfSetCard = " blue ";
    else if (avred > 17.0) colorOfSetCard = " red ";
    else if (avgre > 20.0) colorOfSetCard = " green ";
    else  colorOfSetCard = " blue ";
    
    // split the input images
    cvInRangeS( inputImage, cvScalar( currentColor.bmin, currentColor.gmin, currentColor.rmin ),  // min side
               cvScalar( currentColor.bmax, currentColor.gmax, currentColor.rmax ),  // max side
               binaryImage );   
    
    // OK, now we blobify!
    blobs = CBlobResult( binaryImage, NULL, 100, true ); // source, mask, threshold, find moments?
    //blobs.PrintBlobs( "./blobs.txt" );
    //printf("Got %d blobs.\n", blobs.GetNumBlobs() );
    
    blobs.Filter( blobs, B_INCLUDE, CBlobGetMean(), B_GREATER, 200 );
    blobs.Filter( blobs, B_INCLUDE, CBlobGetArea(), B_GREATER, 100 );
    blobs.Filter( blobs, B_EXCLUDE, CBlobGetDiffY(), B_GREATER, inputImage->height * 0.8 );
    
    //nthblob = (nthblob + blobs.GetNumBlobs())%blobs.GetNumBlobs();
    //printf("Showing blob #%d.\n", nthblob);
    //blobs.GetNthBlob( CBlobGetHullArea(), nthblob , blobToShow );
    //blobToShow.FillBlob( outputImage, CV_RGB( 255, 255, 0 ));
    
    /*
     * count distinct symbols and get their centers
     */
    double SAME_CENTER_THRESHHOLD = 30.0;  // within 30 pixels => same shape center
    double shapeXCenters[3] = { -100.0, -100.0, -100.0 }; // to check if this has already been handled...
    double shapeYCenters[3] = { -100.0, -100.0, -100.0 }; // to check if this has already been handled...
    int numSymbols = 0; // counter for the number of symbols
    bool foundSameCenter = false;
    CBlobGetHullArea getHullArea = CBlobGetHullArea();
    double averageHullArea = 0.0;
    for (numSymbols=0 ; numSymbols<MIN(3,blobs.GetNumBlobs()) ; ++numSymbols)
    {
      blobs.GetNthBlob( CBlobGetHullArea(), numSymbols , blobToShow );
      // should get the maxx-minx over two - better than this average
      double thisCenter = blobToShow.sumx; // sumx is the average x
      for (int k=0 ; k<numSymbols ; ++k)
      {
        if ( fabs(thisCenter-shapeXCenters[k]) < SAME_CENTER_THRESHHOLD )
          foundSameCenter = true;
      }
      if (foundSameCenter) break; // no more symbols!
      // otherwise, this is a new symbol...
      blobToShow.FillBlob( outputImage, numSymbols == 0 ? CV_RGB(255, 255, 0 ) :
                          numSymbols == 1 ? CV_RGB(0, 255, 255 ) :
                          CV_RGB(255, 0, 255 ));
      averageHullArea += getHullArea(blobToShow); 
      shapeXCenters[numSymbols] = thisCenter;
      shapeYCenters[numSymbols] = blobToShow.sumy;
    }
    if (numSymbols) averageHullArea /= numSymbols;
    
    /*
     * texture -- look around the centers...
     */
    int halfSide = 10;
    avred = avgre = avblu = 0.0; numpix = 0;
    for (int symbolIndex = 0 ; symbolIndex < numSymbols ; ++symbolIndex )
    {
      // draw a rectangle in white for now
      CvPoint UL = cvPoint( shapeXCenters[symbolIndex] - halfSide , shapeYCenters[symbolIndex] - halfSide );
      CvPoint LR = cvPoint( shapeXCenters[symbolIndex] + halfSide , shapeYCenters[symbolIndex] + halfSide );
      cvRectangle( outputImage, UL, LR, CV_RGB(255,255,255), 1, 8, 0 );
      /*
       * go through that rectangle within the inputImage and get average intensity...
       */
      for ( int col=UL.x ; col<LR.x ; ++col) 
      {
        for ( int row=UL.y ; row<LR.y ; ++row) 
        {
          getrgb( inputImage, col, row, r, g, b );
          avred += r; avgre += g; avblu += b; numpix++;
        }
      }
    }
    avred /= numpix; avgre /= numpix; avblu /= numpix;
    //printf( "%f %f %f  ", avred, avgre, avblu );
    
    /*
     * output card characteristics
     */
    printf("I see  %d %s", numSymbols, colorOfSetCard);
    
    if (avblu < 30.0) printf(" solid ");
    else if (avblu < 75.0) printf(" striped ");
    else printf(" empty ");
    
    if (averageHullArea > 8000.0) printf(" ellipse");
    else if (averageHullArea > 6500.0) printf(" squiggle");
    else  printf(" diamond");
    
    if (numSymbols > 1) printf("s");
    printf("\n");
    
  }
  cvReleaseImage( &binaryImage );   
  return;
}

/*
 * MAIN
 */
int main( int argc, char** argv )
{	
  /*
   * set up vision server
   */
  int runServer = true;     
  currentColor.loadFromFile( ROOT_FOLDER "colordef.txt" ); // get the colors!
  runAlgorithm = true;

#ifdef MAC_OS               
  pthread_t serverThread;   // the identifier for the thread
  if (runServer)            // do we want to run the server?
  {
    printf("Creating serverThread.\n");
    int rc = pthread_create(&serverThread, NULL, runServerThread, NULL);
    if (rc) printf("ERROR in pthread_create: %d.\n", rc);
  }
#endif
#ifdef WINDOWS_OS
  DWORD dwThreadId, dwThrdParam = 1; // nice windows names...
  HANDLE hThread; 
  if (runServer)
  {
	printf("Creating serverThread.\n");
    hThread = CreateThread(          // the thread for our windows server
        NULL,                        // default security attributes 
        0,                           // use default stack size  
        ThreadFunc,                  // thread function 
        &dwThrdParam,                // argument to thread function 
        0,                           // use default creation flags 
        &dwThreadId);                // returns the thread identifier 
	if (!hThread) printf("ERROR in CreateThread.\n"); // if hThread is NULL
  }
#endif
  
  /*
   * set up image input source
   */
  enum { IMAGES_FROM_CAMERA, IMAGES_FROM_FOLDER, IMAGES_FROM_AVI_FILE };
  int captureType = IMAGES_FROM_CAMERA; 
  
  char* MOVIE_NAME = ROOT_FOLDER "path0.mpg"; 
  char* FOLDER_NAME = ROOT_FOLDER "SetImages"; 
  int MAX_FILE_NUMBER = 81;  // the number of images in the folder
  int nextFileNumber = 0;    // the next file number to read 
  
  enum { LIVE, ONEFRAME, STILL };  // how are we now capturing
  int captureStatus = LIVE;
  
  CvCapture* capture = 0;  // the capture object to grab frames from a camera/avi file

  if ( captureType == IMAGES_FROM_CAMERA )   // initialize "capture," the image source
    capture = cvCaptureFromCAM( 0 );         // camera number zero, which is usually the one attached
  else if ( captureType == IMAGES_FROM_AVI_FILE )
    capture = cvCaptureFromAVI( MOVIE_NAME ); 
  else // captureType = IMAGES_FROM_FOLDER;
    capture = 0;
  
  /*
   * create windows for displaying images
   */
  cvNamedWindow( "InputImage", CV_WINDOW_AUTOSIZE );
  cvMoveWindow( "InputImage", 100, 100 );
	cvSetMouseCallback( "InputImage", mouseEventHandler, 0 );
  cvNamedWindow( "OutputImage", CV_WINDOW_AUTOSIZE );
  cvMoveWindow( "OutputImage", 100, 370 );
  //cvNamedWindow( "BinaryImage", CV_WINDOW_AUTOSIZE ); // have them do this!
  //cvMoveWindow( "BinaryImage", 440, 100 );
  
  /*
   * create an initial image, displayImage, and any other images...
   */
  inputImage = cvCreateImage( cvSize(10,10), 8, 1 );
  IplImage* outputImage = NULL;
  IplImage* frame = NULL;
  IplImage* binaryImage = NULL;
  
  /*
   * blob handling
   */
  CBlobResult blobs; 
  CBlob blobToShow;
  int nthblob = 0;
  int imageAreaInPixels = 100;
  
  /*
   * text handling
   */
  CvFont ourFont;
  cvInitFont( &ourFont, CV_FONT_HERSHEY_SIMPLEX, 0.5, 0.5, 0.0, 2, CV_AA );
  
  /*
   * windows and images for set
   */
  int numSetImages = 9;
  IplImage** setInputImages = new IplImage*[numSetImages];
  IplImage** setOutputImages = new IplImage*[numSetImages];
  char* wName[] = { "s0", "s1", "s2", "s3", "s4", "s5", "s6", "s7", "s8" }; // only 9 !
  bool setStarted = false;
  for (int w=0 ; w<numSetImages ; ++w) {
    setInputImages[w] = NULL;
    setOutputImages[w] = NULL;
  }
  
  /*
   * MAIN LOOP
   */
  while (!timeToQuitNow)
  {
    /*
     * INPUT IMAGE SETUP
     *
     * the goal here is to populate the _image_ variable with pixels
     *   depending on the source in captureType
     */
    if (captureStatus != STILL) 
    {
      if (captureType == IMAGES_FROM_FOLDER)
      {
        char filename[255];
        
        if (nextFileNumber > MAX_FILE_NUMBER || nextFileNumber < 0) nextFileNumber = 0;
        sprintf(filename,"%s/image%05d.bmp", FOLDER_NAME, nextFileNumber++);
        inputImage = cvLoadImage(filename, -1); // -1 means determine number of channels from format
      }
      else // captureType == IMAGES_FROM_AVI_FILE or IMAGES_FROM_CAMERA
      {
        frame = cvQueryFrame( capture );  
        if ( !frame  )
        {
          printf( "Frame not obtained when capturing!\n");
          printf( "Setting captureStatus to STILL.\n");
          captureStatus = STILL;
        }
        else  // set up the destination image, if needed
        {
          CvSize frameSize = cvGetSize(frame);
          if ( frameSize.width != inputImage->width || frameSize.height != inputImage->height )
          {
            cvReleaseImage( &inputImage );
            inputImage = cvCreateImage( frameSize, 8, 3 );
            inputImage->origin = frame->origin;
          } // image is OK and the right size, now
          cvCopy( frame, inputImage, 0 );  // copy the frame data into the image
        }
      }
      
      if (captureStatus == ONEFRAME)  captureStatus = STILL; // we got our one frame...
    } 
    
    /*
     * OUTPUT IMAGE SETUP
     *
     * we need to make sure to set up the output images before processing!
     * (or other intermediate images)  we check to be sure they're the right size...
     */
    if ( outputImage == NULL || 
         ( inputImage->width != outputImage->width  || inputImage->height != outputImage->height ) ) 
    { 
      imageAreaInPixels = inputImage->width * inputImage->height; // set this variable...
      // resizes the other images
      if (outputImage) cvReleaseImage( &outputImage );   outputImage = cvCreateImage( cvGetSize(inputImage), 8, 3 );
      if (binaryImage) cvReleaseImage( &binaryImage );   binaryImage = cvCreateImage( cvGetSize(inputImage), 8, 1 ); 
    }
    
    cvCopy(inputImage, outputImage, NULL);  // copy input to output image as a start
    
     
    //double avred = 0.0, avgre = 0.0, avblu = 0.0;
    //int numpix = 0;
    /*
     * OUR ALGORITHM/PROCESSING/VISUALIZATION
     */

    if (runAlgorithm)
    {
      int b,r,g;
      // loop over the input image
      for ( int col=0 ; col<inputImage->width ; ++col) 
      {
        for ( int row=0 ; row<inputImage->height ; ++row) 
        {
          getrgb( inputImage, col, row, r, g, b );
          if ( currentColor.pixelMatch( r, g, b ) ) 
          {
            //avred += r; avgre += g; avblu += b; numpix++;
            setrgb( outputImage, col, row, 0, 255, 0 );
          }
        }
      } // end of for x=0 ...
      //avred /= numpix; avgre/= numpix; avblu /= numpix;
      //printf(" argb: %f %f %f  ", avred, avgre, avblu );
      
      // split the input images
      cvInRangeS( inputImage, cvScalar( currentColor.bmin, currentColor.gmin, currentColor.rmin ),  // min side
                              cvScalar( currentColor.bmax, currentColor.gmax, currentColor.rmax ),  // max side
                  binaryImage );   
      
      // OK, now we blobify!
      blobs = CBlobResult( binaryImage, NULL, 100, true ); // source, mask, threshold, find moments?
      //printf("Got %d blobs.\n", blobs.GetNumBlobs() );
      
      blobs.Filter( blobs, B_INCLUDE, CBlobGetMean(), B_GREATER, 200 );
      blobs.Filter( blobs, B_INCLUDE, CBlobGetArea(), B_GREATER, 100 );
      
      //nthblob = (nthblob + blobs.GetNumBlobs())%blobs.GetNumBlobs();
      //printf("Showing blob #%d.\n", nthblob);
      blobs.GetNthBlob( CBlobGetArea(), 0 , blobToShow ); // biggest blob
      blobToShow.FillBlob( outputImage, CV_RGB( 255, 255, 0 ));
      averageTargetX = blobToShow.sumx;
      printf("Average x is %f.\n", averageTargetX);
      
    } // end of if (runAlgorithm) 
          
    /*
		 * DRAW ON THE OUTPUT IMAGE
		 */
		if (drawingRectangle)  // shows a yellow rectangle when selecting a region
		{
		  cvRectangle(outputImage, cvPoint(selection.x,selection.y), 
                  cvPoint(selection.x+selection.width, selection.y+selection.height),
                  CV_RGB(255,255,0),1,8,0);
		}
    
    cvShowImage( "InputImage", inputImage );    // render the input image
    cvShowImage( "OutputImage", outputImage );  // render the output image
    //cvShowImage( "BinaryImage", binaryImage );
    
    /*
     * HANDLE KEY PRESSES to an OpenCV window
     */
    char cchar = (char) cvWaitKey(10);   // waits for 10 ms to get a keypress
                                         // use cvWaitKey(0) in order to wait forever...
    if ( cchar == 27 )  break;           // break on ESCape key
    
    switch( cchar )                      // handle other key presses
    {        

      case ' ':
      {
        captureStatus = ONEFRAME;
        break;
      }
        
      case 'B':
      {
        nextFileNumber -= 2; 
        captureStatus = ONEFRAME;
        break;
      }
        
      case 'N':
      {
        nthblob++;
        break;
      }
        
      case 'n':
      {
        nthblob--;
        break;
      }
        
      case 'S':
      {
        // play Set!
        // get 9 windows, 9 images, 9 output images
        // call the set-running function
        if (!setStarted)
        {
          for (int w=0 ; w<numSetImages ; ++w)
          {
            cvNamedWindow( wName[w], CV_WINDOW_AUTOSIZE );
            cvMoveWindow( wName[w], 100+320*(w%3), 100+255*(w/3) );
          } 
        }
        //printf("Point 1.\n");
        // get random images and place in windows
        int imIndices[100]; //safe
        int imsChosenSoFar = 0;
        while (imsChosenSoFar < numSetImages)
        {
          imIndices[imsChosenSoFar] = imsChosenSoFar+1;
          // check for match
          imsChosenSoFar++;
        }
        // printf("Point 2.\n");
        char filename[255];
        for (int w=0 ; w<numSetImages ; ++w)
        {
          //if (setInputImages[w]) cvReleaseImage( &(setInputImages[w]) ); 
          sprintf(filename,"%s/image%05d.bmp", FOLDER_NAME, imIndices[w]);
          setInputImages[w] = cvLoadImage(filename, -1);
          if (setOutputImages[w]) cvReleaseImage( &(setOutputImages[w]) );   
          setOutputImages[w] = cvCreateImage( cvGetSize(inputImage), 8, 3 );
          cvCopy( setInputImages[w], setOutputImages[w] );
          setify(setInputImages[w], setOutputImages[w]);  
          cvShowImage( wName[w], setOutputImages[w] );
        } 
        // printf("Point 3.\n");
        break;
      }
        
      case 'L': // restart live capturing
      {
        captureStatus = LIVE;
        printf("Getting one frame.\n");
        break;
      }
        
      case 'R':
      {
        currentColor.reset();
      }
        
      case 'O': // save params to File
      {
        currentColor.saveToFile( ROOT_FOLDER "colordef.txt" );
        break;
      }
        
      case 'I':  // get params from file
      {
        currentColor.loadFromFile( ROOT_FOLDER "colordef.txt" );
        break;
      }
        
      case 'A': // turn on or off algorithm running
      {
        if (runAlgorithm) runAlgorithm = false;
        else runAlgorithm = true;
        break;
      }
        
      default:
        break;
    } // end of the keypress-handling switch statement
    
  } // end of the main image input - process - output loop
  
  /*
   * If we're here, some event triggered the main loop to quit
   */
  timeToQuitNow = 1;    // set our signal for the serverThread to quit
  
  printf("Main thread quitting...\n");
#ifdef MAC_OS
  sleep(1.0);             // give it a moment...
#endif
#ifdef WINDOWS_OS
  Sleep(1000);
#endif
  
  /*
   * release the OpenCV resources
   */
  cvReleaseCapture(&capture);
  cvDestroyWindow( "InputImage" );
  cvDestroyWindow( "OutputImage" );
  
  return 0;
}




