#if 0
    FLI CCD
    INDI Interface for Apogee PPI
    Copyright (C) 2003 Jasem Mutlaq (mutlaqja@ikarustech.com)

    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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

#endif
 
#include <ctype.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <zlib.h>

#include "apogee_ppi.h"
#include "lilxml.h"
#include "base64.h"

static void ISPoll(void *);

extern char* me;			/* argv[0] */
ApogeeCam *MainCam = NULL;		/* Main and only camera */

/* send client definitions of all properties */
void ISInit()
{
  if (MainCam == NULL)
  {
    MainCam = new ApogeeCam();
    IEAddTimer (POLLMS, ISPoll, NULL);
  }
}
    
void ISGetProperties (const char *dev)
{ 
  if (dev && strcmp (mydev, dev))
    return;
  
   ISInit();
  
  MainCam->ISGetProperties(dev);
}


void ISNewSwitch (const char *dev, const char *name, ISState *states, char *names[], int n)
{
	
  /* ignore if not ours */
  if (dev && strcmp (dev, mydev))
    return;
	    
  ISInit();
  
  MainCam->ISNewSwitch(dev, name, states, names, n);
}

void ISNewText (const char *dev, const char *name, char *texts[], char *names[], int n)
{
  /* ignore if not ours */ 
  if (dev && strcmp (mydev, dev))
    return;

   ISInit();
   
   MainCam->ISNewText(dev, name, texts, names, n);
}


void ISNewNumber (const char *dev, const char *name, double values[], char *names[], int n)
{
      
  /* ignore if not ours */
  if (dev && strcmp (dev, mydev))
    return;
	    
  ISInit();
  
  MainCam->ISNewNumber(dev, name, values, names, n);
}

void ISNewBLOB (const char */*dev*/, const char */*name*/, int */*sizes[]*/, char **/*blobs[]*/, char **/*formats[]*/, char **/*names[]*/, int /*n*/)
{

}

void ISPoll(void *)
{
 MainCam->ISPoll(); 
 IEAddTimer (POLLMS, ISPoll, NULL);
}


ApogeeCam::ApogeeCam()
{
  ApogeeModelS = NULL;
  
  initProperties();
  
}

ApogeeCam::~ApogeeCam()
{
  
}

void ApogeeCam::initProperties()
{
  fillSwitch(&PowerS[0], "CONNECT", "Connect", ISS_OFF);
  fillSwitch(&PowerS[1], "DISCONNECT", "Disconnect", ISS_ON);
  fillSwitchVector(&PowerSP, PowerS, NARRAY(PowerS), mydev, "CONNECTION", "Connection", COMM_GROUP, IP_RW, ISR_1OFMANY, 60, IPS_IDLE);
  
  fillSwitch(&FrameTypeS[0], "FRAME_LIGHT", "Light", ISS_ON);
  fillSwitch(&FrameTypeS[1], "FRAME_BIAS", "Bias", ISS_OFF);
  fillSwitch(&FrameTypeS[2], "FRAME_DARK", "Dark", ISS_OFF);
  fillSwitch(&FrameTypeS[3], "FRAME_FLAT", "Flat Field", ISS_OFF);
  fillSwitchVector(&FrameTypeSP, FrameTypeS, NARRAY(FrameTypeS), mydev, "CCD_FRAME_TYPE", "Frame Type", EXPOSE_GROUP, IP_RW, ISR_1OFMANY, 60, IPS_IDLE);

  fillNumber(&FrameN[0], "X", "X", "%.0f", 0., MAX_PIXELS, 1., 0.);
  fillNumber(&FrameN[1], "Y", "Y", "%.0f", 0., MAX_PIXELS, 1., 0.);
  fillNumber(&FrameN[2], "WIDTH", "Width", "%.0f", 0., MAX_PIXELS, 1., 0.);
  fillNumber(&FrameN[3], "HEIGHT", "Height", "%.0f", 0., MAX_PIXELS, 1., 0.);
  fillNumberVector(&FrameNP, FrameN, NARRAY(FrameN), mydev, "CCD_FRAME", "Frame", IMAGE_GROUP, IP_RW, 60, IPS_IDLE);
  
  fillNumber(&BinningN[0], "HOR_BIN", "X", "%0.f", 1., MAXHBIN, 1., 1.);
  fillNumber(&BinningN[1], "VER_BIN", "Y", "%0.f", 1., MAXVBIN, 1., 1.);
  fillNumberVector(&BinningNP, BinningN, NARRAY(BinningN), mydev, "CCD_BINNING", "Binning", IMAGE_GROUP, IP_RW, 60, IPS_IDLE);
    
  fillNumber(&ExposeTimeN[0], "DURATION", "Duration (s)", "%5.2f", 0., 36000., 0.5, 1.);
  fillNumberVector(&ExposeTimeNP, ExposeTimeN, NARRAY(ExposeTimeN), mydev, "CCD_EXPOSE_DURATION", "Expose", EXPOSE_GROUP, IP_RW, 60, IPS_IDLE);
  
  fillNumber(&TemperatureN[0], "TEMPERATURE", "Temperature", "%+06.2f", MIN_CCD_TEMP, MAX_CCD_TEMP, 0.2, 0.);
  fillNumberVector(&TemperatureNP, TemperatureN, NARRAY(TemperatureN), mydev, "CCD_TEMPERATURE", "Expose", EXPOSE_GROUP, IP_RW, 60, IPS_IDLE);
  
  strcpy(imageB.name, "CCD1");
  strcpy(imageB.label, "Feed");
  strcpy(imageB.format, "");
  imageB.blob    = 0;
  imageB.bloblen = 0;
  imageB.size    = 0;
  imageB.bvp     = 0;
  imageB.aux0    = 0;
  imageB.aux1    = 0;
  imageB.aux2    = 0;
  
  strcpy(imageBP.device, mydev);
  strcpy(imageBP.name, "Video");
  strcpy(imageBP.label, "Video");
  strcpy(imageBP.group, COMM_GROUP);
  strcpy(imageBP.timestamp, "");
  imageBP.p       = IP_RO;
  imageBP.timeout = 0;
  imageBP.s       = IPS_IDLE;
  imageBP.bp	  = &imageB;
  imageBP.nbp     = 1;
  imageBP.aux     = 0;
  
  //loadXMLModel();
}

bool ApogeeCam::loadXMLModel()
{
  LilXML *XMLParser = newLilXML();
  XMLEle *root = NULL, *camera = NULL;
  XMLAtt *modelName;
  FILE *modelSpecFile = NULL;
  char errmsg[1024];
  int ncams = 0;
  
  //IDLog("Top dir is "TOP_DATADIR, NULL);
  modelSpecFile = fopen(TOP_DATADIR"/apogee_caminfo.xml", "r");
  //modelSpecFile = fopen("/opt/trinity/share/apps/kstars/apogee_caminfo.xml", "r");
  if (modelSpecFile == NULL) 
  {
    IDLog("Error: Unable to open file apogee_caminfo.xml\n");
    IDMessage(mydev, "Error: Unable to open file apogee_caminfo.xml");
    return false;
  }
  
  root = readXMLFile(modelSpecFile, XMLParser, errmsg);
  if (root == NULL)
  {
    IDLog("Error: Unable to process apogee_caminfo.xml. %s\n", errmsg);
    IDMessage(mydev, "Error: Unable to process apogee_caminfo.xml. %s\n", errmsg);
    fclose(modelSpecFile);
    delLilXML(XMLParser);
    return false;
  }
    
  for (camera = nextXMLEle (root, 1); camera != NULL; camera = nextXMLEle (root, 0))
  {
    modelName = findXMLAtt(camera, "model");
    if (modelName == NULL) 
      continue;
    
    ApogeeModelS = (ApogeeModelS == NULL) ? (ISwitch *) malloc (sizeof(ISwitch))
                                          : (ISwitch *) realloc(ApogeeModelS, sizeof(ISwitch) * (ncams + 1));
    
    snprintf(ApogeeModelS[ncams].name, MAXINDINAME, "Model%d", ncams);
    strcpy(ApogeeModelS[ncams].label, valuXMLAtt(modelName));
    ApogeeModelS[ncams].s = (ncams == 0) ? ISS_ON : ISS_OFF;
    ApogeeModelS[ncams].svp = NULL;
    ApogeeModelS[ncams].aux = NULL;
    
    ncams++;
  }
  
  fclose(modelSpecFile);
  delLilXML(XMLParser);
  
  if (ncams > 0)
  {
  	fillSwitchVector(&ApogeeModelSP, ApogeeModelS, ncams, mydev, "Model", "", COMM_GROUP, IP_RW, ISR_1OFMANY, 60, IPS_IDLE);
	return true;
  }
  
  return false;
  
}

void ApogeeCam::ISGetProperties(const char */*dev*/)
{
  

  /* COMM_GROUP */
  IDDefSwitch(&PowerSP, NULL);
  if (loadXMLModel())
    IDDefSwitch(&ApogeeModelSP, NULL);
  else
    IDMessage(mydev, "Error: Unable to read camera specifications. Driver is disabled.");
  IDDefBLOB(&imageBP, NULL);

  /* Expose */
  IDDefSwitch(&FrameTypeSP, NULL);  
  IDDefNumber(&ExposeTimeNP, NULL);
  IDDefNumber(&TemperatureNP, NULL);
  
  /* Image Group */
  IDDefNumber(&FrameNP, NULL);
  IDDefNumber(&BinningNP, NULL);

  IDLog("Apogee Driver Debug Enabled\n");
  
}

void ApogeeCam::ISNewSwitch (const char */*dev*/, const char *name, ISState *states, char *names[], int n)
{
  
	/* Connection */
	if (!strcmp (name, PowerSP.name))
	{
  		IUResetSwitches(&PowerSP);
  		IUUpdateSwitches(&PowerSP, states, names, n);
  		connectCCD();
  		return;
	}
	
     	/* Frame Type */
     	if (!strcmp(FrameTypeSP.name, name))
	{
  		if (checkPowerS(&FrameTypeSP))
    		return;
		
		IUResetSwitches(&FrameTypeSP);
		IUUpdateSwitches(&FrameTypeSP, states, names, n);
		FrameTypeSP.s = IPS_OK;
		IDSetSwitch(&FrameTypeSP, NULL);
		
		return;
	}
     
     	/* Apogee Model */
     	if (!strcmp(ApogeeModelSP.name, name))
	{
  		IUResetSwitches(&ApogeeModelSP);
  		IUUpdateSwitches(&ApogeeModelSP, states, names, n);
		ApogeeModelSP.s = IPS_OK;
		IDSetSwitch(&ApogeeModelSP, NULL);
  		return;
	}
	
}

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

void ApogeeCam::ISNewNumber (const char */*dev*/, const char *name, double values[], char *names[], int n)
{
    /* Exposure time */
    if (!strcmp (ExposeTimeNP.name, name))
    {
       if (checkPowerN(&ExposeTimeNP))
         return;

       if (ExposeTimeNP.s == IPS_BUSY)
       {
	  cam->Reset();
	  ExposeTimeNP.s = IPS_IDLE;
	  ExposeTimeN[0].value = 0;

	  IDSetNumber(&ExposeTimeNP, "Exposure cancelled.");
	  IDLog("Exposure Cancelled.\n");
	  return;
        }
    
       ExposeTimeNP.s = IPS_IDLE;
       
       IUUpdateNumbers(&ExposeTimeNP, values, names, n);
       
      IDLog("Exposure Time is: %g\n", ExposeTimeN[0].value);
      
      handleExposure(NULL);
      return;
    } 
    
  if (!strcmp(TemperatureNP.name, name))
  {
    if (checkPowerN(&TemperatureNP))
      return;
      
    TemperatureNP.s = IPS_IDLE;
    
    if (values[0] < MIN_CCD_TEMP || values[0] > MAX_CCD_TEMP)
    {
      IDSetNumber(&TemperatureNP, "Error: valid range of temperature is from %d to %d", MIN_CCD_TEMP, MAX_CCD_TEMP);
      return;
    }
    
    targetTemp = values[0];
    cam->write_CoolerMode(0);
    cam->write_CoolerMode(1);
    cam->write_CoolerSetPoint(targetTemp);
    
    TemperatureNP.s = IPS_BUSY;
    
    IDSetNumber(&TemperatureNP, "Setting CCD temperature to %+06.2f C", values[0]);
    IDLog("Setting CCD temperature to %+06.2f C\n", values[0]);
    return;
   }
   
   if (!strcmp(FrameNP.name, name))
   {
     if (checkPowerN(&FrameNP))
      return;
      
     FrameNP.s = IPS_OK;
     IUUpdateNumbers(&FrameNP, values, names, n);
     
     cam->m_StartX = (int) FrameN[0].value;
     cam->m_StartY = (int) FrameN[1].value;
     cam->m_NumX   = (int) FrameN[2].value;
     cam->m_NumY   = (int) FrameN[3].value;
     IDSetNumber(&FrameNP, NULL);
      
   } /* end FrameNP */
      
    
   if (!strcmp(BinningNP.name, name))
   {
     if (checkPowerN(&BinningNP))
       return;
       
     
     BinningNP.s = IPS_OK;
     IUUpdateNumbers(&BinningNP, values, names, n);
     
     cam->m_BinX = (int) BinningN[0].value;
     cam->m_BinY = (int) BinningN[1].value;
     
     IDLog("Binning is: %.0f x %.0f\n", BinningN[0].value, BinningN[1].value);
     return;
   }
}


void ApogeeCam::ISPoll()
{
  static int mtc=5;
  int readStatus=0;
  double ccdTemp;

  if (!isCCDConnected())
   return;

  switch (ExposeTimeNP.s)
  {
    case IPS_IDLE:
    case IPS_OK:
      break;
	    
    case IPS_BUSY:
      
      readStatus = cam->read_Status();
      if (readStatus < 0)
      {
	IDLog("Error in exposure! Read status: %d\n", readStatus);
	ExposeTimeNP.s = IPS_ALERT;
	ExposeTimeN[0].value = 0;
	IDSetNumber(&ExposeTimeNP, "Error in exposure procedure. Read states: %d", readStatus);
	return;
      }
      else if (readStatus == Camera_Status_ImageReady)
      {
	ExposeTimeN[0].value = 0;
	ExposeTimeNP.s = IPS_OK;
	IDSetNumber(&ExposeTimeNP, "Exposure done, downloading image...");
	IDLog("Exposure done, downloading image...\n");
	/* grab and save image */
	grabImage();
	return;
      }
      
      ExposeTimeN[0].value --;
      IDSetNumber(&ExposeTimeNP, NULL);
      break;
	    
	case IPS_ALERT:
	    break;
    }
	 
    switch (TemperatureNP.s)
    {
	   case IPS_IDLE:
	   case IPS_OK:
	     mtc--;
	     
	     if (mtc == 0)
	     {
	       TemperatureN[0].value = cam->read_Temperature();
	       IDSetNumber(&TemperatureNP, NULL);
	       mtc = 5;
	     }
	     break;
	     
	   case IPS_BUSY:
	   
	     ccdTemp = cam->read_Temperature();
	     
	     if (fabs(targetTemp - ccdTemp) <= TEMP_THRESHOLD)
	       TemperatureNP.s = IPS_OK;
	     	       
	      mtc = 1;
              TemperatureN[0].value = ccdTemp;
	      IDSetNumber(&TemperatureNP, NULL);
	      break;
	      
	    case IPS_ALERT:
	     break;
	  }
  	  
}

/* Downloads the image from the CCD row by row and store them
   in a raw file.
 N.B. No processing is done on the image */
void ApogeeCam::grabImage()
{
  
  long err;
  int img_size, fd;
  char errmsg[1024];
  char filename[] = "/tmp/fitsXXXXXX";

   IDLog("In grab Image\n");
  
   if ((fd = mkstemp(filename)) < 0)
   { 
    IDMessage(mydev, "Error making temporary filename.");
    IDLog("Error making temporary filename.\n");
    return;
   }
   close(fd);
     
   img_size = APGFrame.width * APGFrame.height * sizeof(unsigned short);
   
   IDLog("Allocating memory buffer. Width: %d - Height: %d\n", APGFrame.width, APGFrame.height);

   APGFrame.img = (unsigned short *) malloc (img_size);
   
  if (APGFrame.img == NULL)
  {
    IDMessage(mydev, "Not enough memory to store image.");
    IDLog("Not enough memory to store image.\n");
    return;
  }
  
  IDLog("Getting frame buffer from camera...\n");
  if (!cam->GetImage( APGFrame.img , APGFrame.width, APGFrame.height ))
  {
       free(APGFrame.img);
       IDMessage(mydev, "GetImage() failed.");
       IDLog("GetImage() failed.");
       return;
  }

  IDLog("Done with getting frame buffer, writing FITS file\n");
  
   err = writeFITS(filename, errmsg);
   
   if (err)
   {
       free(APGFrame.img);
       IDMessage(mydev, errmsg, NULL);
       return;
   }
   
  free(APGFrame.img);

  IDLog("All good, returning\n");
   
}

int ApogeeCam::writeFITS(char *filename, char errmsg[])
{
  
  FITS_FILE* ofp;
  int bpp, bpsl, width, height;
  long nbytes;
  FITS_HDU_LIST *hdu;
  
  IDLog("in write FITS, opening filename %s\n", filename);

  ofp = fits_open (filename, "w");
  if (!ofp)
  {
    sprintf(errmsg, "Error: cannot open file for writing.");
    return (-1);
  }
  
  width  = APGFrame.width;
  height = APGFrame.height;
  bpp    = sizeof(unsigned short); /* Bytes per Pixel */
  bpsl   = bpp * APGFrame.width;    /* Bytes per Line */
  nbytes = 0;
  
  IDLog("Creating FITS header\n");
  hdu = create_fits_header (ofp, width, height, bpp);
  if (hdu == NULL)
  {
     sprintf(errmsg, "Error: creating FITS header failed.");
     return (-1);
  }
  if (fits_write_header (ofp, hdu) < 0)
  {
    sprintf(errmsg, "Error: writing to FITS header failed.");
    return (-1);
  }
  
  IDLog("Converting to BIG Endian\n");
  for (int i=0; i < height; i++)
    for (int j=0 ; j < width; j++)
      APGFrame.img[width * i + j] = getBigEndian( (APGFrame.img[width * i + j]) );
  
  IDLog("Writing frame to disk\n");
  for (int i= 0; i < height  ; i++)
  {
    fwrite(APGFrame.img + (i * width), 2, width, ofp->fp);
    nbytes += bpsl;
  }
  
  IDLog("Calculating nbytes\n");
  nbytes = nbytes % FITS_RECORD_SIZE;
  if (nbytes)
  {
    while (nbytes++ < FITS_RECORD_SIZE)
      putc (0, ofp->fp);
  }
  
  if (ferror (ofp->fp))
  {
    sprintf(errmsg, "Error: write error occured");
    return (-1);
  }
 
 IDLog("Closing ofp\n");
 fits_close (ofp);      
 
  /* Success */
 ExposeTimeNP.s = IPS_OK;
 IDSetNumber(&ExposeTimeNP, NULL);
 IDLog("Loading FITS image...\n");
 
 IDLog("Uploading filename\n");
 uploadFile(filename);
 IDLog("Uploading done, returning\n");

 return 0;

}

void ApogeeCam::uploadFile(char * filename)
{

   FILE * fitsFile;
   unsigned char *fitsData, *compressedData;
   int r=0;
   unsigned int i =0, nr = 0;
   uLongf compressedBytes=0;
   uLong  totalBytes;
   struct stat stat_p; 

   IDLog("in upload file, will stat file now\n");
 
   if ( -1 ==  stat (filename, &stat_p))
   { 
     IDLog(" Error occoured attempting to stat %s\n", filename); 
     return; 
   }
   
   totalBytes     = stat_p.st_size;
   fitsData       = (unsigned char *) malloc (sizeof(unsigned char) * totalBytes);
   compressedData = (unsigned char *) malloc (sizeof(unsigned char) * totalBytes + totalBytes / 64 + 16 + 3);
   
   if (fitsData == NULL || compressedData == NULL)
   {
     IDLog("Error! low memory. Unable to initialize fits buffers.\n");
     return;
   }
   
   IDLog("opening file\n");
   fitsFile = fopen(filename, "r");
   
   if (fitsFile == NULL)
    return;
   
   IDLog("Reading file from disk\n");
   /* #1 Read file from disk */ 
   for (i=0; i < totalBytes; i+= nr)
   {
      nr = fread(fitsData + i, 1, totalBytes - i, fitsFile);
     
     if (nr <= 0)
     {
        IDLog("Error reading temporary FITS file.\n");
        return;
     }
   }
   
   compressedBytes = sizeof(char) * totalBytes + totalBytes / 64 + 16 + 3;
     
   IDLog("Compressing data\n");
   /* #2 Compress it */
   r = compress2(compressedData, &compressedBytes, fitsData, totalBytes, 9);
   if (r != Z_OK)
   {
 	/* this should NEVER happen */
 	IDLog("internal error - compression failed: %d\n", r);
	return;
   }
   
   IDLog("Sending blob. bloblen %ld - size %ld\n", compressedBytes, totalBytes);

   /* #3 Send it */
   imageB.blob = compressedData;
   imageB.bloblen = compressedBytes;
   imageB.size = totalBytes;
   strcpy(imageB.format, ".fits.z");
   imageBP.s = IPS_OK;
   IDSetBLOB (&imageBP, NULL);
   
   free (fitsData);   
   free (compressedData);
   
}

/* Initiates the exposure procedure */
void ApogeeCam::handleExposure(void */*p*/)
{
  
  int curFrame = getOnSwitch(&FrameTypeSP);
  
  switch (curFrame)
  {
    /* Light frame */ 
   case LIGHT_FRAME:
   if (!cam->Expose( (int) ExposeTimeN[0].value, true ))
   {  
     ExposeTimeNP.s = IPS_IDLE;
     IDSetNumber(&ExposeTimeNP, "Light Camera exposure failed.");
     IDLog("Light Camera exposure failed.\n");
     return;
   }
   break;
   
   /* BIAS frame is the same as DARK but with minimum period. i.e. readout from camera electronics.
   */
    case BIAS_FRAME:
      if (!cam->Expose( 0.05 , false ))
      {  
	ExposeTimeNP.s = IPS_IDLE;
	IDSetNumber(&ExposeTimeNP, "Bias Camera exposure failed.");
	IDLog("Bias Camera exposure failed.\n");
	return;
      }
      break;
      
      /* Dark frame */
    case DARK_FRAME:
      if (!cam->Expose( (int) ExposeTimeN[0].value , false ))
      {  
	ExposeTimeNP.s = IPS_IDLE;
	IDSetNumber(&ExposeTimeNP, "Dark Camera exposure failed.");
	IDLog("Dark Camera exposure failed.\n");
	return;
      }
      break;
      
    case FLAT_FRAME:
      if (!cam->Expose( (int) ExposeTimeN[0].value , true ))
      {  
	ExposeTimeNP.s = IPS_IDLE;
	IDSetNumber(&ExposeTimeNP, "Flat Camera exposure failed.");
	IDLog("Flat Camera exposure failed.\n");
	return;
      }
      break;
  }
      
  APGFrame.frameType	= curFrame;
  APGFrame.width	= (int) FrameN[2].value;
  APGFrame.height	= (int) FrameN[3].value;
  APGFrame.expose	= (int) ExposeTimeN[0].value;
  APGFrame.temperature  =       TemperatureN[0].value;
  APGFrame.binX         = (int) BinningN[0].value;
  APGFrame.binY         = (int) BinningN[1].value;
  
  ExposeTimeNP.s = IPS_BUSY;
		  
  IDSetNumber(&ExposeTimeNP, "Taking a %g seconds frame...", ExposeTimeN[0].value);
  IDLog("Taking a frame. Width: %d - Height: %d - expose %d - temperature %g - binX %d - binY %d\n", APGFrame.width, APGFrame.height, APGFrame.expose, APGFrame.temperature, APGFrame.binX, APGFrame.binY);
   
}

/* Retrieves basic data from the CCD upon connection like temperature, array size, firmware..etc */
void ApogeeCam::getBasicData()
{


  // Maximum resolution
  FrameN[2].max = cam->m_NumX;
  FrameN[3].max = cam->m_NumY;
  IUUpdateMinMax(&FrameNP);
  
  // Maximum Bin
  BinningN[0].max = cam->m_MaxBinX;
  BinningN[1].max = cam->m_MaxBinX;
  IUUpdateMinMax(&BinningNP);

  FrameN[0].value = 0;
  FrameN[1].value = 0;
  FrameN[2].min   = 0;
  FrameN[2].max   = cam->m_ImgColumns;
  FrameN[2].value = cam->m_ImgColumns;
  FrameN[3].min   = 0;
  FrameN[3].max   = cam->m_ImgRows;
  FrameN[3].value = cam->m_ImgRows;

  IUUpdateMinMax(&FrameNP);
  IDSetNumber(&FrameNP, NULL);
  
  // Current Temperature
  TemperatureN[0].value = cam->read_Temperature();
  IDSetNumber(&TemperatureNP, NULL);
  
}

int ApogeeCam::getOnSwitch(ISwitchVectorProperty *sp)
{
  int i=0;
 for (i=0; i < sp->nsp ; i++)
 {
   /*IDLog("Switch %s is %s\n", sp->sp[i].name, sp->sp[i].s == ISS_ON ? "On" : "Off");*/
     if (sp->sp[i].s == ISS_ON)
      return i;
 }

 return -1;
}

int ApogeeCam::checkPowerS(ISwitchVectorProperty *sp)
{
  if (PowerSP.s != IPS_OK)
  {
    if (!strcmp(sp->label, ""))
    	IDMessage (mydev, "Cannot change property %s while the CCD is offline.", sp->name);
    else
    	IDMessage (mydev, "Cannot change property %s while the CCD is offline.", sp->label);
	
    sp->s = IPS_IDLE;
    IDSetSwitch(sp, NULL);
    return -1;
  }

  return 0;
}

int ApogeeCam::checkPowerN(INumberVectorProperty *np)
{
  if (PowerSP.s != IPS_OK)
  {
     if (!strcmp(np->label, ""))
    	IDMessage (mydev, "Cannot change property %s while the CCD is offline.", np->name);
    else
    	IDMessage (mydev, "Cannot change property %s while the CCD is offline.", np->label);
    
    np->s = IPS_IDLE;
    IDSetNumber(np, NULL);
    return -1;
  }

  return 0;
}

int ApogeeCam::checkPowerT(ITextVectorProperty *tp)
{

  if (PowerSP.s != IPS_OK)
  {
    if (!strcmp(tp->label, ""))
    	IDMessage (mydev, "Cannot change property %s while the CCD is offline.", tp->name);
    else
    	IDMessage (mydev, "Cannot change property %s while the CCD is offline.", tp->label);
	
    tp->s = IPS_IDLE;
    IDSetText(tp, NULL);
    return -1;
  }

  return 0;

}

void ApogeeCam::connectCCD()
{
  
  /* USB by default {USB, SERIAL, PARALLEL, INET} */
  switch (PowerS[0].s)
  {
    case ISS_ON:
      if (initCamera())
      {
	/* Sucess! */
	PowerS[0].s = ISS_ON;
	PowerS[1].s = ISS_OFF;
	PowerSP.s = IPS_OK;
	IDSetSwitch(&PowerSP, "CCD is online. Retrieving basic data.");
	IDLog("CCD is online. Retrieving basic data.\n");
	getBasicData();
	
      }
      else
      {
      	PowerSP.s = IPS_IDLE;
      	PowerS[0].s = ISS_OFF;
      	PowerS[1].s = ISS_ON;
      	IDSetSwitch(&PowerSP, "Error: no cameras were detected.");
      	IDLog("Error: no cameras were detected.\n");
      	return;
      }
      
      break;
      
    case ISS_OFF:
      PowerS[0].s = ISS_OFF;
      PowerS[1].s = ISS_ON;
      PowerSP.s = IPS_IDLE;
      IDSetSwitch(&PowerSP, "CCD is offline.");
      break;
     }

}

bool ApogeeCam::initCamera()
{
  LilXML *XMLParser = newLilXML();
  XMLEle *root = NULL, *camera = NULL, *ele = NULL;
  XMLEle *system = NULL, *geometry = NULL, *temp = NULL, *ccd = NULL;
  XMLAtt *ap;
  FILE *spFile = NULL;
  char errmsg[1024];
  
  spFile = fopen(TOP_DATADIR"/apogee_caminfo.xml", "r");
  //spFile = fopen("/opt/trinity/share/apps/kstars/apogee_caminfo.xml", "r");
  if (spFile == NULL) 
  {
    IDLog("Error: Unable to open file apogee_caminfo.xml\n");
    IDMessage(mydev, "Error: Unable to open file apogee_caminfo.xml");
    return false;
  }
  
  root = readXMLFile(spFile, XMLParser, errmsg);
  if (root == NULL)
  {
    IDLog("Error: Unable to process apogee_caminfo.xml. %s\n", errmsg);
    IDMessage(mydev, "Error: Unable to process apogee_caminfo.xml. %s\n", errmsg);
    fclose(spFile);
    delLilXML(XMLParser);
    return false;
  }
  
  fclose(spFile);
  
  // Let's locate which camera to load the configuration for  
  camera = findXMLEle(root, "Apogee_Camera");
  
  if (camera == NULL)
  {
    IDLog("Error: Unable to find Apogee_Camera element.\n");
    IDMessage(mydev, "Error: Unable to find Apogee_Camera element.");
    delLilXML(XMLParser);
    return false;
  }
   
  IDLog("Looking for %s - len %d\n", ApogeeModelS[getOnSwitch(&ApogeeModelSP)].label, strlen(ApogeeModelS[getOnSwitch(&ApogeeModelSP)].label));
  
  ap = findXMLAtt(camera, "model");
  if (!ap)
  {
    IDLog("Error: Unable to find attribute model.\n");
    IDMessage(mydev, "Error: Unable to find attribute model.");
    return false;
  }
  
  if (strcmp(valuXMLAtt(ap), ApogeeModelS[getOnSwitch(&ApogeeModelSP)].label))
  {
    IDLog("Camera %s not found in XML file\n", ApogeeModelS[getOnSwitch(&ApogeeModelSP)].label);
    IDMessage(mydev, "Camera %s not found in XML file\n", ApogeeModelS[getOnSwitch(&ApogeeModelSP)].label);
    delLilXML(XMLParser);
    return false;
  }
  
  // Let's get the subsections now
  system   = findXMLEle(camera, "System");
  geometry = findXMLEle(camera, "Geometry");
  temp     = findXMLEle(camera, "Temp");
  ccd      = findXMLEle(camera, "CCD");
  
  if (system == NULL)
  {
    IDLog("Error: Unable to find System element in camera.\n");
    IDMessage(mydev, "Error: Unable to find System element in camera.");
    delLilXML(XMLParser);
    return false;
  }
  
  if (geometry == NULL)
  {
    IDLog("Error: Unable to find Geometry element in camera.\n");
    IDMessage(mydev, "Error: Unable to find Geometry element in camera.");
    delLilXML(XMLParser);
    return false;
  }
  
  if (temp == NULL)
  {
    IDLog("Error: Unable to find Temp element in camera.\n");
    IDMessage(mydev, "Error: Unable to find Temp element in camera.");
    delLilXML(XMLParser);
    return false;
  }
  
  if (ccd == NULL)
  {
    IDLog("Error: Unable to find CCD element in camera.\n");
    IDMessage(mydev, "Error: Unable to find CCD element in camera.");
    delLilXML(XMLParser);
    return false;
  }
  
  cam = new CCameraIO();
  
  if (cam == NULL)
  {
    IDLog("Error: Failed to create CCameraIO object.\n");
    IDMessage(mydev, "Error: Failed to create CCameraIO object.");
    delLilXML(XMLParser);
    return false; 
  }
  
  int bAddr = 0x378;
  int val   = 0;
  
  bAddr = hextoi(valuXMLAtt(findXMLAtt(system, "Base"))) & 0xFFF;
  
  // Rows
  ap = findXMLAtt(geometry, "Rows");
  if (!ap)
  {
    IDLog("Error: Unable to find attribute Rows.\n");
    IDMessage(mydev, "Error: Unable to find attribute Rows.");
    delLilXML(XMLParser);
    return false;
  }
  
  cam->m_Rows = hextoi(valuXMLAtt(ap));
  
  // Columns
  ap = findXMLAtt(geometry, "Columns");
  if (!ap)
  {
    IDLog("Error: Unable to find attribute Columns.\n");
    IDMessage(mydev, "Error: Unable to find attribute Columns.");
    delLilXML(XMLParser);
    return false;
  }
  
  cam->m_Columns = hextoi(valuXMLAtt(ap));
  
  // pp_repeat
  ele = findXMLEle(system, "PP_Repeat");
  if (!ele)
  {
    IDLog("Error: Unable to find element PP_Repeat.\n");
    IDMessage(mydev, "Error: Unable to find element PP_Repeat.");
    delLilXML(XMLParser);
    return false;
  }
  
  val = hextoi(pcdataXMLEle(ele));
  if (val > 0 && val <= 1000)
      cam->m_PPRepeat = val;

  // Initiate driver
  if (!cam->InitDriver(0))
  {
    IDLog("Error: Failed to Init Driver.\n");
    IDMessage(mydev, "Error: Failed to Init Driver.");
    delLilXML(XMLParser);
    return false;
  }

  cam->Reset();
  
  // Cable length
  ele = findXMLEle(system, "Cable");
  if (!ele)
  {
    IDLog("Error: Unable to find element Cable.\n");
    IDMessage(mydev, "Error: Unable to find element Cable.");
    delLilXML(XMLParser);
    return false;
 }
 
 if (!strcmp("Long", pcdataXMLEle(ele)))
 {
   cam->write_LongCable( true );
   IDLog("Cable is long\n");
 }
 else
 {
   cam->write_LongCable( false );
   IDLog("Cable is short\n");
 }
 
 
 if (!cam->read_Present())
 {
   IDLog("Error: read_Present() failed.\n");
   IDMessage(mydev, "Error: read_Present() failed.");
   delLilXML(XMLParser);
   return false;
}
 
 // Default settings
 cam->write_UseTrigger( false );
 cam->write_ForceShutterOpen( false );
 
 // High priority
 ele = findXMLEle(system, "High_Priority");
 if (ele)
 {
   if (!strcmp(pcdataXMLEle(ele), "True"))
     cam->m_HighPriority = true;
   else
     cam->m_HighPriority = false;
 }
 
 // Data bits
 ele = findXMLEle(system, "Data_Bits");
 if (ele)
 {
   val = hextoi(pcdataXMLEle(ele));
   if (val >= 8 && val <= 18) cam->m_DataBits = val;
 }
 
 // Sensor
 ele = findXMLEle(system, "Sensor");
 if (ele)
 {
   if (!strcmp(pcdataXMLEle(ele), "CCD"))
     cam->m_SensorType = Camera_SensorType_CCD;
   else
     cam->m_SensorType = Camera_SensorType_CMOS;
 }
 
 // Mode
 ele = findXMLEle(system, "Mode");
 if (ele)
 {
   val = hextoi(pcdataXMLEle(ele)) & 0xF;
   cam->write_Mode( val );
   IDLog("Mode %d\n", val);
 }
 else
   cam->write_Mode( 0 );
 
 // Test
 ele = findXMLEle(system, "Test");
 if (ele)
 {
   val = hextoi(pcdataXMLEle(ele)) & 0xF;
   cam->write_TestBits( val );
   IDLog("Test bits %d\n", val);
 }
 else
   cam->write_TestBits( 0 );
 
 // Test2
 ele = findXMLEle(system, "Test2");
 if (ele)
 {
   val = hextoi(pcdataXMLEle(ele)) & 0xF;
   cam->write_Test2Bits( val );
   IDLog("Test 2 bits %d\n", val);
 }
 else
   cam->write_Test2Bits( 0 );
 
 // Shutter Speed
 ele = findXMLEle(system, "Shutter_Speed");
 if (ele)
 {
   cam->m_MaxExposure = 10485.75;
   
   if (!strcmp(pcdataXMLEle(ele), "Normal"))
   {
     cam->m_FastShutter = false;
     cam->m_MinExposure = 0.01;
     IDLog("Shutter speed normal\n");
   }
   else if ( (!strcmp(pcdataXMLEle(ele), "Fast")) || (!strcmp(pcdataXMLEle(ele), "Dual")) )
   {
     cam->m_FastShutter = true;
     cam->m_MinExposure = 0.001;
     IDLog("Shutter speed fast\n");
   }
 }
 
 // Shutter Bits
 ele = findXMLEle(system, "Shutter_Bits");
 if (ele)
 {
   val = hextoi(pcdataXMLEle(ele));
   cam->m_FastShutterBits_Mode = val & 0x0F;
   cam->m_FastShutterBits_Test = ( val & 0xF0 ) >> 4;
   IDLog("Shutter bits %d\n", val);
 }
 
 // Max X Bin
 ele = findXMLEle(system, "MaxBinX");
 if (ele)
 {
   val = hextoi(pcdataXMLEle(ele));
   if (val >= 1 && val <= MAXHBIN) 
     cam->m_MaxBinX = val;
 }
 
 // Max Y Bin
 ele = findXMLEle(system, "MaxBinY");
 if (ele)
 {
   val = hextoi(pcdataXMLEle(ele));
   if (val >= 1 && val <= MAXVBIN) 
     cam->m_MaxBinY = val;
 }
 
 // Guider Relays
 ele = findXMLEle(system, "Guider_Relays");
 if (ele)
 {
   if (!strcmp(pcdataXMLEle(ele), "True"))
     cam->m_GuiderRelays = true;
   else
     cam->m_GuiderRelays = false;
 }
 
 // Timeout
 ele = findXMLEle(system, "Timeout");
 if (ele)
 {
   double dval = atof(pcdataXMLEle(ele));
   if (dval >= 0.0 && dval <= 10000.0) cam->m_Timeout = dval;
 }
 
 // BIC
  ele = findXMLEle(geometry, "BIC");
  if (ele)
  {
    val = hextoi(pcdataXMLEle(ele));
    if (val >= 1 && val <= MAXCOLUMNS)
      cam->m_BIC = val;
  }
  
   // BIR
  ele = findXMLEle(geometry, "BIR");
  if (ele)
  {
    val = hextoi(pcdataXMLEle(ele));
    if (val >= 1 && val <= MAXROWS)
      cam->m_BIR = val;
  }
  
  // SKIPC
  ele = findXMLEle(geometry, "SKIPC");
  if (ele)
  {
    val = hextoi(pcdataXMLEle(ele));
    if (val >= 1 && val <= MAXCOLUMNS)
      cam->m_SkipC = val;
  }
  
  // SKIPR
  ele = findXMLEle(geometry, "SKIPR");
  if (ele)
  {
    val = hextoi(pcdataXMLEle(ele));
    if (val >= 1 && val <= MAXROWS)
      cam->m_SkipR = val;
  }
  
  // IMG COlS
  ele = findXMLEle(geometry, "ImgCols");
  if (ele)
  {
    val = hextoi(pcdataXMLEle(ele));
    if (val >= 1 && val <= MAXTOTALCOLUMNS)
      cam->m_ImgColumns = val;
  }
  else
    cam->m_ImgColumns = cam->m_Columns - cam->m_BIC - cam->m_SkipC;
  
  // IMG ROWS
  ele = findXMLEle(geometry, "ImgRows");
  if (ele)
  {
    val = hextoi(pcdataXMLEle(ele));
    if (val >= 1 && val <= MAXTOTALROWS)
      cam->m_ImgRows = val;
  }
  else
    cam->m_ImgRows = cam->m_Rows - cam->m_BIR - cam->m_SkipR;
  
  // Hor Flush
  ele = findXMLEle(geometry, "HFlush");
  if (ele)
  {
    val = hextoi(pcdataXMLEle(ele));
    if (val >= 1 && val <= MAXHBIN) 
      cam->m_HFlush = val;
  }
  
  // Ver Flush
  ele = findXMLEle(geometry, "VFlush");
  if (ele)
  {
    val = hextoi(pcdataXMLEle(ele));
    if (val >= 1 && val <= MAXVBIN) 
      cam->m_VFlush = val;
  }

  // Default to full frame
  cam->m_NumX = cam->m_ImgColumns;
  cam->m_NumY = cam->m_ImgRows;
  
  // Temp Control 
  ap = findXMLAtt(temp, "Control");
  if (ap)
  {
    if (!strcmp(valuXMLAtt(ap), "True"))
      cam->m_TempControl = true;
    else
      cam->m_TempControl = false;
  }
  
  // Calibration
  ap = findXMLAtt(temp, "Cal");
  if (ap)
  {
    val = hextoi(valuXMLAtt(ap));
    if (val >= 1 && val <= 255) 
      cam->m_TempCalibration = val;
  }
  
  // Scale
  ap = findXMLAtt(temp, "Scale");
  if (ap)
  {
    double dval = atof(valuXMLAtt(ap));
    if (dval >= 1.0 && dval <= 10.0) 
      cam->m_TempScale = dval;
  }
  
  // Target
  ap = findXMLAtt(temp, "Target");
  if (ap)
  {
    double dval = atof(valuXMLAtt(ap));
    if (dval >= -60.0 && dval <= 40.0) 
      cam->write_CoolerSetPoint( dval );
    else
      cam->write_CoolerSetPoint( -10.0 );		// Default   
    
    IDLog("Target: %g\n", dval);
  }
  
  // Sensor
  ap = findXMLAtt(ccd, "Sensor");
  if (ap)
  {
    strncpy (cam->m_Sensor, valuXMLAtt(ap), 255);
    IDLog("Sensor: %s\n", cam->m_Sensor);
  }
  
  // Color
  ele = findXMLEle(ccd, "Color");
  if (ele)
  {
    if (!strcmp(pcdataXMLEle(ele), "True"))
    {
      cam->m_Color = true;
      IDLog("Color: true\n");
    }
    else
    { 
      cam->m_Color = false;
      IDLog("Color: false\n");
    }
  }
  
  // Noise
  ele = findXMLEle(ccd, "Noise");
  if (ele)
    cam->m_Noise = atof( pcdataXMLEle(ele) );
  
  // Noise
  ele = findXMLEle(ccd, "Gain");
  if (ele)
    cam->m_Gain = atof( pcdataXMLEle(ele) );
  
  // Pixel X Size
  ele = findXMLEle(ccd, "PixelXSize");
  if (ele)
  {
    cam->m_PixelXSize = atof( pcdataXMLEle(ele) );
    IDLog("Pixel X Size: %g\n", cam->m_PixelXSize);
  }
  
  // Pixel Y Size
  ele = findXMLEle(ccd, "PixelYSize");
  if (ele)
  {
    cam->m_PixelYSize = atof( pcdataXMLEle(ele) );
    IDLog("Pixel Y Size: %g\n", cam->m_PixelYSize);
  }
  
  // Log all values
  IDLog("Cam Row: %d - Cam Cols: %d - PP_Repeat %d\n",cam->m_Rows,  cam->m_Columns, cam->m_PPRepeat);
  IDLog("High_Priority %s - Data_Bits %d - Sensor %s\n", cam->m_HighPriority ? "true" : "false", cam->m_DataBits, (cam->m_SensorType == Camera_SensorType_CCD) ? "CCD" : "CMOS");
  IDLog("Max X Bin: %d - Max Y Bin: %d - Guider Relays: %s\n", cam->m_MaxBinX, cam->m_MaxBinY, cam->m_GuiderRelays ? "true" : "false");
  IDLog("BIC: %d - BIR: %d - SKIPC: %d - SKIPR: %d - ImgRows: %d - ImgCols %d\n", cam->m_BIC, cam->m_BIR, cam->m_SkipC, cam->m_SkipR, cam->m_ImgRows, cam->m_ImgColumns);
  IDLog("HFlush: %d - VFlush: %d - Control: %s - Cal: %d - Scale: %g\n", cam->m_HFlush, cam->m_VFlush, cam->m_TempControl ? "true" : "false", cam->m_TempCalibration, cam->m_TempScale); 
  
  delLilXML(XMLParser);
  
  return true;
}

/* isCCDConnected: return 1 if we have a connection, 0 otherwise */
int ApogeeCam::isCCDConnected(void)
{
  return ((PowerS[0].s == ISS_ON) ? 1 : 0);
}

FITS_HDU_LIST * ApogeeCam::create_fits_header (FITS_FILE *ofp, uint width, uint height, uint bpp)
{
 
 FITS_HDU_LIST *hdulist;
 
 char temp_s[FITS_CARD_SIZE], expose_s[FITS_CARD_SIZE], binning_s[FITS_CARD_SIZE], frame_s[FITS_CARD_SIZE], pixel_s[FITS_CARD_SIZE];
 char obsDate[FITS_CARD_SIZE];
 
 snprintf(obsDate, FITS_CARD_SIZE, "DATE-OBS= '%s' /Observation Date UTC", timestamp());
 
 hdulist = fits_add_hdu (ofp);
 if (hdulist == NULL) return (NULL);

 hdulist->used.simple = 1;
 hdulist->bitpix = 16;
 hdulist->naxis = 2;
 hdulist->naxisn[0] = width;
 hdulist->naxisn[1] = height;
 hdulist->naxisn[2] = bpp;
 hdulist->used.datamin = 1;
 hdulist->datamin = min();
 hdulist->used.datamax = 1;
 hdulist->datamax = max();
 hdulist->used.bzero = 1;
 hdulist->bzero = 0.0;
 hdulist->used.bscale = 1;
 hdulist->bscale = 1.0;
 
 snprintf(temp_s, FITS_CARD_SIZE, "CCD-TEMP= %g / degrees celcius", APGFrame.temperature);
 snprintf(expose_s, FITS_CARD_SIZE, "EXPOSURE= %d / milliseconds", APGFrame.expose);
 snprintf(binning_s, FITS_CARD_SIZE, "BINNING = '(%d x %d)'", APGFrame.binX, APGFrame.binY);
 snprintf(pixel_s, FITS_CARD_SIZE, "PIX-SIZ = '%0.f x %0.f microns square'", cam->m_PixelXSize, cam->m_PixelYSize);
 switch (APGFrame.frameType)
  {
    case LIGHT_FRAME:
      	strcpy(frame_s, "FRAME   = 'Light'");
	break;
    case BIAS_FRAME:
        strcpy(frame_s, "FRAME   = 'Bias'");
	break;
    case FLAT_FRAME:
        strcpy(frame_s, "FRAME   = 'Flat Field'");
	break;
    case DARK_FRAME:
        strcpy(frame_s, "FRAME   = 'Dark'");
	break;
  }
 
 fits_add_card (hdulist, frame_s);   
 fits_add_card (hdulist, temp_s);
 fits_add_card (hdulist, expose_s);
 fits_add_card (hdulist, pixel_s);
 fits_add_card (hdulist, "INSTRUME= 'Apogee CCD'");
 fits_add_card (hdulist, obsDate);
  
 return (hdulist);
}

// Convert a string to a decimal or hexadecimal integer
// Code taken from Dave Mills Apogee driver
unsigned short ApogeeCam::hextoi(char *instr)
{
  unsigned short val, tot = 0;
  bool IsHEX = false;

  long n = strlen( instr );
  if ( n > 1 )
  {	// Look for hex format e.g. 8Fh, A3H or 0x5D
    if ( instr[ n - 1 ] == 'h' || instr[ n - 1 ] == 'H' )
      IsHEX = true;
    else if ( *instr == '0' && *(instr+1) == 'x' )
    {
      IsHEX = true;
      instr += 2;
    }
  }

  if ( IsHEX )
  {
    while (instr && *instr && isxdigit(*instr))
    {
      val = *instr++ - '0';
      if (9 < val)
	val -= 7;
      tot <<= 4;
      tot |= (val & 0x0f);
    }
  }
  else
    tot = atoi( instr );

  return tot;
}

double ApogeeCam::min()
{
  double lmin = APGFrame.img[0];
  int ind=0, i, j;
  
  for (i= 0; i < APGFrame.height ; i++)
    for (j= 0; j < APGFrame.width; j++)
    {
       ind = (i * APGFrame.width) + j;
       if (APGFrame.img[ind] < lmin) lmin = APGFrame.img[ind];
    }
    
    return lmin;
}

double ApogeeCam::max()
{
  double lmax = APGFrame.img[0];
  int ind=0, i, j;
  
   for (i= 0; i < APGFrame.height ; i++)
    for (j= 0; j < APGFrame.width; j++)
    {
      ind = (i * APGFrame.width) + j;
       if (APGFrame.img[ind] > lmax) lmax = APGFrame.img[ind];
    }
    
    return lmax;
}

