/* -------------------------------------------------------------------------- */
/* -                 Astronomical CCD Camera Control                        - */
/* -                      Apogee Camera Protocol                            - */
/* -------------------------------------------------------------------------- */
/*                                                                            */
/* Copyright 2009-2012 John Kielkopf                                          */
/* kielkopf@louisville.edu                                                    */
/*                                                                            */
/* This file is part of XmCCD.                                                */
/*                                                                            */
/* XmCCD 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 3 of the License, or          */
/* (at your option) any later version.                                        */
/*                                                                            */
/* XmCCD 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.                               */   
/*                                                                            */
/* Date: September 3, 2013                                                    */
/*                                                                            */
/* Version 4.2                                                                */
/*                                                                            */
/* For use with XmCCD and the INDI driver                                     */
/*                                                                            */
/* -------------------------------------------------------------------------- */


/* System */

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

/* XmCCD */

#include "protocol.h"

/* Include the cfitsio library */

#include <fitsio.h>


/* The follow functions are exported and availble for use in other programs   */
/* As much as possible these prototypes will be stable in subsequent versions */

/* Camera system operations */
/* Return (1) on success, (0) on failure */
/* May send messages to stderr */

int OpenCamera(int device);     

int CloseCamera(int device);    

int GetCameraInfo(int *image_width, int *image_height);

int GetCameraStatus(int *status);

int GetCameraTemperature(double *temperature, double *setpoint, double *power);

int SetCameraTemperature(int state, double temperature);

int GetCameraFilterName(int new_filter_number,  char *new_filter_name);

int SetCameraFilter(int filter);

int SetCameraFilterName(int flag, 
  int new_filter_number, char *new_filter_name);
  
int ActivateCameraRelay(int relay, double ontime);
             
/* Image capture functions for external use                    */
/* Returns 1 on success and 0 on failure                       */
/* May send messages to stderr                                 */
/* Images are always saved as a file on disk                   */
/* Most recent image is always available in allocated storage  */

/* Reads these parameters as needed:                           */
/*   phase     0 start exposure                                */
/*             1 readout if ready                              */
/*             2 interrupt and reset                           */
/*   frame     0 dark                                          */
/*             1 light                                         */
/*             2 bias                                          */
/*             3 flat                                          */
/*   exposure  time in seconds                                 */
/*   data      malloc'd image storage                          */
/*   subarea   TRUE or FALSE for subarea extraction            */
/*   x         initial x for region                            */
/*   y         initial y for region                            */
/*   width     width of region                                 */
/*   height    height of region                                */

/* Returns these parameters as needed:                         */
/*   phase     0 exposure not started                          */
/*             1 exposure in progress                          */
/*             2 exposure complete                             */
/*   data      binary array with image                         */


/* Usage:                                                      */
/*   Make the first call with phase = 0 to start an exposure   */
/*   Make repeated calls with phase = 1 to ask for a readout   */
/*   User may delay asking for phase 1 or poll                 */
/*   Make one call with phase = 2 to interrupt an exposure     */
/*   Return value of phase indicates whether image was read    */
/*   Function will set phase = 2 when the exposure is done     */
/*   A minimum of two calls are required to capture an image   */

  
int CaptureImage(int *phase,  unsigned short *data,
  int frame, double exposure, int subarea, 
  int x, int y, int w, int h);

int CaptureEnd(int *phase);

/* Save an image as in FITS file format with header information */
  
void WriteFits(char *filename, int w, int h, unsigned short *data, 
  double fits_duration, 
  char*  fits_type, 
  char*  fits_date, 
  char*  fits_target,
  char*  fits_instrument,
  double fits_temperature, 
  char*  fits_filter, 
  char*  fits_telescope);  

/* Internal fits error handler used in WriteFits */

void show_cfitsio_error(int status);

                        
/* A fixed ip address for ethernet cameras */

unsigned long camera_ip_address = CCD_ETH; 

/* Parameters for internal use */
/* Prefixed by ccd_ may differ from global parameters */

static int ccd_device;
static int ccd_image_width;
static int ccd_image_height;
static int ccd_phase;
static int ccd_image_status;
int ccd_filter;
int ccd_filter_max;
double ccd_cooler_power = 0.;
int ccd_cooler_state = 0;
double ccd_temperature = 20.;
double ccd_setpoint = 20.;
static unsigned int ccd_id=1;
 

/* Default labels for filters in the wheel                       */
/* The names can be changed by calling SetCameraFilterName       */
/* The indiserver or xmtel preferences will redefine these names */

char ccd_filter_name[][24] = {
  " (1)              ",
  " (2)              ",
  " (3)              ",
  " (4)              ",
  " (5)              ",
  " (6)              ",  
  " (7)              ",  
  " (8)              ",
  " (9)              ", 
  "(10)              "
 }; 
 
 
/* Array of default blank labels that simply number the filters */ 
 
char default_filter_name[][24] = {
  " (1)              ",
  " (2)              ",
  " (3)              ",
  " (4)              ",
  " (5)              ",
  " (6)              ",  
  " (7)              ",  
  " (8)              ",
  " (9)              ", 
  "(10)              "
 };     
 

/* Open connection and intialize camera */
/* Returns TRUE on success */

int OpenCamera(int device)
{  
  int status;   
  int simulation = FALSE;
  
  if (device == USB)
  { 
    ccd_device = USB;
  }
  else if  (device == ETH)
  {
    ccd_device = ETH;
  }
  else if (device == PIO)
  {
    ccd_device = PIO;
  }
  else
  {
    if ( !simulation )
    {
      fprintf(stderr,"Incorrect device specification sent to camera driver");
      return(0);
    }      
  }
       
  /* Apogee support only for USB at this time */
  
  if (ccd_device == USB)
  {
    status = apogee_open(ccd_id);
  }
  else
  {
    fprintf(stderr,"Only USB Apogee cameras are supported");
    status = -1;
  }    
  if ( status != 0 )
  {
    return(0);
  }   
  return(1);
}


/* Close the camera and driver */
/* Returns TRUE on success and FALSE on failure */
/* This should be the last action on shutdown */

int CloseCamera(int device)
{

  
  /* Apogee driver library does not include a close function */


  return(1);
}


/* Provide essential information about the camera system */
/* Returns TRUE on success and FALSE otherwise */

int GetCameraInfo(int *image_width, int *image_height)
{
  char *sensor; 
  char *camera;
  int roiw, roih;
  int binw, binh;
  int osw, osh;
  double mintemp; 
  
  /* Get camera information */
  
  apogee_get_info(&sensor, &camera);
    
  ccd_image_width  = 0;
  ccd_image_height = 0;
      
  apogee_get_values(&roiw, &roih, &osw, &osh, 
    &binw, &binh, &mintemp);     
   
  ccd_image_width = roiw;
  ccd_image_height = roih;

  *image_width = ccd_image_width;
  *image_height = ccd_image_height;
  
  if ( ccd_image_width < 256 )
  {
    fprintf(stderr,"Unknown CCD.\n");
    return(0);
  }
  else
  {
    fprintf(stderr,"Apogee %dx%d CCD found.\n", 
    ccd_image_width, ccd_image_height);
    fprintf(stderr,"Sensor: %s\n", sensor);
    fprintf(stderr,"Camera: %s\n", camera);
  }          
  return(1);
}

/* Inquire about the exposure status of the camera */
/* Sets internal ccd_image_status                  */
/* Returns these values to the calling routine     */

int GetCameraStatus(int *image_status)
{
  int status;
      
  /* Assign status to standard values */
  /* Options are                      */
  /*   IDLE                           */
  /*   INTEGRATING                    */
  /*   COMPLETE                       */
  
  /* Apogee reports either IDLE or COMPLETE with image ready to read */
   
  status = apogee_get_status();
  if (status == 2)
  {
    ccd_image_status = COMPLETE;
  }
  else
  {
    ccd_image_status = IDLE;
  }    

  /* Return status to calling routine  */
  
  *image_status = ccd_image_status;
  return(1);
}


/* Inquire about the CCD temperature, setpoint, and cooler power level */

int GetCameraTemperature(double *power, double *temperature, double *setpoint)
{
  
  int status;
  
  status = apogee_get_cooler_temperature(&ccd_temperature);
  
  if ( status == 0 )
  {
    ccd_cooler_power = 0.;
  }
  else if ( status == 1)
  {
    ccd_cooler_power = 1.;
  }
  else if ( status == 2 )
  {  
    ccd_cooler_power = 0.5;
  }
  else
  {
    ccd_cooler_power = 0.;
  }      

  /* Return data to calling routine */

  *power = ccd_cooler_power;
  *temperature = ccd_temperature;
  *setpoint = ccd_setpoint;

  return(1);
}


/* CCD cooler control */
/* Includes ambient workaround for cooler off bug */
/* Fan will stay high */


int SetCameraTemperature(int state, double temperature)
{
  int fan = 3;
  double ambient = 20.;
    
  if( state == TRUE )
  {
    /* Fan always on high */
    apogee_set_fan(fan);

    /* Cooler on at requested temperature */
    ccd_cooler_power = 1.;
    ccd_cooler_state = 1;
    apogee_set_cooler_state(ccd_cooler_state);
    ccd_setpoint = temperature;
    apogee_set_cooler_temperature(ccd_setpoint);
  }
  else if ( state == FALSE )
  {
    /* Fan left on high for warmup */
    apogee_set_fan(fan);

    /* Cooler flags off at ambient */
    ccd_cooler_power = 0.;
    ccd_cooler_state = 0;
    
    /****************************************************************/
    /*                                                              */
    /* Apogee 16M cooler will not turn off but stops regulating     */
    /*                                                              */
    /* Workaround is to regulate at ambient temperature             */
    /* Set flags to indicate cooler is disabled and power is off    */
    /* Enable setpoint at typical ambient temperature               */
    /* Cooler will continue to regulate until camera is reset       */
    /*                                                              */
    /* apogee_set_cooler_state(ccd_cooler_state);                   */
    /*                                                              */
    /****************************************************************/
    
    ccd_setpoint = ambient;
    apogee_set_cooler_temperature(ambient);
  }
  else
  {
    fprintf(stderr,"Unknown cooler state request\n");
  }      
  
  return(1);
}

/* Provide filter information for a filter number */
/* For use only with an Apogee filter wheel  */

int GetCameraFilterName(int new_filter_number,char new_filter_name[24])
{
  return(0);
}

/* Replace a filter name with a new one for positive flag    */
/* Restore defaults for zero or negative flag                */
/* Return 1: success                                         */ 
/* Return 0: if filter wheel not present no action taken     */
/* Return 0: out of bounds filter_number no action taken     */
/* Use default filter name if incoming name is too long      */
/* For use only with an Apogee filter wheel                  */


int SetCameraFilterName(int flag, 
  int new_filter_number, char *new_filter_name)
{
  return(0);
}

/* Request filter change                     */
/* Filter numbers are from 1 to FILTER_MAX   */
/* For use only with an Apogee filter wheel  */

int SetCameraFilter(int filter)
{
  return(0);  
}


/* Activate one tracking relay at a time              */
/* May be generalized for both x and y simultaneously */
/* Relay ID is the bit pattern 0001 0010 0100 1000    */
/* Time ontime in seconds up to maximum of 30         */

int ActivateCameraRelay(int relay, double ontime)
{
  int status = 0;

  status = apogee_guide(relay, ontime);

  if ( status == 0 ) 
  {
    fprintf(stderr,"Camera relay error\n");
    return(0);    
  } 
  return(1);
}


/* Acquire an image */

int CaptureImage(int *phase,  unsigned short *data,
  int frame, double exposure, int subarea, 
  int x, int y, int w, int h)
{ 
  int status;
    
  ccd_phase = *phase;
  
  if ( ccd_phase == 2 )
  {
    
    /* End an exposure without readout */
    
    status = apogee_stop_exposure();
    ccd_phase = 0;
    *phase = ccd_phase;
    if ( status == FALSE )  
    {
      fprintf(stderr,"Error ending image exposure");
      return(0);
    }
    return(1);
  }
    
  if ( ccd_phase == 0 )
  { 
  
    /* Test for subarea request */
    /* If not subarea, then default to driver's values for ccd dimensions */
    /* If subarea, then check validity and clamp to allowed bounds */

    if (subarea == FALSE)
    {
       x = 0;
       y = 0;
       w = ccd_image_width;
       h = ccd_image_height;
    }  
    else 
    {
      if (x < 0)
      {
        x = 0;
      }
      if (y < 0)
      {
        y = 0;
      }
      if (x > ccd_image_width  )
      {
        x = ccd_image_width;
      }
      if (y > ccd_image_height )
      {
        y = ccd_image_height;
      }
      if (w > ccd_image_width)
      {
        w = ccd_image_width;
      }
      if (h > ccd_image_height)
      {
        h = ccd_image_height;
      }
    } 
    
    /* Send start request to the camera for imaging exposure  */
      
    status = TRUE;
    
    if ( (frame == LIGHT) | (frame == FLAT) )
    {  
      /* Shutter open */
      status = apogee_expose(exposure, x, y, h, w, TRUE);
    }
    else if ( (frame == DARK) | (frame == BIAS) )
    {
      /* Shutter closed */
      status = apogee_expose(exposure, x, y, h, w, FALSE);
    }
    else
    {
      fprintf(stderr,"Unknown frame type requested in CaptureImage");
      return(0);
    }

    /* Did this work? */

    if ( status == FALSE ) 
    {
      fprintf(stderr,"Request to start camera exposure ignored");
      return(0);
    }
   
    /* Return indicating succesful start and exposure in progress */
    
    ccd_phase = 1;
    *phase = ccd_phase;
    return(1);
  }   
    
  if ( ccd_phase != 1 )
  {
    /* We have already handled all allowed values except 1 */
    /* Reset phase to 0.  This permits recursive calls */
    
    ccd_phase = 0;
    *phase = ccd_phase;
    return(0) ;
  }
  
  /* Are we done yet? */
  /* If so, read it, if not reset phase and return */

  GetCameraStatus(&ccd_image_status);
  
  if ( ccd_image_status != COMPLETE )
  {
    /* The exposure is still underway */
    ccd_phase = 1;
    *phase = ccd_phase;
    return(1);
  }  
           
  /* Flush the buffers */
  /* This probably isn't necessary.  It's a carryover from earlier versions. */
        
  fflush(stdout);
  fflush(stderr);
  sync();
      
  /* Read it using subarea information sent with exposure request */
  
  status = apogee_get_image(data);

  if ( status == FALSE ) 
  {
    fprintf(stderr,"Unable to read image data from camera");
    ccd_phase = 0;
    *phase = ccd_phase;    
    return(0);
  } 
      
  /* Successful readout */
  /* Indicate that a new image is available */
  
  ccd_phase = 2;
  *phase = ccd_phase;  
  return(1);
}
  

/* End an exposure without readout */

int CaptureEnd(int *phase)
{
  int status;
    
  ccd_phase = *phase;
  
  if ( ccd_phase == 2 )
  {        
    status = apogee_stop_exposure();
    ccd_phase = 0;
    *phase = ccd_phase;
    if ( status == FALSE )  
    {
      fprintf(stderr,"Error ending image exposure");
      return(0);
    }
    return(1);
  }  
  
  return(0);
}


/* FITS routines                               */
/*                                             */
/* Write a FITS primary array with a 2-D image */
/*   and a header with keywords                */
/*                                             */             
  
void WriteFits(char *filename, int w, int h, unsigned short *data, 
  double fits_duration, 
  char*  fits_type, 
  char*  fits_date, 
  char*  fits_target,
  char*  fits_instrument,
  double  fits_temperature, 
  char*  fits_filter, 
  char*  fits_telescope)  
{
    fitsfile *fptr;       /* pointer to the FITS file, defined in fitsio.h */
    int status;
    long  fpixel, nelements;

    /* Initialize FITS image parameters */

    int bitpix   =  USHORT_IMG;       /* 16-bit unsigned short pixel values */
    long naxis    =   2;              /* 2-dimensional image                */    
    long naxes[2] = { 256,256 };      /* default image 256 wide by 256 rows */
    
    /* Set the actual width and height of the image */

    naxes[0] = w;
    naxes[1] = h;

    /* Delete old FITS file if it already exists */  

    remove(filename); 
     
    /* Must initialize status before calling fitsio routines */           

    status = 0;  
    
    /* Create a new FITS file and show error message if one occurs */

    if (fits_create_file(&fptr, filename, &status)) 
         show_cfitsio_error( status );           

    /* Write the required keywords for the primary array image.       */
    /* Since bitpix = USHORT_IMG, this will cause cfitsio to create   */
    /* a FITS image with BITPIX = 16 (signed short integers) with     */
    /* BSCALE = 1.0 and BZERO = 32768.  This is the convention that   */
    /* FITS uses to store unsigned integers.  Note that the BSCALE    */
    /* and BZERO keywords will be automatically written by cfitsio    */
    /* in this case.                                                  */

    if ( fits_create_img(fptr,  bitpix, naxis, naxes, &status) )
        show_cfitsio_error( status );          

    fpixel = 1;                               /* first pixel to write      */
    nelements = naxes[0] * naxes[1];          /* number of pixels to write */

    /* Write the array of unsigned integers to the FITS file */
         
    if ( fits_write_img(fptr, TUSHORT, fpixel, nelements, data, &status) )
      show_cfitsio_error( status );
        
    /* Write optional keywords to the header */
    
    if ( fits_update_key_dbl(fptr, "EXPTIME", fits_duration, -4,
      "exposure time (seconds)", &status) )
      show_cfitsio_error( status );
           
    if ( fits_update_key_str(fptr, "DATE-OBS", 
         fits_date, "date of observation (UT)", &status) )
         show_cfitsio_error( status );
         
    if ( fits_update_key_str(fptr, "IMAGETYP", 
         fits_type, "image type", &status) )
         show_cfitsio_error( status );         

    if ( fits_update_key_str(fptr, "TARGET", 
         fits_target, "target", &status) )
         show_cfitsio_error( status );
                   
    if ( fits_update_key_str(fptr, "INSTRUME", 
         fits_instrument, "instrument", &status) )
         show_cfitsio_error( status ); 
         
    if ( fits_update_key_dbl(fptr, "CCD-TEMP", 
         fits_temperature, -4, "temperature (C)", &status) )
         show_cfitsio_error( status ); 
                                 
    if ( fits_update_key_str(fptr, "FILTER", 
         fits_filter,"filter", &status) )
         show_cfitsio_error( status );

    if ( fits_update_key_str(fptr, "TELESCOP", 
         fits_telescope,"telescope", &status) )
         show_cfitsio_error( status );         
           
    if ( fits_write_date(fptr, &status) )
         show_cfitsio_error( status );                   
          
    /* Close the file */             

    if ( fits_close_file(fptr, &status) )              
         show_cfitsio_error( status );           

    return;
}


/* Print cfitsio error report */

void show_cfitsio_error(int status)
{
  if (status)
  {
     fits_report_error(stderr, status); 
  }
  return;
}  


/* Determine barycenter of guide star in image subarray */

void find_guide_star(double *data, int w, int h, double *x_s, double *y_s)
{
  int i,j,k;
  double xval,yval,pixval,sumval,xav,yav;
  int guide_threshold = 127;
    
  k = 0;
  sumval = 0.;
  xav = 0.;
  yav = 0.;
  for (j = 0; j < h; j++)
  {
    yval = (double) j;
    for (i = 0; i < w; i++)
    {
      pixval = data[k];
      if ( pixval >= guide_threshold )
      {
        xval = (double) i;
        sumval = sumval + pixval;
        xav = xav + xval * pixval;
        yav = yav + yval * pixval;
      }
      k++;
    }
  }
    
  /* Handle frame without signal */ 
  
  if (sumval <= 0.)
  {
    *x_s = ((double) w)/2.; 
    *y_s = ((double) h)/2.;
    return;
  }
     
  xav = xav / sumval;
  yav = yav / sumval;
  *x_s = xav;
  *y_s = yav;
  
  return;
}

