#if 0
    INDI
    Copyright (C) 2003 Elwood C. Downey

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

#endif

/* simulate a CCD camera on an INDI interface.
 * each exposure sends the next .fts file in the current directory,
 * alternately also zlib compressed.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <dirent.h>
#include <errno.h>
#include <math.h>
#include <sys/time.h>
#include <unistd.h>

#include "astro.h"
#include "ip.h"
#include "indidevapi.h"
#include "base64.h"
#include "zlib.h"

static void camSim (void *p);
static int sendImage (char errmsg[]);
static void doBinning (FImage *fip, int b);

#define	POLLMS		250		/* poll period, ms */
#define	MAXEXP		600		/* max exp dur, secs */
#define	MINEXP		0		/* min exp dur, secs */
#define	EXPINC		5		/* exposure time increment, secs */
#define	DEFEXP		10		/* default exposure, secs */

/* operaional info */
#define	MYDEV	"CCDCam"		/* Device name we call ourselves */

/* INDI controls */

/* exposure duration. also serves as Go when set */
static INumber expN = {"ExpTime", "Duration, secs", "%.1f", MINEXP, MAXEXP,
    EXPINC, DEFEXP};
static INumberVectorProperty expNv = {MYDEV, "ExpValues", "Exposure", "", IP_RW,
    0, IPS_IDLE, &expN, 1};
static double exp_dur;
static struct timeval xtv;

/* binning */
static int binning = 1;
static ISwitch binS[] = {
    {"1:1", "1:1", ISS_ON},
    {"2:1", "2:1", ISS_OFF},
    {"3:1", "3:1", ISS_OFF},
    {"4:1", "4:1", ISS_OFF},
};
static ISwitchVectorProperty binSv = {MYDEV, "Binning", "Binning", "", IP_RW,
    ISR_1OFMANY, 0, IPS_IDLE, binS, NARRAY(binS)};

/* temp now */
static INumber tempature = {"Temp", "Temp now, C", "%.1f"};
static INumberVectorProperty tempnow = {MYDEV, "TempNow", "TempNow", "", IP_RO,
    0, IPS_IDLE, &tempature, 1};

/* set temp */
static INumber target = {"Target", "Target temp, C", "%.1f"};
static INumberVectorProperty settemp = {MYDEV, "SetTemp", "SetTemp", "", IP_WO,
    0, IPS_IDLE, &target, 1};

/* BLOB for sending image */
static IBLOB imageBLOB = {"Img", "Science frame"};
static IBLOBVectorProperty imageBLOBv = {MYDEV, "Pixels", "Images", "",
    IP_RO, 0, IPS_IDLE, &imageBLOB, 1};

/* BLOB for receiving 'microcode' (or anything ;-)) */
static IBLOB microcodeBLOB = {"Microcode", "Microcode"};
static IBLOBVectorProperty microcodeBLOBv = {MYDEV, "Code", "Code", "",
    IP_WO, 0, IPS_IDLE, &microcodeBLOB, 1};

/* called once before any other IS function */
static void
initcam()
{
	static int inited;

	if (inited)
	    return;

	/* start timer to simulate exposure countdown */
	IEAddTimer (POLLMS, camSim, NULL);

	inited = 1;
}

/* dummy definition for libip */
void
pm_set (int p)
{
}

/* send client definitions of all properties */
void
ISGetProperties (const char *dev)
{
	if (dev && strcmp (MYDEV, dev))
	    return;

	initcam();

	IDDefNumber (&expNv, NULL);
	IDDefNumber (&tempnow, NULL);
	IDDefNumber (&settemp, NULL);
	IDDefSwitch (&binSv, NULL);
	IDDefBLOB (&imageBLOBv, NULL);
	IDDefBLOB (&microcodeBLOBv, NULL);
}

/* client is telling us an exposure time and start an exposure */
void
ISNewNumber (const char *dev, const char *name, double *doubles, char *names[],
int n)
{
	/* ignore if not ours */
	if (strcmp (dev, MYDEV))
	    return;

	if (!strcmp (name, expNv.name) && n > 0) {
	    if (expNv.s == IPS_BUSY) {
		expNv.s = IPS_OK;
		expN.value = 0;
		IDSetNumber (&expNv, "Exposure aborted");
	    } else {
		int i;
		for (i = 0; i < n; i++)
		    if (strcmp (names[i], "ExpTime") == 0)
			break;
		expN.value = doubles[i];
		expNv.s = IPS_BUSY;
		exp_dur = expN.value;
		gettimeofday (&xtv, NULL);
		IDSetNumber (&expNv, "Starting %g sec exposure binned %s", 
				    exp_dur, IUFindOnSwitch (&binSv)->label);
	    }
	} else if (!strcmp (name, settemp.name) && n > 0) {
	    int i;
	    for (i = 0; i < n; i++)
		if (strcmp (names[i], "Target") == 0)
		    break;
	    settemp.np[0].value = doubles[i];
	}
}

/* we are being sent a new BLOB. just store it as a file.
 * N.B. sizes are after any compression implied by the formats.
 */
void
ISNewBLOB (const char *dev, const char *name, int sizes[],
    int blobsizes[], char *blobs[], char *formats[], char *names[], int n)
{
	/* ignore if not ours */
	if (strcmp (dev, MYDEV))
	    return;

	if (!strcmp (name, microcodeBLOBv.name) && n > 0) {
	    int i;

	    for (i = 0; i < n; i++) {
		FILE *fp;
		char fn[1024];
		sprintf (fn, "%s%s", names[i], formats[i]);
		fp = fopen (fn, "w");
		if (fp && fwrite (blobs[i], blobsizes[i], 1, fp) == 1) {
		    microcodeBLOB.blob = NULL;
		    microcodeBLOB.bloblen = 0;
		    microcodeBLOB.size = 0;
		    microcodeBLOBv.s = IPS_OK;
		    IDSetBLOB (&microcodeBLOBv, "wrote %s", fn);
		} else {
		    microcodeBLOB.blob = NULL;
		    microcodeBLOB.bloblen = 0;
		    microcodeBLOB.size = 0;
		    microcodeBLOBv.s = IPS_ALERT;
		    IDSetBLOB (&microcodeBLOBv, "%s: %s", fn, strerror(errno));
		}
		if (fp)
		    fclose (fp);
	    }
	}
}

void
ISNewText (const char *dev, const char *name, char *texts[], char *names[],
    int n)
{
}

/* client is telling us to set new binning */
void
ISNewSwitch (const char *dev, const char *name, ISState *states, char *names[],
int n)
{
	int i;

	/* ignore if not ours */
	if (strcmp (dev, MYDEV))
	    return;

	if (!strcmp (name, binSv.name)) {
	    for (i = 0; i < n; i++) {
		ISwitch *sp = IUFindSwitch (&binSv, names[i]);
		if (states[i] == ISS_ON) {
		    for (i = 0; i < NARRAY(binS); i++)
			binS[i].s = ISS_OFF;
		    sp->s = ISS_ON;
		    binSv.s = IPS_OK;
		    binning = atoi (sp->label);
		    IDSetSwitch (&binSv, "Binning set to %s", sp->label);
		    break;
		}
	    }
	}
}

/* update the "camera" during an exposure */
static void
camSim (void *p)
{
	if (expNv.s == IPS_BUSY) {
	    struct timeval tv;
	    double dt;

	    /* find total elapsed time since start of exposure */
	    gettimeofday (&tv, NULL);
	    dt = tv.tv_sec - xtv.tv_sec + (tv.tv_usec - xtv.tv_usec)/1e6;
	    if (dt >= exp_dur) {
		char errmsg[1024];

		/* timer is finished */
		expN.value = 0;
		IDSetNumber (&expNv, "Exposure complete, sending image");

		/* send sample image */
		if (sendImage(errmsg) < 0) {
		    expNv.s = IPS_ALERT;
		    IDSetNumber (&expNv, errmsg);
		} else {
		    expNv.s = IPS_OK;
		    IDSetNumber (&expNv, "Image sent");
		}
	    } else {
		expN.value = exp_dur - dt;
		IDSetNumber (&expNv, NULL);
	    }
	}

        /* send temperature -- tracks set value pretty well ;-) */
	tempnow.np[0].value = settemp.np[0].value;
	tempnow.s = IPS_OK;
	IDSetNumber (&tempnow, NULL);

	/* again */
	IEAddTimer (POLLMS, camSim, NULL);
}

/* send the next local file as a BLOB
 * return 0 if ok else -1 with message in errmsg[]
 */
static int
sendImage(char errmsg[])
{
	static int skipnfts;;
	struct dirent *dep;
	char name[sizeof(dep->d_name)];
	DIR *dp;
	int i;

	i = 0;
	dp = opendir (".");
	while ((dep = readdir(dp))) {
	    if (strstr(dep->d_name, ".fts") || strstr(dep->d_name, ".fits")) {
		if (i++ == skipnfts) {
		    strcpy (name, dep->d_name);
		    skipnfts++;
		    break;
		}
	    }
	}
	closedir (dp);
	if (i == 0) {
	    strcpy (errmsg, "No test image files found");
	    return(-1);
	}

	if (dep) {
	    /* open next file */
	    FILE *fp = fopen (name, "r");
	    char *blob = malloc (4096);
	    int n, nblob = 0;
	    int size;

	    /* read into memory */
	    while ((n = fread (blob+nblob, 1, 4096, fp)) > 0)
		blob = realloc (blob, (nblob+=n)+4096);
	    fclose (fp);
	    size = nblob;

	    /* perform image mods, if necessary */
	    if (binning > 1) {
		FImage fim, *fip = &fim;
		char msg[1024];

		/* convert blob to FITS */
		if (readFITSmem (blob, nblob, fip, msg) < 0) {
		    fprintf (stderr, "%s: %s", name, msg);
		    exit(1);
		}
		free (blob);

		/* perform binning IN PLACE */
		if (fip->bitpix != 16) {
		    sprintf (errmsg, "%s: must be 16 bits for binning", name);
		    resetFImage (fip);
		    return (-1);
		}
		doBinning (fip, binning);

		/* convert back to blob */
		nblob = writeFITSmem (fip, &blob, msg, 0);
		if (nblob < 0) {
		    fprintf (stderr, "%s: %s", name, msg);
		    exit(1);
		}
		size = nblob;

		/* finished with fip */
		resetFImage (fip);
	    }

	    strcpy (imageBLOB.format, ".fts");
	    fprintf (stderr, "%s: Sending %s binned %d:1\n", MYDEV, name,
								binning);

	    /* send */
	    imageBLOB.blob = blob;
	    imageBLOB.bloblen = nblob;
	    imageBLOB.size = size;
	    imageBLOBv.s = IPS_OK;
	    IDSetBLOB (&imageBLOBv, NULL);
	    free (blob);
	    return (0);
	} else {
	    skipnfts = 0;
	    return (sendImage(errmsg));	/* just one level recursion */
	}
}

/* perform binning b:1 on fip IN PLACE */
static void
doBinning (FImage *fip, int b)
{
	CamPix *frpix = (CamPix *) fip->image;
	CamPix *topix = frpix;
	int frr, frc;
	char cmt[128];

	/* rebin in place (ok ok, resample, whatever) */
	for (frr = b-1; frr < fip->sh; frr += b) {
	    CamPix *frrow = &frpix[frr*fip->sw];
	    for (frc = b-1; frc < fip->sw; frc += b)
		*topix++ = frrow[frc];
	}

	/* update dimensions in header (breaks WCS) */
	sprintf (cmt, "Rebinned by %d:1 from %d", b, fip->sw);
	setIntFITS (fip, "NAXIS1", fip->sw /= b, cmt);
	sprintf (cmt, "Rebinned by %d:1 from %d", b, fip->sh);
	setIntFITS (fip, "NAXIS2", fip->sh /= b, cmt);
}

/* For RCS Only -- Do Not Edit */
static char *rcsid[2] = {(char *)rcsid, "@(#) $RCSfile: cam.c,v $ $Date: 2005/07/09 03:07:09 $ $Revision: 1.22 $ $Name:  $"};
