/*
 * OpenCV application
 *
 */

#include "stdafx.h"

#include "cxcore.h"    // basic OpenCV data structures
#include "cv.h"        // basic OpenCV algorithms
#include "highgui.h"   // OpenCV's (limited) display and event-handling routines
#include <cstdio>      // epic C-style I/O; feel free to change to iostream...
#include <time.h>      // to compute frames per second

enum CAP_SOURCE { GET_IMAGES_FROM_CAMERA, GET_IMAGES_FROM_FILE, GET_IMAGES_FROM_VIDEO };
using namespace std;   // aargh!

// GLOBALS - there's no avoiding them in OpenCV
#ifdef _WIN32
const char* IMAGES_DIRECTORY          = "..\\setImages_v1\\";
#else
const char* IMAGES_DIRECTORY          = "../../setImages_v1/";
#endif
const char* RAW_IMAGE_NAME            = "Raw Image";
const char* DRAW_IMAGE_NAME           = "Drawing Image";
const char* BINARY_IMAGE_NAME         = "Binary Image";
const char* COLOR_TRACKBAR_WINDOW     = "Color Thresholds"; 
char        BUFFER[1024];             // general-purpose string buffer for filenames, printing, etc.

// IMAGES
IplImage*   source_frame              = NULL; 
IplImage*   raw_image                 = NULL;    
IplImage*   draw_image                = NULL;    
IplImage*   binary_image              = NULL;  

// STATE
int  IMAGE_SOURCE                     = GET_IMAGES_FROM_CAMERA; // GET_IMAGES_FROM_FILE, GET_IMAGES_FROM_VIDEO
int  rectangle_selection              = 0;         // 0 == no drawing, 1 == need 1st pt, 2 == need 2nd pt.
int  rectangle_ul[2], rectangle_lr[2];             // the opposing-corner selection pts (need not be ul/lr)

// THRESHOLDS
int  RedLow = 100; int  GreenLow = 100; int  BlueLow = 100;
int RedHigh = 200; int GreenHigh = 200; int BlueHigh = 200;

// NETWORKING-SPECIFIC DATA
enum SERVER_MESSAGES { SERVER_RUN, SERVER_QUIT };
int server_control             =      SERVER_RUN;  // global value available to control the server's state

// Helper Functions
#include "pixel_access.h"             // get_rgb_pixel, set_rgb_pixel, get_gs_pixel, set_gs_pixel
#include "image_handling.h"           // ensure_image_size
#include "file_handling.h"            // check_for_file
#ifdef _WIN32                         // are we on Windows?
#include "socket_handling_win.h"      // start_vision_server    (for windows)
#else
#include "socket_handling_mac.h"      // start_vision_server    (for Mac OS X -- maybe Linux, but haven't tested it)
#endif

// onMouse is the function for handling mouse events
//
void onMouse(int event, int x, int y, int flags, void* param)     // handle mouse events
{
    if (!rectangle_selection && event == CV_EVENT_LBUTTONDOWN)    // print single-pixel data
    {
        int r,g,b,gs;
        get_rgb_pixel (raw_image, x, y, r, g, b);                 // get the pixel from raw_inage
        get_gs_pixel (binary_image, x, y, gs);                    // get the pixel from binary_inage
        printf ("pixel at (%d,%d) is (%3d,%3d,%3d) and in binary is %3d.\n", x, y, r, g, b, gs);
    }
    if (rectangle_selection && event == CV_EVENT_LBUTTONDOWN)     // starts and ends a rectangle_selection
    {
        if (rectangle_selection == 1) { rectangle_ul[0] = x; rectangle_ul[1] = y; rectangle_selection=2; }
        else if (rectangle_selection == 2) { rectangle_lr[0] = x; rectangle_lr[1] = y; rectangle_selection=1; }
    }
    if (rectangle_selection == 2 && event == CV_EVENT_MOUSEMOVE)  // move a rectangle_selection around
    {
        rectangle_lr[0] = x; rectangle_lr[1] = y;
    }
    return;
}

// main sets everything up and runs a "grab-process-show" loop
//
int main (int argc, char * const argv[]) 
{
    // start the vision server - comment this out for no networking...
    server_thread();
    
    // set up all of the windows and position them
    cvNamedWindow (RAW_IMAGE_NAME, CV_WINDOW_AUTOSIZE);         cvMoveWindow (RAW_IMAGE_NAME,20,20);
    cvNamedWindow (DRAW_IMAGE_NAME, CV_WINDOW_AUTOSIZE);        cvMoveWindow (DRAW_IMAGE_NAME,700,20);
    cvNamedWindow (BINARY_IMAGE_NAME, CV_WINDOW_AUTOSIZE);      cvMoveWindow (BINARY_IMAGE_NAME,20,500);
    cvNamedWindow (COLOR_TRACKBAR_WINDOW, CV_WINDOW_AUTOSIZE);  cvMoveWindow (COLOR_TRACKBAR_WINDOW,700,500);
    
    // trackbars are great!
    cvCreateTrackbar ( "RedLow", COLOR_TRACKBAR_WINDOW, &RedLow, 255, NULL /* callback for motion */ );
    cvCreateTrackbar ( "RedHigh", COLOR_TRACKBAR_WINDOW, &RedHigh, 255, NULL /* callback for motion */ );
    cvCreateTrackbar ( "GreenLow", COLOR_TRACKBAR_WINDOW, &GreenLow, 255, NULL /* callback for motion */ );
    cvCreateTrackbar ( "GreenHigh", COLOR_TRACKBAR_WINDOW, &GreenHigh, 255, NULL /* callback for motion */ );
    cvCreateTrackbar ( "BlueLow", COLOR_TRACKBAR_WINDOW, &BlueLow, 255, NULL /* callback for motion */ );
    cvCreateTrackbar ( "BlueHigh", COLOR_TRACKBAR_WINDOW, &BlueHigh, 255, NULL /* callback for motion */ );
    
    int close_iterations = 1;        // number of times to dilate before eroding for the morphological closing
    cvCreateTrackbar ( "Iters", COLOR_TRACKBAR_WINDOW, &close_iterations, 10, NULL /* callback for motion */ );
    
    cvSetMouseCallback (RAW_IMAGE_NAME, onMouse, NULL);          // mouse events handled from the raw_image window
    
    CvCapture* source = cvCreateCameraCapture (CV_CAP_ANY);      // check if there is a camera available
    printf ("You %s have a camera.\n", source ? "do" : "don't"); // report the results to the console...
    
    CvFont font_structure;                                       // how OpenCV holds its fonts
    cvInitFont ( &font_structure, CV_FONT_HERSHEY_PLAIN, 1.0 /* hscale */, 1.0 /* vscale */ );
    
    clock_t last_clock_ticks = clock();                          // previous ticks since program started
    clock_t clock_ticks = last_clock_ticks + 1;                  // current ticks since program started
    double fps = 1.0;                                            // frames per second
    
    int image_index_to_view = 0;                                 // image index to grab from IMAGES_DIRECTORY 
    
    // the "grab-process-show" loop: until 'q' or ESC is pressed ...
    //
    while (true)
    {
        clock_ticks = clock();                                         // get the number of clock ticks
        fps = CLOCKS_PER_SEC/double(clock_ticks - last_clock_ticks);   // compute fps
        last_clock_ticks = clock_ticks;                                // reset the old value
        
        if (source && IMAGE_SOURCE == GET_IMAGES_FROM_CAMERA)          // grab source_frame from CAMERA
        {
            source_frame = cvQueryFrame (source);                      // grab camera frame
            ensure_image_size (raw_image, source_frame, 3 );           // make sure raw_image is the same size
            cvFlip (source_frame, raw_image, 1);                       // need to flip from camera to raw_image
        }
        else                                                           // grab source_frame from FILE
        {
            strcpy (BUFFER, IMAGES_DIRECTORY);                         // print directory name to BUFFER
            sprintf (BUFFER+strlen(BUFFER),                            // then append ...
                     "image%05d.bmp", image_index_to_view);            // ... the filename
            
            if (file_is_present (BUFFER))                              // from file_handling.h
                source_frame = cvLoadImage( BUFFER );                  // grab the file if it's present
            else if (source_frame != NULL) 
                cvZero (source_frame);                                 // no file? zero out old source_frame
            else
                source_frame = cvCreateImage( cvSize(100,100), IPL_DEPTH_8U, 3);  // nothing at all? default value.
            
            ensure_image_size (raw_image, source_frame, 3 );           // make sure raw_image is the same size
            cvCopy ( source_frame, raw_image );                        // copy into raw_image
        }
        
        ensure_image_size (draw_image,   raw_image, 3 /* rgb */ );     // make sure draw_image is the right size
        ensure_image_size (binary_image, raw_image, 1 /* gs */ );      // make sure binary_image is the right size
        
        // we will start draw_image as a copy of raw_image
        cvCopy (raw_image, draw_image);
        
        // go through and change some pixels to all red - just to show how to access individual pixels...
        for (int row=draw_image->height-25 ; row < draw_image->height ; ++row ) {
            for (int col=draw_image->width-125 ; col < draw_image->width ; ++col ) {
                set_rgb_pixel (draw_image, col, row, 255 /* red */, 0 /* green */, 0 /* blue */);
            }
        }
        
        // write text on top of draw_image
        sprintf (BUFFER, "%4.1f", fps);                // print the frames per second on the image
        cvPutText (draw_image, BUFFER, cvPoint(draw_image->width-100,draw_image->height-10), &font_structure, CV_RGB(255,255,0));
        
        // draw shapes on top of draw_image
        if (rectangle_selection == 2)                  // draw the selection rectangle as it's being defined
        {
            int ulx = rectangle_ul[0], uly = rectangle_ul[1], lrx = rectangle_lr[0], lry = rectangle_lr[1];
            printf ("Rectangle: %d, %d, %d, %d\n", ulx, uly, lrx, lry);
            cvRectangle (draw_image, cvPoint(ulx,uly), cvPoint(lrx,lry), CV_RGB(255,255,0), 2 /* thickness */ );
        }
        
        // create a binary_image
        CvScalar lowerb = cvScalar (RedLow, GreenLow, BlueLow);       // lower-bound thresholds
        CvScalar upperb = cvScalar (RedHigh, GreenHigh, BlueHigh);    // upper-bound thresholds
        cvInRangeS (raw_image, lowerb, upperb, binary_image);         // this thresholds between them
        
        // erode and dilate can be done in-place (src and dst are both binary_image)
        cvDilate (binary_image, binary_image, NULL /* default 3x3 square mask */, close_iterations /* iterations */);
        cvErode  (binary_image, binary_image, NULL /* default 3x3 square mask */, close_iterations /* iterations */);
        
        // show images
        cvShowImage (RAW_IMAGE_NAME, raw_image);
        cvShowImage (DRAW_IMAGE_NAME, draw_image);
        cvShowImage (BINARY_IMAGE_NAME, binary_image);
        
        // handle key press events - cvWaitKey is necessary to handle ALL events, as well!
        int key = cvWaitKey (10);    // 10 milliseconds of waiting, I think
        //
        if (key == 'q' || key == 'Q' || key == 27)    // quit the application, key 27 is ESC
        {
            break;
        }
        if (key == 'f' || key == 'F' )                // save the image to file
        {
            strcpy (BUFFER, IMAGES_DIRECTORY);        // directory
            strcat (BUFFER, "img.bmp");               // plus filename
            cvSaveImage (BUFFER, raw_image);          // saves it
            printf ("Image saved in %s\n", BUFFER);
        }
        if (key == 's' || key == 'S' )                // switch the image source
        {
            IMAGE_SOURCE = (GET_IMAGES_FROM_CAMERA == IMAGE_SOURCE) ? GET_IMAGES_FROM_FILE : GET_IMAGES_FROM_CAMERA;
            printf("Switching to IMAGE_SOURCE %d.\n", IMAGE_SOURCE);
        }
        if (key == 'c' || key == 'C' )                // print our color thresholds
        {
            printf ("Thresholds:    Red:  %3d -> %3d\n", RedLow, RedHigh);
            printf ("             Green:  %3d -> %3d\n", GreenLow, GreenHigh);
            printf ("              Blue:  %3d -> %3d\n", BlueLow, BlueHigh);
        }
        if (key == ' ' || key == 'B')                 // cycle forward (spacebar) or backward 'B' through the images
        {
            image_index_to_view += key == ' ' ? 1 : -1;             // add or subtract 1
            if (image_index_to_view < 0) image_index_to_view = 0;   // can't go less than 0
        }
        if (key == 'R')                               // turn on or off rectangle selection
        {
            if (rectangle_selection) { rectangle_selection = 0; /* turn off rectangle selection */ }
            else { rectangle_selection = 1; /* looking for first corner */ }
            printf("Rectangle drawing is %d.\n", rectangle_selection );
        }
        if (key == '-')
        {
            server_control = SERVER_QUIT;
        }
        
        
    }  // end of the "grab-process-show" while loop
    
    // return nicely from main...
    return 0;
}
