// -----------------------------------------------------------------------------------------
// Sapera++ console demo
// 
//    This program shows how to grab images from a camera into a buffer in the host
//    computer's memory, using Sapera++ Acquisition and Buffer objects, and a Transfer 
//    object to link them.  
//
// -----------------------------------------------------------------------------------------

#include "stdio.h"
#include "SapClassBasic.h"
#include "SapExUtil.h"
#include "X_Display_utils.h"
#include "CameraLinkCmd.h"

#define ENABLE_DISPLAY_OUTPUT 		1
#define MAX_IMAGE_BUFFERS	8

typedef struct tagMY_CONTEXT {
	X_VIEW_HANDLE View;
	UINT32 height;
	UINT32 width;
	UINT32 pixDepth;
	SapBuffer *Buffers;
	void *image[MAX_IMAGE_BUFFERS];

} MY_CONTEXT, *PMY_CONTEXT;

// Static Functions
static void print_usage( char *program_name );
static void AcqCallback(SapXferCallbackInfo *pInfo);
static BOOL GetOptions(int argc, char *argv[], char *acqServerName,
		UINT32 *pAcqDeviceIndex, char *camFileName, char *vicFileName);
static BOOL GetOptionsFromCommandLine(int argc, char *argv[],
		char *acqServerName, UINT32 *pAcqDeviceIndex, char *camFileName,
		char *vicFileName);

void InitConsole() {
	// Initialize the console (stdin) for direct (raw) access.
}

char GetKey() {
	char key = getchar();
	while ((key == '\r') || (key == '\n')) {
		key = getchar();
	}
	return key;
}

void PrintMenu() {
	printf("GRAB CTL   : [0]=stop, [1-9]=snap N, [G]=continuous, [A]=abort\n");
	printf("CAM CTL    : [L]=information, [C]=conversiongain, [E]=exposuretime, [T]=TECenable, [t]=TECtarget,\n\t[R]=ROI, [S]=triggerenable, [s]=triggersource, [P]=softwaretrigger, [F]=show current framerate, [f]=set framerate\n");
	printf("MISC       : [Q]or[ESC]=end, [@]=SaveToFile\n");
}

uint64_t GetTickCount() {
	struct timespec ts;
	clock_gettime(CLOCK_MONOTONIC, &ts);
	return (uint64_t)(ts.tv_sec * 1000 + ts.tv_nsec / 1000000);
}

static unsigned begintime_;
static unsigned totalframe_ = 0;
static int trash_ = 0;
int main(int argc, char* argv[]) {
	printf("consoledemo\n");
	SapAcquisition *Acq = NULL;
	SapBuffer *Buffers = NULL;
	SapTransfer *Xfer = NULL;
	X_VIEW_HANDLE View = NULL;
	MY_CONTEXT context;
	UINT32 pixFormat;
	UINT32 videoType;
	char uniqueName[128];
	char uniqueIndex = 0;
	char acqServerName[CORSERVER_MAX_STRLEN], camFilename[MAX_PATH],
			vicFilename[MAX_PATH];
	UINT32 acqDeviceNumber;
	int done = FALSE;
	int i = 0;
	char c;

	printf("Sapera Console Grab Example (C++ version)\n");

	// Call GetOptions to determine which acquisition device to use and which CAM/VIC
	// files should be loaded to configure it.
	// Note: if this were an MFC-enabled application, we could have replaced the lengthy GetOptions
	// function with the CCamVicDlg dialog of the Sapera++ GUI Classes (see GrabMFC example)
	if (!GetOptions(argc, argv, acqServerName, &acqDeviceNumber, camFilename,
			vicFilename)) 
	{
		print_usage( argv[0] );
		printf("\nPress any key to terminate\n");
		getchar();
		return 0;
	}

	// Generate a unique name to be used for saving image files.
	strcpy(uniqueName, "CorXXXXXX");
	mkstemp(uniqueName);
	remove(uniqueName);
	InitConsole();

	SapLocation loc(acqServerName, acqDeviceNumber);

	Acq = new SapAcquisition(loc, camFilename, vicFilename);
	Buffers = new SapBuffer(MAX_IMAGE_BUFFERS, Acq);

	Xfer = new SapAcqToBuf(Acq, Buffers, AcqCallback, &context);

	// Create Acq object.
	if (Acq && Acq->Create()) 
	{
		// Create buffer object
		if (Buffers && Buffers->Create()) 
		{
			// Create transfer object
			if (Xfer && Xfer->Create()) 
			{
				// Set up the X11 display window.
				Acq->GetParameter(CORACQ_PRM_CROP_HEIGHT, &context.height);
				Acq->GetParameter(CORACQ_PRM_CROP_WIDTH, &context.width);
				Acq->GetParameter(CORACQ_PRM_OUTPUT_FORMAT, &pixFormat);
				Acq->GetParameter(CORACQ_PRM_PIXEL_DEPTH, &context.pixDepth);

				for (i = 0; i < MAX_IMAGE_BUFFERS; i++) 
				{
					Buffers->GetParameter(i, CORBUFFER_PRM_ADDRESS,
							&context.image[i]);
				}
				// This works best for monochrome and RGB. The packed color formats (with Y, U, V, etc) might not work.
				//????int pixDepth = context.pixDepth / ((CORDATA_FORMAT_IS_COLOR(pixFormat)) ? 3 : 1);
				int pixDepth = context.pixDepth;
				if (CORDATA_FORMAT_IS_COLOR(pixFormat))
				{
					Acq->GetParameter(CORACQ_PRM_VIDEO, &videoType);
					if (videoType == CORACQ_VAL_VIDEO_RGB)
					{
						pixDepth *= 4;
						context.pixDepth *= 4;
					} 
				}
#if ENABLE_DISPLAY_OUTPUT
				View = CreateDisplayWindow(NULL, TRUE, context.height, context.width, pixDepth, pixFormat, FALSE);
#endif
				context.View = View;
				context.Buffers = Buffers;

				// Set up the abnormal termination handler to shut off the grab.
				{
					CORHANDLE handles[2] = { 0 };
					handles[0] = Xfer->GetHandle();
					SaperaRegisterTerminationHandler(1, handles);
				}

				//Initialize the serial port and set the baudrate
				HCamL h = CamL_Init(SapManager::GetServerIndex(acqServerName)-1, 115200);
				char model[32], fpgaVer[16], fpgaDate[16], sn[64];
				bool isView = false;
				// Call the main command loop or the example.
				PrintMenu();
				while (!done) 
				{
					printf("USER>");
					c = GetKey();

					// Stop
					if (c == '0') 
					{
						isView = false;
						Xfer->Freeze();
						if (!Xfer->Wait(2000)) 
						{
							Xfer->Abort();
						}
					}
					//Abort
					if ((c == 'A') || (c == 'a')) 
					{
						Xfer->Abort();
					}
					// Snap N (1 to 9 frames)
					if ((c >= '1') && (c <= '9')) 
					{
						Xfer->Snap(c - '0');
					}
					// Continuous grab.
					if ((c == 'G') || (c == 'g')) 
					{
						begintime_ = GetTickCount();
						totalframe_ = 0;
						isView = true;
						Xfer->Grab();
					}
					// Save file. Named as COR_<tempname>_<#>.bmp
					if (c == '@') 
					{
						char filename[256];

						// Try as TIFF - in case libtiff is present.
						// Named as COR_<tempname>_<#>.tif
						sprintf(filename, "%s_img%04d.tif", uniqueName, uniqueIndex++);
						if ( !Buffers->Save(filename, "-format tiff") )
						{
							// Try as BMP - Named as COR_<tempname>_<#>.bmp
							sprintf(filename, "%s_img%04d.bmp", uniqueName, uniqueIndex);
							Buffers->Save(filename, "-format bmp");
						}
					}

					if (c == '?') 
					{
						PrintMenu();
					}

					if ((c == 0x1b) || (c == 'q') || (c == 'Q')) 
					{
						done = TRUE;
					}

					//Camera Control
					//Get information about the camera
					if ((c == 'L') || (c == 'l'))
					{
						int pxOffset, pyOffset, pWidth, pHeight;
						CamL_GetModel(h, model);
						printf("Model Name \t%s\n", model);
						CamL_GetSN(h, sn);
						printf("SN \t%s\n", sn);
						CamL_GetFpgaVersion(h, fpgaVer, fpgaDate);
						printf("Fpga Version \t%s\n", fpgaVer);
						printf("Fpga Date \t%s\n", fpgaDate);
						printf("Conversion Gain \t%d\n", CamL_GetCG(h));
						printf("Exposure Time \t%d [us]\n", CamL_GetExpoTime(h));
						printf("Max FrameRate \t%d fps\n", CamL_GetMaxFrameRate(h));
						if (CamL_IsTecEnabled(h))
						{
							printf("TEC Enable \ttrue\n");
							printf("TEC Target \t%.1f\n", CamL_GetTargetTemperature(h));
						}
						else
							printf("TEC Enable \tfalse\n");
						printf("Temperature \t%.1f\n", CamL_GetCurrentTemperature(h));
						if (CamL_IsTriggerEnabled(h))
						{
							printf("Trigger Enable \ttrue\n");
							printf("Trigger Sourse \t%d\n", CamL_GetTriggerSource(h));
						}
						else
							printf("Trigger Enable \tfalse\n");
						CamL_GetRoi(h, &pxOffset, &pyOffset, &pWidth, &pHeight);
						printf("ROI \t(%d,%d,%d,%d)\n", pxOffset, pyOffset, pWidth, pHeight);
					}

					//Set camera conversion gain
					if ((c == 'C') || (c == 'c'))
					{
						fflush(stdin);
						printf("Input conversion gain:");
						int cg;
						scanf("%d", &cg);
						CamL_SetCG(h, cg);
					}

					//Set camera exposure time
					if ((c == 'E') || (c == 'e'))
					{
						int time;
						printf("Input exposure time:");
						scanf("%d", &time);
						CamL_SetExpoTime(h, time);
						if (isView)
						{
							begintime_ = GetTickCount();
							totalframe_ = 0;
						}
					}

					//Set camera TEC enable
					if (c == 'T')
					{
						int enable;
						printf("Input TEC enable:");
						scanf("%d", &enable);
						CamL_EnableTec(h, (bool)enable);
					}
					//Set camera TEC target
					if (c == 't')
					{
						float target;
						printf("Input TEC target:");
						scanf("%f", &target);
						CamL_SetTargetTemperature(h, target);
					}

					//Set camera ROI
					if ((c == 'R') || (c == 'r'))
					{
						int pxoffset, pyoffset, pwidth, pheight;
						printf("Input ROI Xoffset Yoffset width height:");
						scanf("%d %d %d %d", &pxoffset, &pyoffset, &pwidth, &pheight);
						CamL_SetRoi(h, pxoffset, pyoffset, pwidth, pheight);
					}

					//Set camera trigger enable
					if (c == 'S')
					{
						int enable;
						printf("Input trigger enable:");
						scanf("%d", &enable);
						CamL_EnableTrigger(h, (bool)enable);
					}
					//Set camera trigger source
					if (c == 's')
					{
						int source;
						printf("Input trigger source:");
						scanf("%d", &source);
						CamL_SetTriggerSource(h, source);
					}

					//Software Trigger
					if ((c == 'P') || (c == 'p'))
					{
						int start;
						printf("Input start:");
						scanf("%d", &start);
						CamL_Trigger(h, start);
						printf("Software Trigger\n");
					}

					//Framerate
					if (c == 'f')
					{
						int rate;
						printf("Input FrameRate target:");
						scanf("%d", &rate);
						CamL_SetFrameRate(h, rate);
						if (isView)
						{
							begintime_ = GetTickCount();
							totalframe_ = 0;
						}
					}

					//show current framerate
					if (c == 'F')
					{
						unsigned uTime = GetTickCount() - begintime_;
						int frameRate = totalframe_ * 1000 / nTime;
						printf("frame rate = %d fps, total frames = %u, time = %u ms\n", frameRate, totalframe_, uTime);
					}
				}

				//Unitialize the serial port
				CamL_UnInit(h);

				// Destroy transfer object
				Xfer->Freeze();
				if (!Xfer->Wait(2000)) 
				{
					Xfer->Abort();
				}

				// Destroy the display view.
				if (View) 
				{
					context.View = NULL;
					DestroyDisplayWindow(View);
					View = NULL;
				}
				Xfer->Destroy();

			}
			// Destroy buffer object
			Buffers->Destroy();
		}
		// Destroy acquisition object
		Acq->Destroy();
	}

	// Delete all objects
	if (Xfer)
		delete Xfer;
	if (Buffers)
		delete Buffers;
	if (Acq)
		delete Acq;
	return 0;
}
static void AcqCallback(SapXferCallbackInfo *pInfo) 
{
	// Get the context structure and the current buffer index.
	PMY_CONTEXT pcontext = (PMY_CONTEXT) pInfo->GetContext();
	int buf_index = pcontext->Buffers->GetIndex();
#if ENABLE_DISPLAY_OUTPUT
	// Display the current buffer.
	Display_Image(pcontext->View, pcontext->pixDepth, pcontext->width,
			pcontext->height, pcontext->image[buf_index]);
#endif
	if (pInfo->IsTrash())
	{
		trash_++;
	}
	else
	{
		totalframe_++;
	}
}

static BOOL GetServerNamesByResource( int maxServerNames, int nameLength, char **serverNames, int resourceType, int *numFound)
{	
   int found = 0;
	BOOL  serverFound = FALSE;
	
	if ( (numFound == NULL) || (serverNames != NULL) )
	{
		// Scan the servers to find those that support the desired resource.
		for (int i = 0; i < maxServerNames; i++)
		{
			if (SapManager::GetResourceCount(i, resourceType) != 0)
			{
				SapManager::GetServerName(i, serverNames[found], nameLength);
				found++;
				serverFound = TRUE;
			}
		}	
		*numFound = found;
	}
	return serverFound;
}

static void print_usage( char *program_name )
{
   int i;
   int numServer = 0;
   char **serverNames = NULL;
   char *use_name = program_name;
   const char *def_name = "consoledemo";
   char resourceName[128] = {0};
   
   use_name = (use_name == NULL) ? (char *)def_name : use_name; 
   
   // Get total number of servers in the system
   int serverCount = SapManager::GetServerCount();
   if (serverCount < 1)
   {
      printf("No server found!\n");
      return;
   }
   
   // Allocate the max number of server names
   serverNames = (char **)malloc(serverCount * sizeof(char *));
   for (i = 0; i < serverCount; i++)
   {
		serverNames[i] = (char *)malloc( CORSERVER_MAX_STRLEN );
	}
	
	// Get all ACQ servers....
	if ( GetServerNamesByResource( serverCount, CORSERVER_MAX_STRLEN, serverNames, SapManager::ResourceAcq, &numServer) )
	{
		if (numServer > 0)
		{
			printf("Found %d servers supporting Acquisition - possible command lines are :\n", numServer);
			
			for (i = 0; i < numServer; i++)
			{
				int j;
				int numAcq = SapManager::GetResourceCount(serverNames[i], SapManager::ResourceAcq);
				for (j = 0; j < numAcq; j++)
				{
					SapManager::GetResourceName( serverNames[i], SapManager::ResourceAcq, j, resourceName, 128);
					printf("\t%s %s %d <CCF filename for %s >\n", use_name, serverNames[i], j,  resourceName);
				}
			}
		}
		else
		{
			// No Acq servers found
			printf("No servers supporting acquisition were found!\n");
		}
	}
	else
	{
		// No Acq servers found
		printf("No servers supporting acquisition were found!\n");
	}
	
	// Cleanup
   for (i = 0; i < serverCount; i++)
   {
		free(serverNames[i]);
	}
	free(serverNames);
}

static BOOL GetOptions(int argc, char *argv[], char *acqServerName,
		UINT32 *pAcqDeviceIndex, char *camFileName, char *vicFileName) 
{
	// Check if arguments were passed
	return GetOptionsFromCommandLine(argc, argv, acqServerName,
			pAcqDeviceIndex, camFileName, vicFileName);
}

static BOOL GetOptionsFromCommandLine(int argc, char *argv[],
		char *acqServerName, UINT32 *pAcqDeviceIndex, char *camFileName,
		char *vicFileName) 
{
	if (argc < 2) 
	{
		// print help
		printf("Usage:\n");
		printf(
				"\t%s [<acquisition server name> <acquisition device index> <CCF filename>]\n\n", argv[0]);
		return FALSE;
	}
	// Check the command line for user commands
	if ((strcmp(argv[1], "/?") == 0) || (strcmp(argv[1], "-?") == 0)) 
	{
		// print help
		printf("Usage:\n");
		printf(
				"\t%s [<acquisition server name> <acquisition device index> <CCF filename>]\n\n", argv[0]);
		return FALSE;
	}

	// Check if enough arguments were passed
	if (argc < 4) 
	{
		printf("\n\tInvalid command line!\n");
		// print help
		printf("Usage:\n");
		printf(
				"\t%s [<acquisition server name> <acquisition device index> <CCF filename>]\n\n", argv[0]);
		return FALSE;
	}

	// Validate server name
	if (SapManager::GetServerIndex(argv[1]) < 0) 
	{
		printf("\n\tInvalid acquisition server name!\n");
		printf("Usage:\n");
		printf(
				"\t%s [<acquisition server name> <acquisition device index> <CCF filename>]\n\n", argv[0]);
		return FALSE;
	}

	// Does the server support acquisition?
	int deviceCount = SapManager::GetResourceCount(argv[1], SAPMAN_RESOURCE_ACQ);
	if (deviceCount == 0) 
	{
		printf("\n\tThis server [%s] does not support acquisition!\n\n", argv[1]);
		return FALSE;
	}

	// Validate device index
	if (atoi(argv[2]) < 0 || atoi(argv[2]) >= deviceCount) 
	{
		printf("\n\tInvalid device index [%d] for server [%s]!\n", atoi(argv[2]), argv[1] );
		printf("Usage:\n");
		printf(
				"\t%s [<acquisition server name> <acquisition device index> <CCF filename>]\n\n", argv[0]);
		return FALSE;
	}

	// Verify that the specified CCF (CAM/VIC) files exist
	if (!CheckFileExists(argv[3], FALSE)) 
	{
		printf("\n\tSpecified CAM file (%s) is invalid!\n", argv[3]);
		printf("Usage:\n");
		printf(
				"\t%s [<acquisition server name> <acquisition device index> <CCF filename>]\n\n", argv[0]);
		return FALSE;
	}

	// Fill-in output variables
	strcpy(acqServerName, argv[1]);
	*pAcqDeviceIndex = atoi(argv[2]);
	strcpy(camFileName, argv[3]);
	strcpy(vicFileName, argv[3]);
	
	return TRUE;
}
