// CameraDll.cpp : Defines the exported functions for the DLL application.
//

#include "stdafx.h"
#include "CameraDll.h"
#include <process.h>

#include "cv.h"
#include "highgui.h"

//-------------------------------------------------------------------------------

BOOL APIENTRY DllMain(
		HMODULE hModule,
		DWORD  ul_reason_for_call,
		LPVOID lpReserved
	)
{
	switch (ul_reason_for_call)
	{
	case DLL_PROCESS_ATTACH:
		Init();
		break;
	case DLL_THREAD_ATTACH:
	case DLL_THREAD_DETACH:
		break;
	case DLL_PROCESS_DETACH:
		Terminate();
		break;
	}
	DEBUG_OPEN();
	return TRUE;
}

//-------------------------------------------------------------------------------

void CameraProcessFunction(void *param)
{
	CAMERA_STRUCT		*camera = (PCAMERA_STRUCT)param;
    FIREi_STATUS        FireiStatus;
	FIREi_CAMERA_FRAME	CameraFrame;

	char path[128];
	ULONG frame = 0;
	DWORD bsize = 0;

	IplImage * cvRGBframe = NULL;
	IplImage * cvRGBAframe = NULL;

	BYTE * RGBbuffer = NULL;
	BYTE * RGBAbuffer = NULL;
	
	//tady kdyztak videowriter
	while (!camera->bRun)
	{
		Sleep(1);
	}

	srand(GetTickCount());

#if 1
	while (camera->bRun)
	{
		if(camera->hCamera)
		{
			FiCameraOneShotTransmit(camera->hCamera, &camera->StartupInfo, 1);
			if (FIREi_STATUS_SUCCESS == (FireiStatus = FiGetNextCompleteFrame(&CameraFrame, camera->hIsochEngine,INFINITE)))//if (FiGetNextCompleteFrame(&CameraFrame, camera->hIsochEngine, 5000) == FIREi_STATUS_SUCCESS)//INFINITE
			{
				camera->nFrameSize = CameraFrame.uFrameWidth * CameraFrame.uFrameHeight * 4;
				camera->nFrameWidth = CameraFrame.uFrameWidth;
				camera->nFrameHeight = CameraFrame.uFrameHeight;

				bsize = CameraFrame.uFrameWidth * CameraFrame.uFrameHeight * 3;
					
				RGBbuffer = (BYTE*)malloc(bsize);
				RGBAbuffer = (BYTE*)malloc(camera->nFrameSize);

				FiYuv2Rgb(&CameraFrame, RGBbuffer, &bsize);

				cvRGBframe = cvCreateImageHeader(cvSize(CameraFrame.uFrameWidth, CameraFrame.uFrameHeight), IPL_DEPTH_8U, 3);
				cvRGBframe->imageData = (char*)RGBbuffer;

				cvRGBAframe = cvCreateImageHeader(cvSize(CameraFrame.uFrameWidth, CameraFrame.uFrameHeight), IPL_DEPTH_8U, 4);
				cvRGBAframe->imageData = (char*)RGBAbuffer;

				cvConvertImage(cvRGBframe, cvRGBframe, CV_CVTIMG_SWAP_RB);//CV_CVTIMG_FLIP);//vertical flip
				cvCvtColor(cvRGBframe, cvRGBAframe, CV_RGB2RGBA);	

				sprintf_s(path, "C:\\frame-%d-%05d.bmp", camera->nCameraNumber, frame++);
				//cvSaveImage(path, cvRGBframe);
				//FiSaveFrame(&CameraFrame, path, 0);
				//cvWriteFrame(writer,cvframe);

				cvReleaseImageHeader(&cvRGBframe);
				cvReleaseImageHeader(&cvRGBAframe);
				free(RGBbuffer);
				//free(RGBAbuffer);

				// insert into queue
				switch (WaitForSingleObject(camera->hMutex, 1000L))
				{
				case WAIT_OBJECT_0:
					// pridani framu do vektoru
					camera->vecFrames.push_back(RGBAbuffer);
					
					if (camera->vecFrames.size() > CAMERA_BUFFER)
					{
						DEBUG("Delete from buffer");
						free(camera->vecFrames.front());
						camera->vecFrames.pop_front();
						camera->nFirstFrameInVector++;
					}
					if (camera->vecFrames.size() > 0)
					{
						DEBUG("Add to buffer");
						//ukazatel na prvni frame
						camera->pFirstFrame = camera->vecFrames.front();
						//ukazatel na posledni pridany frame
						camera->pLastFrame = camera->vecFrames.back();
					}

					ReleaseMutex(camera->hMutex);
					break;
				case WAIT_TIMEOUT:
					break;
				}
			}
			Sleep(5);
		}		
    }
#else
	while (camera->bRun)
	{
		camera->nFrameSize = camera->nFrameWidth * camera->nFrameHeight * 4;

		RGBAbuffer = (BYTE*)malloc(camera->nFrameSize);
		if (RGBAbuffer)
		{
			UINT line = camera->nFrameWidth*4;
			if (camera->nCameraIdentifier == 0)
			{
				for (ULONG i = 0; i < camera->nFrameHeight; i++)
				{
					BYTE value = (rand() % 255 + 1);
//					RGBAbuffer[i] = (rand() % 255 + 1);
					memset(RGBAbuffer + i*line, value, line);
				}
			}
			else if (camera->nCameraIdentifier == 1)
			{
					BYTE value = (rand() % 255 + 1);
					memset(RGBAbuffer, value, camera->nFrameSize);
			}

			switch (WaitForSingleObject(camera->hMutex, 1000L))
			{
			case WAIT_OBJECT_0:
				camera->vecFrames.push_back(RGBAbuffer);
				if (camera->vecFrames.size() > CAMERA_BUFFER)
				{
					DEBUG("Delete from buffer");
					free(camera->vecFrames.front());
					camera->vecFrames.pop_front();
					camera->nFirstFrameInVector++;
				}
				if (camera->vecFrames.size() > 0)
				{
					DEBUG("Add to buffer");
					//ukazatel na prvni frame
					camera->pFirstFrame = camera->vecFrames.front();
					//ukazatel na posledni pridany frame
					camera->pLastFrame = camera->vecFrames.back();
				}

				ReleaseMutex(camera->hMutex);
				break;
			case WAIT_TIMEOUT:
				break;
			}
		}
		Sleep(30);
	}
#endif

    _endthread();

	return;
}


int GetCameraIndex(int camera)
{
	int result = -1;

	// najdeme pozici kamery, kterou chceme
	for (UCHAR i = 0; i < nCamerasCount; i++)
	{
		if (pCameraArray[i]->nCameraNumber == camera)
		{
			result = i;
			break;
		}
	}

	return result;
}


// This is the constructor of a class that has been exported.
// see CameraDll.h for the class definition
CAMERADLL_API void Init()
{
	nCamerasCount = 0;
	nCamerasFound = 0;

	ZeroMemory(pCameraArray, MAX_CAMERA_NUMBER*sizeof(PCAMERA_STRUCT));
	ZeroMemory(CameraGuidArray, sizeof(CameraGuidArray));

	FiInitialize();
	DEBUG("FiInitialize");
	FiInitializeDisplay();
	DEBUG("FiInitializeDisplay");

	// find new cameras
    nCamerasFound = MAX_CAMERA_NUMBER;
    FireiStatus = FiLocateCameras(CameraGuidArray, FIREi_LOCATE_NEW_CAMERAS, &nCamerasFound );
	if (FireiStatus != FIREi_STATUS_SUCCESS)
	{
		DEBUG("FiLocateCameras failed %s", FiStatusString(FireiStatus));
		return;
	}
	DEBUG("FiLocateCameras found %u cameras", (int)nCamerasFound);
}


CAMERADLL_API void Terminate()
{
	StopAll();

	for (ULONG i = 0; i < nCamerasCount; i++)
	{
		if (pCameraArray[i] != NULL)
		{
			if (pCameraArray[i]->hCamera != NULL)
			{
				FiStopCamera(pCameraArray[i]->hCamera);
				DEBUG("FiStopCamera %d", i);
				FiCloseCameraHandle(pCameraArray[i]->hCamera);
				DEBUG("FiCloseCameraHandle %d", i);
			}
			if (pCameraArray[i]->hMutex != NULL)
			{
				CloseHandle(pCameraArray[i]->hMutex);
				pCameraArray[i]->hMutex = NULL;
			}
			for (deque<BYTE*>::iterator it = pCameraArray[i]->vecFrames.begin();
				it != pCameraArray[i]->vecFrames.end(); it++)
			{
				if ((*it) != NULL)
				{
					delete [] (*it);
					(*it) = NULL;
				}
			}
			pCameraArray[i]->vecFrames.clear();
			delete pCameraArray[i];
			pCameraArray[i] = NULL;
		}
	}

	FiTerminate();
	DEBUG("FiTerminate");
	DEBUG_CLOSE();
}


CAMERADLL_API BOOL AddCamera(const char *fileName, int camera)
{
	if ((fileName != NULL) && (camera > 0) && (nCamerasCount < nCamerasFound) && (nCamerasCount < MAX_CAMERA_NUMBER))
	{
		pCameraArray[nCamerasCount] = new CAMERA_STRUCT;
		if (pCameraArray[nCamerasCount] != NULL)
		{
			pCameraArray[nCamerasCount]->hMutex = CreateMutex(NULL, false, NULL);
			pCameraArray[nCamerasCount]->hCamera = NULL;
			pCameraArray[nCamerasCount]->hIsochEngine = NULL;
			pCameraArray[nCamerasCount]->pFirstFrame = NULL;
			pCameraArray[nCamerasCount]->pLastFrame = NULL;
			pCameraArray[nCamerasCount]->strFileName = string(fileName);
			pCameraArray[nCamerasCount]->nCameraNumber = camera;
			pCameraArray[nCamerasCount]->nCameraIdentifier = nCamerasCount;
			pCameraArray[nCamerasCount]->bRun = false;
			pCameraArray[nCamerasCount]->nFirstFrameInVector = 0;
			pCameraArray[nCamerasCount]->nFrameWidth = 0;
			pCameraArray[nCamerasCount]->nFrameHeight = 0;
			pCameraArray[nCamerasCount]->nFrameSize = 0;

			// init camera - open handle
			FireiStatus = FiOpenCameraHandle(&pCameraArray[nCamerasCount]->hCamera, &CameraGuidArray[nCamerasCount]);
			if (FireiStatus != FIREi_STATUS_SUCCESS)
			{
				DEBUG("FiOpenCameraHandle failed %s", FiStatusString(FireiStatus));
				return FALSE;
			}
			DEBUG("FiOpenCameraHandle success");

			if (pCameraArray[nCamerasCount]->hCamera != NULL)
			{
				// set StartupInfo
				pCameraArray[nCamerasCount]->StartupInfo.Tag           = FIREi_CAMERA_STARTUP_INFO_TAG;
				pCameraArray[nCamerasCount]->StartupInfo.FrameRate     = fps_30;
				pCameraArray[nCamerasCount]->StartupInfo.VideoMode     = Mode_1; //3
		        pCameraArray[nCamerasCount]->StartupInfo.VideoFormat   = Format_0;
				pCameraArray[nCamerasCount]->StartupInfo.TransmitSpeed = S200;//400
		        pCameraArray[nCamerasCount]->StartupInfo.IsochSyCode   = 1;
				pCameraArray[nCamerasCount]->StartupInfo.ChannelNumber = (UCHAR)pCameraArray[nCamerasCount]->nCameraIdentifier;

/*!!!*/			pCameraArray[nCamerasCount]->nFrameWidth = 320;
/*!!!*/			pCameraArray[nCamerasCount]->nFrameHeight = 240;
/*!!!*/			pCameraArray[nCamerasCount]->nFrameSize =
					pCameraArray[nCamerasCount]->nFrameWidth * pCameraArray[nCamerasCount]->nFrameHeight * 4;

				// start camera
				FireiStatus = FiStartCamera(pCameraArray[nCamerasCount]->hCamera, &(pCameraArray[nCamerasCount]->StartupInfo));
				if (FireiStatus == FIREi_STATUS_CAMERA_ALREADY_RUNNING)
				{
					DEBUG("Camera is already started. Stoping the camera and retrying...");
					FiStopCamera(pCameraArray[nCamerasCount]->hCamera);

					// Retry starting the camera
					FireiStatus = FiStartCamera(pCameraArray[nCamerasCount]->hCamera, &pCameraArray[nCamerasCount]->StartupInfo);
					if (FireiStatus != FIREi_STATUS_SUCCESS)
					{
						DEBUG("Failed to start the camera with error %s", FiStatusString(FireiStatus));
					}
				}
				else if (FireiStatus != FIREi_STATUS_SUCCESS)
				{
					DEBUG("FiStartCamera failed %s", FiStatusString(FireiStatus));
					return FALSE;
				}
				DEBUG("FiStartCamera success");

				// init isoch engine
				FireiStatus = FiCreateIsochReceiveEngine(&(pCameraArray[nCamerasCount]->hIsochEngine));
				if (FireiStatus != FIREi_STATUS_SUCCESS)
				{
					DEBUG("FiCreateIsochReceiveEngine failed %s", FiStatusString(FireiStatus));
					return FALSE;
				}
				DEBUG("FiCreateIsochReceiveEngine success");

				// start isoch engine
				FireiStatus = FiStartIsochReceiveEngine(pCameraArray[nCamerasCount]->hIsochEngine,
	                                            &(pCameraArray[nCamerasCount]->StartupInfo), 1);
				if (FireiStatus != FIREi_STATUS_SUCCESS)
				{
					DEBUG("FiStartIsochReceiveEngine failed %s", FiStatusString(FireiStatus));
					return FALSE;
				}
				DEBUG("FiStartIsochReceiveEngine success");

				_beginthread(CameraProcessFunction, 4096, (void*)pCameraArray[nCamerasCount++]);

				return TRUE;
			}
		}
	}

	return FALSE;
}


CAMERADLL_API void StartCamera(int camera)
{
	DEBUG("StartCamera entry");
	int nCamera = GetCameraIndex(camera);

	if (nCamera >= 0)
	{
		DEBUG("StartCamera");
		pCameraArray[nCamera]->bRun = TRUE;
	}
}


CAMERADLL_API void StartAll()
{
	DEBUG("StartAll entry");
	for (UCHAR i = 0; i < nCamerasCount; i++)
	{
		DEBUG("StartAll");
		pCameraArray[i]->bRun = TRUE;
	}
}


CAMERADLL_API void StopCamera(int camera)
{
	DEBUG("StopCamera entry");
	int nCamera = GetCameraIndex(camera);

	if (nCamera >= 0)
	{
		pCameraArray[nCamera]->bRun = FALSE;
	}
}


CAMERADLL_API void StopAll()
{
	DEBUG("StopAll entry");
	for (UCHAR i = 0; i < nCamerasCount; i++)
	{
		pCameraArray[i]->bRun = FALSE;
	}
}


CAMERADLL_API ULONG GetFrameSize(int camera)
{
	int nCamera = GetCameraIndex(camera);

	return (nCamera >= 0) ? pCameraArray[nCamera]->nFrameSize : 0;
}


CAMERADLL_API ULONG GetLastFrameIndex(int camera)
{
	int nCamera = GetCameraIndex(camera);

	return (nCamera >= 0) ? pCameraArray[nCamera]->nFirstFrameInVector + pCameraArray[nCamera]->vecFrames.size() : 0;
}


CAMERADLL_API UINT GetFrameWidth(int camera)
{
	int nCamera = GetCameraIndex(camera);

	return (nCamera >= 0) ? pCameraArray[nCamera]->nFrameWidth : 0;
}


CAMERADLL_API UINT GetFrameHeight(int camera)
{
	int nCamera = GetCameraIndex(camera);

	return (nCamera >= 0) ? pCameraArray[nCamera]->nFrameHeight : 0;
}


CAMERADLL_API void GetFirstFrame(int camera, BYTE* data, UINT* width, UINT* height)
{
	if (data == NULL || width == NULL || height == NULL) { return; }

	int nCamera = GetCameraIndex(camera);

	if (nCamera >= 0)
	{
		switch (WaitForSingleObject(pCameraArray[nCamera]->hMutex, 1000L))
		{
			case WAIT_OBJECT_0:
				if (pCameraArray[nCamera]->pFirstFrame != NULL)
				{
					*width = pCameraArray[nCamera]->nFrameWidth;
					*height = pCameraArray[nCamera]->nFrameHeight;
					memcpy(data, pCameraArray[nCamera]->pFirstFrame, pCameraArray[nCamera]->nFrameSize);
				}
				else
				{
					*width = 0;
					*height = 0;
				}
				ReleaseMutex(pCameraArray[nCamera]->hMutex);
				break;
			case WAIT_TIMEOUT:
				break;
		}
	}
	else
	{
		*width = 0;
		*height = 0;
	}
}


CAMERADLL_API void GetLastFrame(int camera, BYTE* data, UINT* width, UINT* height)
{
	if (data == NULL || width == NULL || height == NULL) { return; }

	int nCamera = GetCameraIndex(camera);

	if (nCamera >= 0)
	{
		switch (WaitForSingleObject(pCameraArray[nCamera]->hMutex, 1000L))
		{
			case WAIT_OBJECT_0:
				if (pCameraArray[nCamera]->pLastFrame != NULL)
				{
					*width = pCameraArray[nCamera]->nFrameWidth;
					*height = pCameraArray[nCamera]->nFrameHeight;
					memcpy(data, pCameraArray[nCamera]->pLastFrame, pCameraArray[nCamera]->nFrameSize);
				}
				else
				{
					*width = 0;
					*height = 0;
				}
				ReleaseMutex(pCameraArray[nCamera]->hMutex);
				break;
			case WAIT_TIMEOUT:
				break;
		}
	}
	else
	{
		*width = 0;
		*height = 0;
	}
}


CAMERADLL_API void GetFrame(int camera, ULONG frame, BYTE* data, UINT* width, UINT* height)
{
	if (data == NULL || width == NULL || height == NULL) { return; }

	int nCamera = GetCameraIndex(camera);

	if (nCamera >= 0)
	{
		// kamera byla nalezena, muze se zpracovavat dal
		switch (WaitForSingleObject(pCameraArray[nCamera]->hMutex, 1000L))
		{
			case WAIT_OBJECT_0:
				// frame neni v bufferu, vrati se prvni frame co tam je
				if (((ULONG)frame < pCameraArray[nCamera]->nFirstFrameInVector) ||
					((ULONG)frame >= pCameraArray[nCamera]->nFirstFrameInVector + CAMERA_BUFFER))
				{
					//DEBUG("GetFrame first");
					*width = pCameraArray[nCamera]->nFrameWidth;
					*height = pCameraArray[nCamera]->nFrameHeight;
					if (pCameraArray[nCamera]->pFirstFrame != NULL)
					{
						memcpy(data, pCameraArray[nCamera]->pFirstFrame, pCameraArray[nCamera]->nFrameSize);
						//DEBUG("data from first");
					}
				}
				// frame je ulozeny v bufferu
				else
				{
					ULONG index = frame - pCameraArray[nCamera]->nFirstFrameInVector;

					//DEBUG("GetFrame from buffer");
					*width = pCameraArray[nCamera]->nFrameWidth;
					*height = pCameraArray[nCamera]->nFrameHeight;
					if (pCameraArray[nCamera]->vecFrames.at(index) != NULL)
					{
						memcpy(data, pCameraArray[nCamera]->vecFrames.at(index), pCameraArray[nCamera]->nFrameSize);
						//DEBUG("data from buffer");
					}
				}
				ReleaseMutex(pCameraArray[nCamera]->hMutex);
				break;
			case WAIT_TIMEOUT:
				break;
		}
	}
	else
	{
		*width = 0;
		*height = 0;
	}
}
