#include "OSCriticalSection.h"
#include "OSMisc.h"
#include "OSTimer.h"
#include "CvUtils.h"
#ifdef _WIN32
#include "resource.h"
#endif // _WIN32

#define MAX_BUF_LEN 256

CRITICAL_SECTION imageCS;
CvVideoWriter* videofile = NULL;
IplImage* image = NULL;

// Parameters.
int camid = 0;
int videoimgwidth = 0; 
int videoimgheight = 0; 
int pixcolorchgthreshold = 0; 
int captureperiod = 0;
int capturetimeout = 0;
int pixchgratiothreshold = 0; 
int pixstep = 0; 
int imgstep = 0; 

int LoadConfig()
{
	FILE* file = NULL;
	char line[MAX_BUF_LEN];

	// Default values.
	camid = 0;
	videoimgwidth = 640; 
	videoimgheight = 480; 
	pixcolorchgthreshold = 75; // In [0,255].
	captureperiod = 100; // In ms. Modifying this value might involve modifying imgstep.
	capturetimeout = 30; // In s.
	pixchgratiothreshold = 1024; 
	pixstep = 1; // >1.
	imgstep = 1; // >1.

	file = fopen("WebcamMonitoring.txt", "r");
	if (file != NULL)
	{
		if (fgets3(file, line, sizeof(line)) == NULL) printf("Invalid configuration file.\n");
		if (sscanf(line, "%d", &camid) != 1) printf("Invalid configuration file.\n");
		if (fgets3(file, line, sizeof(line)) == NULL) printf("Invalid configuration file.\n");
		if (sscanf(line, "%d", &videoimgwidth) != 1) printf("Invalid configuration file.\n");
		if (fgets3(file, line, sizeof(line)) == NULL) printf("Invalid configuration file.\n");
		if (sscanf(line, "%d", &videoimgheight) != 1) printf("Invalid configuration file.\n");
		if (fgets3(file, line, sizeof(line)) == NULL) printf("Invalid configuration file.\n");
		if (sscanf(line, "%d", &pixcolorchgthreshold) != 1) printf("Invalid configuration file.\n");
		if (fgets3(file, line, sizeof(line)) == NULL) printf("Invalid configuration file.\n");
		if (sscanf(line, "%d", &captureperiod) != 1) printf("Invalid configuration file.\n");
		if (fgets3(file, line, sizeof(line)) == NULL) printf("Invalid configuration file.\n");
		if (sscanf(line, "%d", &capturetimeout) != 1) printf("Invalid configuration file.\n");
		if (fgets3(file, line, sizeof(line)) == NULL) printf("Invalid configuration file.\n");
		if (sscanf(line, "%d", &pixchgratiothreshold) != 1) printf("Invalid configuration file.\n");
		if (fgets3(file, line, sizeof(line)) == NULL) printf("Invalid configuration file.\n");
		if (sscanf(line, "%d", &pixstep) != 1) printf("Invalid configuration file.\n");
		if (fgets3(file, line, sizeof(line)) == NULL) printf("Invalid configuration file.\n");
		if (sscanf(line, "%d", &imgstep) != 1) printf("Invalid configuration file.\n");

		if (fclose(file) != EXIT_SUCCESS) printf("fclose() failed.\n");
	}
	else
	{
		printf("Configuration file not found.\n");
	}

	return EXIT_SUCCESS;
}

int MovementDetection(IplImage* previmg, IplImage* img, IplImage* detectimg)
{
	int r = 0, g = 0, b = 0;
	int i = 0, j = 0, indexr = 0, indexg = 0, indexb = 0;
	unsigned int count = 0; 
	unsigned char* prevdata = (unsigned char*)previmg->imageData;
	unsigned char* data = (unsigned char*)img->imageData;
	unsigned char* detectdata = (unsigned char*)detectimg->imageData;

	for (i = 0; i < img->height; i += pixstep)
	{
		for (j = 0; j < img->width; j += pixstep)
		{
			indexb = 3*(j+img->width*i);
			indexg = indexb+1;
			indexr = indexb+2;

			b = data[indexb]-prevdata[indexb];
			g = data[indexg]-prevdata[indexg];
			r = data[indexr]-prevdata[indexr];
			if ((abs(r) > pixcolorchgthreshold)||
				(abs(g) > pixcolorchgthreshold)||
				(abs(b) > pixcolorchgthreshold)) 
			{
				count++; 

				detectdata[indexb] = 0;
				detectdata[indexg] = 255;
				detectdata[indexr] = 0;
			}
		}
	}	

	if (count > (unsigned int)img->width*(unsigned int)img->height/
		((unsigned int)pixchgratiothreshold*(unsigned int)pixstep*(unsigned int)pixstep)) 
		return TRUE; 
	else 
		return FALSE;
}

TIMERCALLBACK_RETURN_VALUE TimerCallbackFunction(void* pParam, BOOLEAN b)
{
	UNREFERENCED_PARAMETER(pParam);
	UNREFERENCED_PARAMETER(b);

	EnterCriticalSection(&imageCS);
	if (!cvWriteFrame(videofile, image)) 
	{
		printf("Error writing to a video file.\n");
	}
	LeaveCriticalSection(&imageCS);
}

#if defined(_WIN32) && !defined(_DEBUG)
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow)
{
	int argc = __argc;
	char** argv = __argv;
#else
int main(int argc, char* argv[])
{
#endif // defined(_WIN32) && !defined(_DEBUG)
	int c = 0, i = 0, detectionskipimgcnt = 0;
	BOOL bCapture = FALSE, bMovement = FALSE, bError = FALSE;
	char videofilename[MAX_BUF_LEN];
	IplImage* previmage = NULL;
	IplImage* detectimage = NULL;
	CvCapture* webcam = NULL;
	CvFont font;
	TIMER timer;
	CHRONO chrono;

	INIT_DEBUG;

	UNREFERENCED_PARAMETER(argc);
	UNREFERENCED_PARAMETER(argv);

	LoadConfig();

	webcam = cvCreateCameraCapture(camid);
	if (!webcam) 
	{
		printf("Error opening the webcam.\n");
		return EXIT_FAILURE;
	}

	cvSetCaptureProperty(webcam, CV_CAP_PROP_FRAME_WIDTH, videoimgwidth);
	cvSetCaptureProperty(webcam, CV_CAP_PROP_FRAME_HEIGHT, videoimgheight);

	// Sometimes the first images are bad on some webcams, so wait a little bit and take
	// several images in the beginning.
	i = 0;
	while (i < 4)
	{
		mSleep(500);
		image = cvQueryFrame(webcam);
		if (!image)
		{
			printf("Error getting an image from the webcam.\n");
			cvReleaseCapture(&webcam);
			return EXIT_FAILURE;
		}
		i++;
	}

	previmage = cvCloneImage(image);
	if (!previmage)	
	{
		printf("Error copying an image.\n");
		cvReleaseCapture(&webcam);
		return EXIT_FAILURE;
	}
	detectimage = cvCloneImage(image);
	if (!detectimage)	
	{
		printf("Error copying an image.\n");
		cvReleaseImage(&previmage);
		cvReleaseCapture(&webcam);
		return EXIT_FAILURE;
	}

	if (InitCriticalSection(&imageCS) != EXIT_SUCCESS)	
	{
		printf("Error initializing a critical section object.\n");
		cvReleaseImage(&detectimage);
		cvReleaseImage(&previmage);
		cvReleaseCapture(&webcam);
		return EXIT_FAILURE;
	}

	cvNamedWindow("Webcam Monitoring", CV_WINDOW_AUTOSIZE);
	cvMoveWindow("Webcam Monitoring", 0, 0);
	cvShowImage("Webcam Monitoring", detectimage);

#ifdef _WIN32
	// Change the icon of the OpenCV window.
	if (SetClassLongPtr(FindWindow(NULL, "Webcam Monitoring"), 
		GCLP_HICON, 
		(LONG_PTR)LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_ICON1))
		) == 0)
	{
		PRINT_DEBUG_ERROR(("SetClassLongPtr() failed : %s\n", GetLastErrorMsg()));
	}
#endif // _WIN32

	cvInitFont(&font, CV_FONT_HERSHEY_SIMPLEX, 1.0, 1.0, 1.0, 4, 8);

	for (;;)
	{
		EnterCriticalSection(&imageCS);
		image = cvQueryFrame(webcam);
		LeaveCriticalSection(&imageCS);
		if (!image)
		{
			printf("Error getting an image from the webcam.\n");
			bError = TRUE;
			break;
		}
		cvCopy(image, detectimage, NULL);

		c = cvWaitKey(captureperiod);
		if ((char)c == 27) break;

		detectionskipimgcnt++;
		if (detectionskipimgcnt%imgstep == 0)
		{
			detectionskipimgcnt = 0;
			bMovement = MovementDetection(previmage, image, detectimage);
			cvCopy(image, previmage, NULL);
		}

		if (!bCapture && bMovement) 
		{
			sprintf(videofilename, "vid_%s.wmv", strtime_fns());
			videofile = cvCreateVideoWriter(videofilename, 
				CV_FOURCC('W','M','V','2'), 
				1000.0/(double)captureperiod, 
				cvSize((int)cvGetCaptureProperty(webcam, CV_CAP_PROP_FRAME_WIDTH),
				(int)cvGetCaptureProperty(webcam, CV_CAP_PROP_FRAME_HEIGHT)), 
				1);
			if (!videofile)
			{
				printf("Error creating a video file.\n");
				bError = TRUE;
				break;
			}
			if (CreateTimer(&timer, TimerCallbackFunction, NULL, 1, captureperiod) != EXIT_SUCCESS)
			{
				printf("Error creating a timer.\n");
				bError = TRUE;
				break;
			}
			bCapture = TRUE;
		}
		if (bMovement) 
		{
			if (StartChrono(&chrono) != EXIT_SUCCESS)
			{
				printf("Error starting capture chronometer\n");
				bError = TRUE;
				break;
			}
			cvPutText(detectimage, "Movement", cvPoint(0,64), &font, CV_RGB(0,255,0));
		} 
		if (bCapture) 
		{
			cvPutText(detectimage, "Capturing", cvPoint(0,32), &font, CV_RGB(255,0,0));
			if ((GetTimeElapsedChronoQuick(&chrono) > capturetimeout)) 
			{
				DeleteTimer(&timer, FALSE);
				StopChronoQuick(&chrono);
				cvReleaseVideoWriter(&videofile);
				bCapture = FALSE;
			}
		}
		cvShowImage("Webcam Monitoring", detectimage);
	}

	if (bCapture) 
	{
		DeleteTimer(&timer, FALSE);
		StopChronoQuick(&chrono);
		cvReleaseVideoWriter(&videofile);
	}
	cvDestroyWindow("Webcam Monitoring");
	DeleteCriticalSection(&imageCS);
	cvReleaseImage(&detectimage);
	cvReleaseImage(&previmage);
	cvReleaseCapture(&webcam);

	if (bError)
	{
		return EXIT_FAILURE;
	}

	return EXIT_SUCCESS;
}
