/*
 * -*- linux-c -*-
 * Linux Kernel Module for the Audine Camera
 * Copyright (C) 2001 Peter Kirchgessner
 * http://www.kirchgessner.net, mailto:peter@kirchgessner.net
 *
 * Modified by F. Manenti <oss_astr_cav@arcanet.it> for the use in the
 * NOVA environment (nova.sourceforge.net)
 *
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 * The sample interface routines for the module have been taken from the
 * Linux Kernel Module Programming Guide by Ori Pomerantz contained
 * in the Linux Documentation Project.
 *
 */

#include "audineccd.h"
#include "audinelib.h"

#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <sys/ioctl.h>
#include <errno.h>

#define AUD_DEVICE_FILE  "/dev/audine"

#define ERRNORET(a) return (errno = a, -(a))

#define AUD_HANDLE_CHECK(a) \
  { if (!(a)) ERRNORET (EINVAL); if ((a)->fd < 0) ERRNORET (EBADF); }

struct aud_handle_s {
  int fd;
  int single_read;
};


AUD_HANDLE aud_open (void)

{AUD_HANDLE aud = (AUD_HANDLE)calloc (1, sizeof (struct aud_handle_s));

 if (!aud) return 0;

 aud->fd = open (AUD_DEVICE_FILE, O_RDWR);
 if (aud->fd < 0)
 {
   free (aud);
   return 0;
 }

 aud->single_read = 1;

 return aud;
}


void aud_close (AUD_HANDLE aud)

{
 if (aud)
 {
   if (aud->fd >= 0) close (aud->fd);
   free (aud);
 }
}


char *aud_version (const AUD_HANDLE aud)

{static struct ccd_capability Info;
 char *version = "";
 int ret;

 if (aud)
 {
   ret = ioctl (aud->fd, CCD_RD_VER, &Info);
   if (ret == 0) version = &(Info.name[0]);
 }
 return version;
}


int aud_clear (const AUD_HANDLE aud, int nclear)

{int ret = 0;

 if (nclear > 0) ret = ioctl (aud->fd, CCD_CLR, &nclear);
 return ret;
}


int aud_binning_set (const AUD_HANDLE aud, int vb, int hb)

{struct ccd_capability Info;

 AUD_HANDLE_CHECK (aud);
 if ((vb < 1) || (vb > 4) || (hb < 1) || (hb > 4))
   ERRNORET (EINVAL);

 Info.width = vb;
 Info.height = hb;
 return ioctl (aud->fd, CCD_SET_BNN, &Info);
}


int aud_binning_get (const AUD_HANDLE aud, int *vb, int *hb)

{struct ccd_capability Info;
 int ret;

 AUD_HANDLE_CHECK (aud);

 Info.width = 0;
 Info.height = 0;
 ret = ioctl (aud->fd, CCD_SET_BNN, &Info);
 if (ret != 0) return ret;
 if (vb) *vb = Info.width;
 if (hb) *hb = Info.height;
 return 0;
}


int aud_geometry_set (const AUD_HANDLE aud, int x, int y, int width, int height)

{struct ccd_capability Info;

 AUD_HANDLE_CHECK (aud);

 Info.minwidth = x;
 Info.minheight = y;
 Info.width = width;
 Info.height = height;
 return ioctl (aud->fd, CCD_SET_WND, &Info);
}


int aud_geometry_reset (const AUD_HANDLE aud)

{
 AUD_HANDLE_CHECK (aud);

 return ioctl (aud->fd, CCD_RST_WND);
}


int aud_geometry_get (const AUD_HANDLE aud, int *xorigin, int *yorigin,
                      int *winwidth, int *winheight, int *color)

{struct ccd_capability Info;
 int ret;

 AUD_HANDLE_CHECK (aud);

 ret = ioctl(aud->fd, CCD_RD_GEOM, &Info);
 if (ret != 0) return ret;

 if (xorigin) *xorigin = Info.minwidth;
 if (yorigin) *yorigin = Info.minheight;
 if (winwidth) *winwidth = Info.width;
 if (winheight) *winheight = Info.height;
 if (color) *color = Info.color;

 return 0;
}


int aud_port_set (const AUD_HANDLE aud, int base)

{
 AUD_HANDLE_CHECK (aud);
 if (base <= 0) return -EINVAL;
 return ioctl (aud->fd, CCD_SET_PRT, &base);
}


int aud_port_get (const AUD_HANDLE aud, int *base)

{
 AUD_HANDLE_CHECK (aud);
 if (!base) return -1;
 *base = 0;
 return ioctl (aud->fd, CCD_SET_PRT, base);
}


static int aud_line_ctrl_set (const AUD_HANDLE aud, int cmd, int ctrl)

{
 AUD_HANDLE_CHECK (aud);
 if ((ctrl != 1) && (ctrl != 2) && (ctrl != 4) && (ctrl != 8))
   ERRNORET (EINVAL);
 return ioctl (aud->fd, cmd, &ctrl);
}


static int aud_line_set (const AUD_HANDLE aud, int cmd, int on_off)

{
 AUD_HANDLE_CHECK (aud);
 if (on_off != 0) on_off = 1;
 return ioctl (aud->fd, cmd, &on_off);
}


int aud_amplifier_ctrl_set (const AUD_HANDLE aud, int ctrl)

{
 return aud_line_ctrl_set (aud, CCD_SET_AMP, ctrl);
}


int aud_amplifier_set (const AUD_HANDLE aud, int on_off)

{
 return aud_line_set (aud, CCD_SWTC_AMP, on_off);
}


int aud_shutter_ctrl_set (const AUD_HANDLE aud, int ctrl)

{
 return aud_line_ctrl_set (aud, CCD_SET_SHT, ctrl);
}


int aud_shutter_set (const AUD_HANDLE aud, int on_off)

{
 return aud_line_set (aud, CCD_SWTC_SHT, on_off);
}


int aud_aux0_ctrl_set (const AUD_HANDLE aud, int ctrl)

{
 return aud_line_ctrl_set (aud, CCD_SET_AX0, ctrl);
}


int aud_aux0_set (const AUD_HANDLE aud, int on_off)

{
 return aud_line_set (aud, CCD_SWTC_AX0, on_off);
}


int aud_aux1_ctrl_set (const AUD_HANDLE aud, int ctrl)

{
 return aud_line_ctrl_set (aud, CCD_SET_AX1, ctrl);
}


int aud_aux1_set (const AUD_HANDLE aud, int on_off)

{
 return aud_line_set (aud, CCD_SWTC_AX1, on_off);
}


int aud_ccd_info_get (const AUD_HANDLE aud, char **name, int *width,
                      int *height, int *color)

{static struct ccd_capability Info;
 int ret;

 AUD_HANDLE_CHECK (aud);
 if ((ret = ioctl (aud->fd, CCD_RD_CHIP, &Info)) != 0) return ret;

 if (name) *name = Info.name;
 if (width) *width = Info.width;
 if (height) *height = Info.height;
 if (color) *color = Info.color;

 return 0;
}


int aud_ccd_listentry_get (const AUD_HANDLE aud, int entry, char **name,
                           int *width, int *height, int *color)
{static struct ccd_capability Info;
 int ret;

 AUD_HANDLE_CHECK (aud);
 Info.color = entry;
 if ((ret = ioctl (aud->fd, CCD_RD_CCDL, &Info)) != 0) return ret;

 if (name) *name = Info.name;
 if (width) *width = Info.width;
 if (height) *height = Info.height;
 if (color) *color = Info.color;

 return 0;
}


void aud_single_read_set (AUD_HANDLE aud, int single_read)

{
 if (aud) aud->single_read = single_read;
}


int aud_image_read (const AUD_HANDLE aud, char **buf, int *bufsize,
                    int *width, int *height, int *color)

{int ret;
 int nbytes, nread, len;
 char *imgbuf;

 AUD_HANDLE_CHECK (aud);
 if ((!buf) || (!bufsize)) ERRNORET (EINVAL);
 if ((!width) || (!height) || (!color)) ERRNORET (EINVAL);

 if ((ret = aud_geometry_get (aud, 0, 0, width, height, color)) != 0)
   return ret;

 /* We get 2 bytes per pixel */
 nbytes = *width * *height * 2;

 /* Do we have to free the user buffer ? */
 if ((*bufsize < nbytes) && (*buf))
 {
   free (*buf);
   *buf = 0;
   *bufsize = 0;
 }

 /* If buffer not supplied, allocate buffer */
 if (!(*buf))
 {
   *buf = malloc (nbytes);
   if (!*buf) ERRNORET (ENOMEM);
   *bufsize = nbytes;
 }

 /* Start reading image */
 if ((ret = ioctl(aud->fd, CCD_RD_IMG)) != 0) return ret;

 imgbuf = *buf;
 while (nbytes > 0)
 {
   if (aud->single_read)
   {
     nread = nbytes;
   }
   else
   {
     nread = *width * 2;
     if (nread > nbytes)
       nread = nbytes;
   }
   len = read(aud->fd, imgbuf, nread);
   if (len <= 0) return -1;
   nbytes -= len;
   imgbuf += len;
 }
 return 0;
}


static void aud_ccdstruct_log (const char *fct, int ret,
                               const struct ccd_capability *info)

{
 printf ("\nFunction %s returned %d\n", fct, ret);
 if (ret != 0) return;
 printf ("Name : %-32s\n", info->name);
 printf ("Width: %-6d Height: %-6d Minwidth: %-6d Minheight: %-6d Color: %d\n",
         info->width, info->height, info->minwidth, info->minheight,
         info->color);
}


void aud_ioctl_test (AUD_HANDLE aud)

{int ret;
 struct ccd_capability info;
 char *buf = 0, *name;
 int bufsize = 0, width, height, color;
 int j;

 if ((!aud) || (aud->fd < 0)) return;

 /* Read driver version */
 ret = ioctl (aud->fd, CCD_RD_VER, &info);
 aud_ccdstruct_log ("CCD_RD_VER", ret, &info);

 /* Read chip information */
 ret = ioctl (aud->fd, CCD_RD_CHIP, &info);
 aud_ccdstruct_log ("CCD_RD_CHIP", ret, &info);

 /* Read geometry */
 ret = ioctl (aud->fd, CCD_RD_GEOM, &info);
 aud_ccdstruct_log ("CCD_RD_GEOM", ret, &info);

 /* Set Window */
 info.minwidth = 1;
 info.minheight = 2;
 info.width = 200;
 info.height = 100;
 ret = ioctl (aud->fd, CCD_SET_WND, &info);
 printf ("\nCalled CCD_SET_WND: (1,2) (200,100)\n");

 /* Read geometry */
 ret = ioctl (aud->fd, CCD_RD_GEOM, &info);
 aud_ccdstruct_log ("CCD_RD_GEOM", ret, &info);

 /* Set binning */
 info.width = 2;
 info.height = 3;
 printf ("\nSet binning %dx%d", info.width, info.height);
 ret = ioctl (aud->fd, CCD_SET_BNN, &info);
 aud_ccdstruct_log ("CCD_SET_BNN", ret, &info);

 /* Read geometry */
 ret = ioctl (aud->fd, CCD_RD_GEOM, &info);
 aud_ccdstruct_log ("CCD_RD_GEOM", ret, &info);

 /* (Re-)Set binning */
 info.width = 1;
 info.height = 1;
 printf ("\nSet binning %dx%d", info.width, info.height);
 ret = ioctl (aud->fd, CCD_SET_BNN, &info);
 aud_ccdstruct_log ("CCD_SET_BNN", ret, &info);

 /* Read geometry */
 ret = ioctl (aud->fd, CCD_RD_GEOM, &info);
 aud_ccdstruct_log ("CCD_RD_GEOM", ret, &info);

 /* Clear two times */
 info.width = 2;
 printf ("\nStart clear %d times\n", info.width); fflush (stdout);
 ret = ioctl (aud->fd, CCD_CLR, &info.width);
 printf ("Clear finished.\n");

 /* Reading */
 printf ("Start reading image\n");
 ret = aud_image_read (aud, &buf, &bufsize, &width, &height, &color);
 printf ("Finished reading: ret=%d width=%d height=%d color=%d bufsize=%d\n",
         ret, width, height, color, bufsize);

 /* Set binning */
 info.width = 2;
 info.height = 3;
 printf ("\nSet binning %dx%d", info.width, info.height);
 ret = ioctl (aud->fd, CCD_SET_BNN, &info);
 aud_ccdstruct_log ("CCD_SET_BNN", ret, &info);

 /* Reading */
 printf ("Start reading small image\n");
 ret = aud_image_read (aud, &buf, &bufsize, &width, &height, &color);
 printf ("Finished reading: ret=%d width=%d height=%d color=%d bufsize=%d\n",
         ret, width, height, color, bufsize);

 /* Reset window */
 ret = ioctl (aud->fd, CCD_RST_WND);
 printf ("\nReset window\n");

 /* Reset binning */
 info.width = 1;
 info.height = 1;
 printf ("\nSet binning %dx%d", info.width, info.height);
 ret = ioctl (aud->fd, CCD_SET_BNN, &info);
 aud_ccdstruct_log ("CCD_SET_BNN", ret, &info);

 /* Read geometry */
 ret = ioctl (aud->fd, CCD_RD_GEOM, &info);
 aud_ccdstruct_log ("CCD_RD_GEOM", ret, &info);

 /* Reading */
 printf ("Start reading large image\n");
 ret = aud_image_read (aud, &buf, &bufsize, &width, &height, &color);
 printf ("Finished reading: ret=%d width=%d height=%d color=%d bufsize=%d\n",
         ret, width, height, color, bufsize);

 /* Read current port */
 ret = aud_port_get (aud, &j);
 printf ("\naud_port_get returned with %d. Port=0x%x\n", ret, j);

 printf ("\nList of supported CCDs:\n");
 j = 0;
 while (aud_ccd_listentry_get (aud, j, &name, &info.width, &info.height,
                               &info.color) == 0)
 {
   printf ("%d: %s, %dx%d, %d\n", j, name, info.width, info.height, info.color);
   j++;
 }


}


#define FITS_WRITE_BOOLCARD(fp,key,value) \
{char card[81]; \
 sprintf (card, "%-8.8s= %20s%50s", key, value ? "T" : "F", " "); \
 fwrite (card, 1, 80, fp); }

#define FITS_WRITE_LONGCARD(fp,key,value) \
{char card[81]; \
 sprintf (card, "%-8.8s= %20ld%50s", key, (long)value, " "); \
 fwrite (card, 1, 80, fp); }

#define FITS_WRITE_DOUBLECARD(fp,key,value) \
{char card[81], dbl[21], *istr; \
 sprintf (dbl, "%20f", (double)value); istr = strstr (dbl, "e"); \
 if (istr) *istr = 'E'; \
 sprintf (card, "%-8.8s= %20.20s%50s", key, dbl, " "); \
 fwrite (card, 1, 80, fp); }

#define FITS_WRITE_STRINGCARD(fp,key,value) \
{char card[81]; int k;\
 sprintf (card, "%-8.8s= \'%s", key, value); \
 for (k = strlen (card); k < 81; k++) card[k] = ' '; \
 k = strlen (key); if (k < 8) card[19] = '\''; else card[11+k] = '\''; \
 fwrite (card, 1, 80, fp); }

#define FITS_WRITE_CARD(fp,value) \
{char card[81]; \
 sprintf (card, "%-80.80s", value); \
 fwrite (card, 1, 80, fp); }

int audine_fits_write (const char *fname, const char *img,
                       int width, int height)
{
     FILE *fp;
     char value[50];
     unsigned short *us_img = (unsigned short *) img;
     unsigned short *row;
     int x, y, high, low;
     long pos;
     time_t timp;

     fp = fopen(fname, "w");
     if (fp) {
	  FITS_WRITE_BOOLCARD(fp, "SIMPLE", 1);
	  FITS_WRITE_LONGCARD(fp, "BITPIX", 16);
	  FITS_WRITE_LONGCARD(fp, "NAXIS", 2);
	  FITS_WRITE_LONGCARD(fp, "NAXIS1", width);
	  FITS_WRITE_LONGCARD(fp, "NAXIS2", height);
	  FITS_WRITE_DOUBLECARD(fp, "BZERO", 0.0);
	  FITS_WRITE_DOUBLECARD(fp, "BSCALE", 1.0);
	  FITS_WRITE_DOUBLECARD(fp, "DATAMIN", 0.0);
	  FITS_WRITE_DOUBLECARD(fp, "DATAMAX", 32767.0);
	  FITS_WRITE_CARD(fp, " ");
	  FITS_WRITE_CARD(fp, "HISTORY THIS FILE WAS GENERATED BY AUDINELIB");
	  FITS_WRITE_CARD(fp, " ");

	  timp = time(NULL);
	  strftime(value, sizeof(value), "%d/%m/%Y", gmtime(&timp));
	  FITS_WRITE_STRINGCARD(fp, "DATE", value);

	  FITS_WRITE_CARD(fp, "END");

	  /* Fill up primary HDU to multiple of 2880 bytes */
	  fflush(fp);
	  pos = ftell(fp) % 2880;
	  if (pos != 0) {
	       pos = 2880 - pos;
	       while (pos-- > 0)
		    putc(' ', fp);
	  }

	  /* FITS standard requires integer data to be most significant
	     byte first, */
	  /* image origin bottom left. We want to create an astronomical
	     oriented image (top is bottom, left is right) */
	  for (y = 0; y < height; y++) {
	       row = us_img + y * width;
	       for (x = width - 1; x >= 0; x--) {
		    high = (row[x] >> 8) & 0xff;
		    low = (row[x] & 0xff);
		    putc(high, fp);
		    putc(low, fp);
	       }
	  }
	  /* Fill up file to multiple of 2880 bytes */
	  fflush(fp);
	  pos = ftell(fp) % 2880;
	  if (pos != 0) {
	       pos = 2880 - pos;
	       while (pos-- > 0)
		    putc(0, fp);
	  }
	  fclose(fp);
     } else {
	  return -1;
     }
     return 0;
}
