/*

Copyright (c) 2002 Finger Lakes Instrumentation (FLI), L.L.C.
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.

Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials
provided with the distribution.

Neither the name of Finger Lakes Instrumentation (FLI), LLC
nor the names of its contributors may be used to endorse or
promote products derived from this software without specific
prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

======================================================================

Finger Lakes Instrumentation, L.L.C. (FLI)
web: http://www.fli-cam.com
email: support@fli-cam.com

*/

#include "stdafx.h"

#ifdef WIN32
#include "XGetopt.h"
#endif

#define WINTAKEPIC_VER "1.0.0"

#define USEFITS

#define MAX_DEVICES 32
#define BUFF_SIZE 1024

#ifdef USEFITS
#include "fitsio.h"
#endif /* USEFITS */

#include "libfli.h"

#define DOFLIAPI(F) { if ((status = (F)) != 0) { printf("%s failed in %s, line %d. status = %d\n", #F, __FILE__, __LINE__, status); ThreadState = TS_DONE; break; } }

#define LIBVERSIZE 1024

long status = 0;

int numCams = 0;
char *listName[MAX_DEVICES];
long listDomain[MAX_DEVICES];

void EnumerateCameras(flidomain_t enumDomain);

int WriteRaw(char *filename, int width, int height, void *data);

#ifdef USEFITS
int WriteFits(char *filename, int width, int height, void *data);
#endif

enum threadstate_t 
{
	TS_RBI_FLOOD,
	TS_RBI_FLUSH,
	TS_EXPOSING,
	TS_DOWNLOAD,
	TS_START_EXPOSURE,
	TS_DONE,
	TS_ABORT,
} ThreadState = TS_START_EXPOSURE;

void usage(char *fmt, ...)
{
	extern const char *__progname;
	va_list ap;

	va_start(ap, fmt);

	printf("\n");
	if(fmt != NULL)
	{
		vprintf(fmt, ap);
		printf("\n\n");
	}

	printf("Usage: wintakepic [-n <num pics>] [-h <hbin>] [-v <vbin>] [-x <exposure time in milliseconds>] [-t <tdi rate>] [-y <rbi exposure time in milliseconds>] [-r <rbi num flushes>] [-b <rbi bin>] <outfile basename>\n");

	va_end(ap);

	exit(0);
}

int myatoi(int *num, const char *str)
{
	long tmp;
	char *endptr;

	tmp = strtol(str, &endptr, 0);
	if(*str == '\0' || *endptr != '\0' || tmp < INT_MIN || tmp > INT_MAX)
	{
		return -1;
	}
	*num = (int)tmp;
	return 0;
}

int main(int argc, char *argv[])
{
	printf("====================\n");
	printf("FLI WinTakePic " WINTAKEPIC_VER " \n");

	if(argc > 1)
	{
		printf("Arguments: ");
		for(int i = 1; i < argc; i++)
		{
			printf("%s ", argv[i]);
		}
		printf("\n");
	}

	//FLISetDebugLevel("C://test.txt", FLIDEBUG_ALL);

	int opt;
	int pics = 1, hbin = 1, vbin = 1, exptime = 500, rbiFlushes = 0, rbin = 1, rbiExposureTime = 500;
	int tdi_rate = 0;
	char *outfile, libver[LIBVERSIZE];

	////////////////////////////////////////////////
	// Parse command line argument list
	while((opt = getopt(argc, argv, "h:n:v:x:t:y:r:b:")) != -1)
	{
		switch (opt)
		{
		case 'h':
			if (myatoi(&hbin, optarg) || hbin <= 0)
				usage("Invalid hbin: %s", optarg);
			break;

		case 'n':
			if (myatoi(&pics, optarg) || pics <= 0)
				usage("Invalid number of pics: %s", optarg);
			break;

		case 'v':
			if (myatoi(&vbin, optarg) || vbin <= 0)
				usage("Invalid vbin: %s", optarg);
			break;

		case 't':
			if (myatoi(&tdi_rate, optarg) || tdi_rate <= 0)
				usage("Invalid tdi rate: %s", optarg);
			break;

		case 'x':
			if (myatoi(&exptime, optarg) || exptime <= 0)
				usage("Invalid exposure time: %s", optarg);
			break;

		// RBI Flooding
		case 'y':
			if (myatoi(&rbiExposureTime, optarg) || rbiExposureTime <= 0)
				usage("Invalid rbi exposure time: %s", optarg);
			break;

		case 'r':
			if (myatoi(&rbiFlushes, optarg) || rbiFlushes < 0)
				usage("Invalid rbi num flushes: %s", optarg);
			break;

		case 'b':
			if (myatoi(&rbin, optarg) || rbin <= 0)
				usage("Invalid rbin: %s", optarg);
			break;

		default:
			usage(NULL);
		}
	}

	argc -= optind;
	argv += optind;

	if(argc == 0)
	{
		usage("No output file basename given");
	}

	outfile = argv[0];
	argc--;
	argv++;

	for(int i = 0; i < argc; i++)
	{
		printf("Ignoring command line argument '%s'\n", argv[i]);
	}

	////////////////////////////////////////////////

	if(FLIGetLibVersion(libver, LIBVERSIZE) != 0)
	{
		printf("Unable to retrieve library version!\n");
		exit(0);
	}

	printf("Library version '%s'\n", libver);

	EnumerateCameras(FLIDOMAIN_USB | FLIDEVICE_CAMERA);

	if(numCams == 0)
	{
		printf("\nNo FLI cameras have been detected\n");
	}

	flidev_t dev = FLI_INVALID_DEVICE;

	for(int i = 0; i < numCams; i++)
	{
		long tmp1, tmp2, tmp3, tmp4, img_rows, row_width;
		double d1, d2;
		char buff[BUFF_SIZE];
		unsigned short *img;
		int row;

		if(dev != FLI_INVALID_DEVICE)
		{
			FLIClose(dev);
			dev = FLI_INVALID_DEVICE;
		}

		printf("\nConnecting to camera '%s' with domain '%d'\n", listName[i], listDomain[i]);

		DOFLIAPI(FLIOpen(&dev, listName[i], listDomain[i]));
		if(status != 0)
		{
			continue;
		}

		DOFLIAPI(FLIGetModel(dev, buff, BUFF_SIZE));
		printf("Model:        %s\n", buff);

		DOFLIAPI(FLIGetSerialString(dev, buff, BUFF_SIZE));
		printf("Serial Num:   %s\n", buff);

		DOFLIAPI(FLIGetHWRevision(dev, &tmp1));
		printf("Hardware Rev: %ld\n", tmp1);

		DOFLIAPI(FLIGetFWRevision(dev, &tmp1));
		printf("Firmware Rev: %ld\n", tmp1);

		DOFLIAPI(FLIGetPixelSize(dev, &d1, &d2));
		printf("Pixel Size:   %f x %f\n", d1, d2);

		DOFLIAPI(FLIGetArrayArea(dev, &tmp1, &tmp2, &tmp3, &tmp4));
		printf("Array Area:   (%ld, %ld)(%ld, %ld)\n", tmp1, tmp2, tmp3, tmp4);

		DOFLIAPI(FLIGetVisibleArea(dev, &tmp1, &tmp2, &tmp3, &tmp4));
		printf("Visible Area: (%ld, %ld)(%ld, %ld)\n", tmp1, tmp2, tmp3, tmp4);

		DOFLIAPI(FLISetTemperature(dev, -10.0));

		d1 = 0.0;

		DOFLIAPI(FLIReadTemperature(dev, FLI_TEMPERATURE_CCD, &d1))
		printf("CCD Temp:     %f\n", d1);

		DOFLIAPI(FLIReadTemperature(dev, FLI_TEMPERATURE_BASE, &d1))
		printf("Base Temp:    %f\n", d1);

		d1 = 0;
		FLIGetCoolerPower(dev, &d1);
		printf("Cooler Power: %f%%\n", d1);

		printf("Press any key to start the exposure or press 'Q' to quit\n");

		if(_getch() == 'q')
		{
			printf("Quitting...\n");
			continue;
		}

		row_width = (tmp3 - tmp1) / hbin;
		img_rows = (tmp4 - tmp2) / vbin;

		if((img = (unsigned short*)malloc(img_rows * row_width * sizeof(unsigned short))) == NULL)
		{
			printf("Image malloc failed\n");
			continue;
		}

		int rbiWidth = (tmp3 - tmp1) / rbin;
		int rbiRows = (tmp4 - tmp2) / rbin;
		unsigned short *rowBuffer = NULL;

		if(rbiFlushes > 0)
		{
			if((rowBuffer = (unsigned short*)malloc(rbiWidth * sizeof(unsigned short))) == NULL)
			{
				printf("Row buffer malloc failed\n");
				continue;
			}
		}

		for(int j = 0; j < pics; j++)
		{
			printf("Image %d:\n\tExposing...", j);

			///////////////////////////////////
			// Exposure
			long remaining_exposure = 0;
			long camera_status = 0;

			int rbiCount = 0;

			if(rbiFlushes > 0)
			{
				rbiCount = rbiFlushes;

				ThreadState = TS_RBI_FLOOD;
			}
			else
			{
				ThreadState = TS_START_EXPOSURE;
			}

			while(ThreadState != TS_DONE)
			{
				switch(ThreadState)
				{
					case TS_RBI_FLOOD:
					{
						//printf("TS_RBI_FLOOD\n");

						FLISetTDI(dev, 0, 0);
						DOFLIAPI(FLISetExposureTime(dev, rbiExposureTime))
						DOFLIAPI(FLISetFrameType(dev, FLI_FRAME_TYPE_RBI_FLUSH))
						DOFLIAPI(FLISetHBin(dev, rbin))
						DOFLIAPI(FLISetVBin(dev, rbin))
						DOFLIAPI(FLISetImageArea(dev, tmp1, tmp2, tmp1 + rbiWidth, tmp2 + img_rows));
						DOFLIAPI(FLIExposeFrame(dev))

						ThreadState = TS_EXPOSING;

						break;
					}

					case TS_RBI_FLUSH:
					{
						//printf("TS_RBI_FLUSH\n");

						FLISetTDI(dev, 0, 0);
						DOFLIAPI(FLISetExposureTime(dev, 0))
						DOFLIAPI(FLISetFrameType(dev, FLI_FRAME_TYPE_DARK))
						DOFLIAPI(FLISetHBin(dev, rbin))
						DOFLIAPI(FLISetVBin(dev, rbin))
						DOFLIAPI(FLISetImageArea(dev, tmp1, tmp2, tmp1 + rbiWidth, tmp2 + img_rows));
						DOFLIAPI(FLIExposeFrame(dev))

						ThreadState = TS_EXPOSING;

						break;
					}

					case TS_EXPOSING:
					{
						DOFLIAPI(FLIGetDeviceStatus(dev, &camera_status))
						DOFLIAPI(FLIGetExposureStatus(dev, &remaining_exposure))

//						if((remaining_exposure == 0) || (camera_status & FLI_CAMERA_DATA_READY))
						if ( ((camera_status == FLI_CAMERA_STATUS_UNKNOWN) && (remaining_exposure == 0)) ||
						 		 ((camera_status != FLI_CAMERA_STATUS_UNKNOWN) && ((camera_status & FLI_CAMERA_DATA_READY) != 0))
						 )
						{
							ThreadState = TS_DOWNLOAD;
							if(rbiCount == 0)
							{
								printf("Done\n");
							}
						}
						else
						{
							Sleep((remaining_exposure > 200) ? 200 : remaining_exposure + 1);
						}

						break;
					}

					case TS_DOWNLOAD:
					{
						if(rbiCount > 0)
						{
							if(rowBuffer != NULL)
							{
								for(row = 0; row < rbiRows; row++)
								{
									DOFLIAPI(FLIGrabRow(dev, rowBuffer, rbiWidth));
								}
							}
							
							rbiCount--;

							if(rbiCount <= 0)
							{
								ThreadState = TS_START_EXPOSURE;
							}
							else
							{
								ThreadState = TS_RBI_FLUSH;
							}
						}
						else
						{
							printf("\tDownloading...");

							for(row = 0; row < img_rows; row++)
							{
								DOFLIAPI(FLIGrabRow(dev, &img[row * row_width], row_width));
							}

							printf("Done\n");

							////////////////////////////////////////////////
							#define WRITEIMG(writefn, ext)	\
								if(_snprintf(buff, BUFF_SIZE, "%s.%d.%d." ext, outfile, i, j) == -1) \
								{ \
									printf("\tOutput file name too long\n"); \
								} \
								if(writefn(buff, row_width, img_rows, img) == 0) \
								{ \
									printf("\tImage written to '%s'\n", buff); \
								}

							WRITEIMG(WriteRaw, "raw");

							#ifdef USEFITS
								WRITEIMG(WriteFits, "fit");
							#endif

							#undef WRITEIMG
							////////////////////////////////////////////////

							ThreadState = TS_DONE;
						}

						break;
					}

					case TS_START_EXPOSURE:
					{
						DOFLIAPI(FLISetImageArea(dev, tmp1, tmp2, tmp1 + row_width, tmp2 + img_rows));
						DOFLIAPI(FLISetExposureTime(dev, exptime));
						DOFLIAPI(FLISetFrameType(dev, FLI_FRAME_TYPE_NORMAL));
						DOFLIAPI(FLISetHBin(dev, hbin));
						DOFLIAPI(FLISetVBin(dev, vbin));

						if(tdi_rate > 0)
						{
							printf("...Setting TDI rate to '%d'...", tdi_rate);
							DOFLIAPI(FLISetTDI(dev, tdi_rate, 0));
						}
						else
						{
							FLISetTDI(dev, 0, 0);
						}

						DOFLIAPI(FLIExposeFrame(dev))

						ThreadState = TS_EXPOSING;

						break;
					}

					case TS_DONE:
					{
						break;
					}
				}
			}
		}

		//printf("Freeing Resources\n");

		if(rowBuffer != NULL)
		{
			free(rowBuffer);
			rowBuffer = NULL;
		}

		free(img);
	}

	if(dev != FLI_INVALID_DEVICE)
	{
		FLIClose(dev);
	}

	for(int i=0; i < numCams; i++)
	{
		free(listName[i]);
	}
}

void EnumerateCameras(flidomain_t enumDomain)
{
	numCams = 0;

	char file[MAX_PATH], name[MAX_PATH];
	long domain;

	FLICreateList(enumDomain);

	if(FLIListFirst(&domain, file, MAX_PATH, name, MAX_PATH) == 0)
	{
		do
		{
			listName[numCams] = (char*)malloc(strlen(file) + 1);
			strcpy(listName[numCams], file);

			listDomain[numCams] = domain;
			numCams++;
		}
		while((FLIListNext(&domain, file, MAX_PATH, name, MAX_PATH) == 0) && (numCams < MAX_DEVICES));
	}

	FLIDeleteList();
}

int WriteRaw(char *filename, int width, int height, void *data)
{
	FILE *pFile = NULL;

	pFile = fopen(filename, "r");
	if(pFile != NULL)
	{
		fclose(pFile);
		printf("\tFile '%s' already exists\n", filename);
		return 1;
	}

	pFile = fopen(filename, "wb");
	if(pFile == NULL)
	{
		printf("\tUnable to open file for writing\n");
		return 1;
	}

	int count = width * height * sizeof(unsigned short);
	if(fwrite(data, 1, count, pFile) != count)
	{
		printf("\tUnable to write data\n");
		return 1;
	}

	if(fclose(pFile) != 0)
	{
		printf("\tUnable to close file\n");
		return 1;
	}

	return 0;
}

#ifdef USEFITS

int WriteFits(char *filename, int width, int height, void *data)
{
	FILE *pFile = fopen(filename, "r");
	if(pFile != NULL)
	{
		fclose(pFile);
		printf("\tFile '%s' already exists\n", filename);
		return 1;
	}

	int status = 0;
	long naxes[2] = {width, height};
	fitsfile *fp;

	fits_create_file(&fp, filename, &status);
	if(status)
	{
		printf("fits_create_file: %d\n", status);
		fits_report_error(stderr, status);
		return -1;
	}

	fits_create_img(fp, USHORT_IMG, 2, naxes, &status);
	if(status)
	{
		printf("fits_create_img: %d\n", status);
		fits_report_error(stderr, status);
		return -1;
	}

	fits_write_date(fp, &status);

	fits_write_img(fp, TUSHORT, 1, (width * height), data, &status);
	if(status)
	{
		printf("fits_write_img: %d\n", status);
		fits_report_error(stderr, status);
		return -1;
	}

	fits_close_file(fp, &status);

	if(status)
	{
		printf("fits_close_file: %d\n", status);
		fits_report_error(stderr, status);
		return -1;
	}

	return 0;
}

#endif /* USEFITS */
