/*-----------------------------------------------------------------------------
 * This is a driver for the DELCam Neutron CCD camera. This driver has some 
 * limitations: 
 * - Due to the dark current of a CCD, only timer measurements 
 *   are useful. 
 * - If there is no beam, the measurement has to be aborted.
 * - While reading the CCD, the computer locks up.
 * - No intermediate results can be retrieved while data acquisition is
 *   active. This is due to the nature of the CCD readout: Each line is 
 *   swapped into a line buffer which then is sequentially read. This 
 *   may be a consequence of the fact that exposure just generates charges
 *   on the CCD.
 * - This driver requires a Linux kernel module to be loaded beforehand.
 * - The DELCam's paralell port connector MUST be connected to the 
 *   paralell port of the computer running the SICServer.
 * - This code works only on Linux, porting to other OS requires different 
 *   drivers.  
 * The DELCam uses a CCD which was originally developed as CCD for amateur 
 * astronomers. The name of the project is Audine. This driver uses the
 * audine driver code. Look in audinelib.h for a WWW pointer.
 * 
 * copyright: see file COPYRIGHT
 * 
 * Mark Koennecke, March 2007
 */
#include <errno.h>
#include <unistd.h>
#include <sics.h>
#include <countdriv.h>
#include <counter.h>
#include <HistMem.h>
#include <stringdict.h>
#include <HistDriv.i>
#include "audinelib.h"

/* This delay is not the real value, but approximated */
/* time in ms to activate amplifier before use */
#define AMPLIFIER_DELAY 1

/* Time in ms to activate shutter before/after exposure */
#define SHUTTER_DELAY 200
/*=================== DaDa Structure =========================*/
typedef struct {
  AUD_HANDLE aud_handle;
  int amplictrl;
  int amplion;
  int shutterctrl;
  int nClear;
  int running;
  int error;
  int schnarch;
  int width, height;
  pCounter count;
} Delcam, *pDelcam;
/*====================== codes =============================================*/
#define NOBEAM -120
#define FAULT  -121
/*-------------------------------------------------------------------
  Configures the HM from the options in pOpt and the HM data structure
  Returns 1 on success, 0 on failure
---------------------------------------------------------------------*/
static int DelcamConfigure(pHistDriver self, SConnection * pCon,
                           pStringDict pOpt, SicsInterp * pSics)
{
  pDelcam pPriv = NULL;
  char buffer[80];
  int status, w, h;

  pPriv = (pDelcam) self->pPriv;

  if (StringDictGet(pOpt, "amplictrl", buffer, 79) == 1) {
    pPriv->amplictrl = atoi(buffer);
  } else {
    pPriv->amplictrl = 0;
  }
  if (pPriv->amplictrl < 0 || pPriv->amplictrl > 8) {
    SCWrite(pCon, "ERROR: invalid amplictrl value, range 0 - 8", eError);
    pPriv->amplictrl = 0;
  }
  if (StringDictGet(pOpt, "amplion", buffer, 79) == 1) {
    pPriv->amplion = atoi(buffer);
  } else {
    pPriv->amplion = 1;
  }
  if(pPriv->amplion != 0 && pPriv->amplion != 1){
	    SCWrite(pCon, "ERROR: invalid amplion value, 0,1 allowed", eError);
	    pPriv->amplion = 1;
  }

  if (StringDictGet(pOpt, "shutterctrl", buffer, 79) == 1) {
    pPriv->shutterctrl = atoi(buffer);
  } else {
    pPriv->shutterctrl = 0;
  }
  if (StringDictGet(pOpt, "clear", buffer, 79) == 1) {
    pPriv->nClear = atoi(buffer);
  } else {
    pPriv->nClear = 1;
  }
  if (StringDictGet(pOpt, "schnarch", buffer, 79) == 1) {
    pPriv->schnarch = atoi(buffer);
  } else {
    pPriv->schnarch = 1;
  }
  if (StringDictGet(pOpt, "counter", buffer, 79) == 1) {
    pPriv->count = (pCounter) FindCommandData(pServ->pSics,
                                              buffer, "SingleCounter");
  }
  if (pPriv->count == NULL) {
    SCWrite(pCon,
            "ERROR: no slave counter configured: need it desperatly!",
            eError);
    return 0;
  }
  pPriv->running = 0;
  if (pPriv->aud_handle == NULL) {
    pPriv->aud_handle = aud_open();
    if (pPriv->aud_handle == NULL) {
      SCWrite(pCon, "ERROR: failed to open DELCam", eError);
      return 0;
    }
  }
  status = aud_geometry_get(pPriv->aud_handle, 0, 0, &w, &h, 0);
  if (status != 0) {
    snprintf(buffer, 80, "ERROR: errno %d while trying to read geometry",
             errno);
    SCWrite(pCon, buffer, eError);
    return 0;
  }
  sprintf(buffer, "%d", w);
  StringDictUpdate(self->pOption, "dim0", buffer);
  sprintf(buffer, "%d", h);
  StringDictUpdate(self->pOption, "dim1", buffer);
  configureHMdata(self->data, self->pOption, pCon);
  if (!resizeBuffer(self->data)) {
    SCWrite(pCon, "ERROR: out of memory allocating HMData for DELCam",
            eError);
    return 0;
  }
  pPriv->width = w;
  pPriv->height = h;

  return 1;
}

/*--------------------------------------------------------------------
 Start histogramming, Returns HWFault on failure, 1 on success
----------------------------------------------------------------------*/
static int DelcamStart(pHistDriver self, SConnection * pCon)
{
  pDelcam pPriv = NULL;
  int status, i;
  char buffer[80];

  pPriv = (pDelcam) self->pPriv;

  /*
   * configure counter
   */
  if (self->eCount != eTimer) {
    SCWrite(pCon, "WARNING: bad count mode switched to preset time",
            eWarning);
  }
  SetCounterMode(pPriv->count, eTimer);
  SetCounterPreset(pPriv->count, self->fCountPreset);

  /*
   * deal with amplifier 
   */
  if (pPriv->amplictrl |= 0) {
    status = aud_amplifier_ctrl_set(pPriv->aud_handle, pPriv->amplictrl);
    if (status != 0) {
      snprintf(buffer, 80, "ERROR: failed to set amplifier, errno = %d",
               errno);
      SCWrite(pCon, buffer, eError);
      pPriv->error = FAULT;
      return HWFault;
    }
  }
  status = aud_amplifier_set(pPriv->aud_handle, 0);
  if (status != 0) {
    snprintf(buffer, 80, "ERROR: failed to start amplifier, errno = %d",
             errno);
    SCWrite(pCon, buffer, eError);
    pPriv->error = FAULT;
    return HWFault;
  }
  usleep(AMPLIFIER_DELAY * 1000);

  /*
   * deal with shutter
   */
  if (pPriv->shutterctrl |= 0) {
    status = aud_shutter_ctrl_set(pPriv->aud_handle, pPriv->shutterctrl);
    if (status != 0) {
      snprintf(buffer, 80, "ERROR: failed to set shutterctrl, errno = %d",
               errno);
      SCWrite(pCon, buffer, eError);
      pPriv->error = FAULT;
      return HWFault;
    }
    status = aud_shutter_set(pPriv->aud_handle, 1);
    if (status != 0) {
      snprintf(buffer, 80, "ERROR: failed to start shutter, errno = %d",
               errno);
      SCWrite(pCon, buffer, eError);
      pPriv->error = FAULT;
      return HWFault;
    }
    usleep(SHUTTER_DELAY * 1000);
  }

  /*
   * clear
   */
  status = aud_clear(pPriv->aud_handle, pPriv->nClear);
  if (status != 0) {
    snprintf(buffer, 80, "ERROR: failed to clear CCD, errno = %d", errno);
    SCWrite(pCon, buffer, eError);
    pPriv->error = FAULT;
    return HWFault;
  }

  pPriv->running = 1;
  clearHMData(self->data);
  return pPriv->count->pDriv->Start(pPriv->count->pDriv);
}

/*--------------------------------------------------------------------
 Stops histogramming, Returns HWFault on failure, 1 on success
----------------------------------------------------------------------*/
static int DelcamHalt(pHistDriver self)
{
  pDelcam pPriv = NULL;

  pPriv = (pDelcam) self->pPriv;
  return pPriv->count->pDriv->Halt(pPriv->count->pDriv);
}

/*--------------------------------------------------------------------
 Checks histogramming status, Returns HWFault on failure, 
 HWIdle when finished, HWBusy when counting 
----------------------------------------------------------------------*/
static int DelcamCountStatus(pHistDriver self, SConnection * pCon)
{
  pDelcam pPriv = NULL;
  int status, color, imSize = 0, i;
  float val;
  char buffer[80];
  unsigned short *imData = NULL;
  char *shittyCompiler = NULL;

  pPriv = (pDelcam) self->pPriv;

  status = pPriv->count->pDriv->GetStatus(pPriv->count->pDriv, &val);
  if (status == HWNoBeam) {
    /*
       SCWrite(pCon,"WARNING: NO BEAM, during CCD measurement",eWarning);
       DelcamHalt(self);
       pPriv->error = NOBEAM;
       status = HWFault;
     */
    return HWBusy;
  }
  if (status != HWBusy && pPriv->running == 1) {
    pPriv->count->pDriv->ReadValues(pPriv->count->pDriv);
    pPriv->running = 0;
    /*
     * stop the CCD and transfer data
     */
    if (pPriv->shutterctrl |= 0) {
      status = aud_shutter_set(pPriv->aud_handle, 0);
      if (status != 0) {
        snprintf(buffer, 80, "ERROR: failed to close shutter, errno = %d",
                 errno);
        SCWrite(pCon, buffer, eError);
        pPriv->error = FAULT;
        status = HWFault;
      }
      usleep(SHUTTER_DELAY * 1000);
    }
    if(pPriv->amplion == 1){
    	status = aud_amplifier_set(pPriv->aud_handle, 1);
    	if (status != 0) {
    		snprintf(buffer, 80, "ERROR: failed to start amplifier, errno = %d",
    				errno);
    		SCWrite(pCon, buffer, eError);
    		pPriv->error = FAULT;
    		status = HWFault;
    	}
        usleep(AMPLIFIER_DELAY*1000);
    }


    usleep(pPriv->schnarch * 1000);

    SCWrite(pCon,
            "WARNING: starting readout, computer freeze for ~15 sec is normal during readout",
            eWarning);

    status = aud_image_read(pPriv->aud_handle, &shittyCompiler,
                            &imSize, &pPriv->width,
                            &pPriv->height, &color);
    imData = (unsigned short *) shittyCompiler;
    if (status != 0) {
      snprintf(buffer, 80, "ERROR: failed to read DELCam with errno = %d",
               errno);
      SCWrite(pCon, buffer, eError);
      pPriv->error = FAULT;
      status = HWFault;
    }
    if (imData != NULL) {
      for (i = 0; i < pPriv->width * pPriv->height; i++) {
        self->data->localBuffer[i] = (int) imData[i];
      }
      free(imData);
    }
    SCWrite(pCon,
             "WARNING: CCD read",eWarning);
  }

  return status;
}

/*--------------------------------------------------------------------
 Get info on error after last HWFault, returns 1 always.
 Puts an int error code into *code and errLen chars of
 error description into error
----------------------------------------------------------------------*/
static int DelcamGetError(pHistDriver self, int *code,
                          char *error, int errLen)
{
  pDelcam pPriv = NULL;

  pPriv = (pDelcam) self->pPriv;
  switch (pPriv->error) {
  case FAULT:
    snprintf(error, errLen, "ERROR: DELcam fault");
    *code = FAULT;
    break;
  case NOBEAM:
    snprintf(error, errLen, "ERROR: ABORT for beam interruption");
    *code = FAULT;
    break;
  default:
    return pPriv->count->pDriv->GetError(pPriv->count->pDriv,
                                         code, error, errLen);
  }
  return 0;
}

/*--------------------------------------------------------------------
 Try to fix the  HM error in code. Returns COREDO when the last
 operation needs to be redone, COTERM when the error cannot be
  fixed.
----------------------------------------------------------------------*/
static int DelcamFixIt(pHistDriver self, int code)
{
  pDelcam pPriv = NULL;

  pPriv = (pDelcam) self->pPriv;
  switch (code) {
  case FAULT:
  case NOBEAM:
    return COTERM;
    break;
  default:
    return pPriv->count->pDriv->TryAndFixIt(pPriv->count->pDriv, code);
    break;
  }
  return COTERM;
}

/*--------------------------------------------------------------------
 GetData reads updates the internal cache of monitor values
 from the hardware, Returns 1 or HWFault
----------------------------------------------------------------------*/
static int DelcamGetData(pHistDriver self, SConnection * pCon)
{
  pDelcam pPriv = NULL;

  pPriv = (pDelcam) self->pPriv;
  return 1;
}

/*--------------------------------------------------------------------
 GetMonitor reads the monitor value i. Returns either the monitor 
 value or -9999 if no such monitor exists or an error occurred
----------------------------------------------------------------------*/
static long DelcamGetMonitor(pHistDriver self, int i, SConnection * pCon)
{
  pDelcam pPriv = NULL;

  pPriv = (pDelcam) self->pPriv;
  return GetMonitor(pPriv->count, i, pCon);
}

/*--------------------------------------------------------------------
 GetTime reads the total counting time. Returns either the  
 value or -9999.99 if no such value exists or an error occurred
----------------------------------------------------------------------*/
static float DelcamGetTime(pHistDriver self, SConnection * pCon)
{
  pDelcam pPriv = NULL;

  pPriv = (pDelcam) self->pPriv;
  return GetCountTime(pPriv->count, pCon);
}

/*--------------------------------------------------------------------
 Pause histogramming, Returns HWFault on failure, 1 on success
----------------------------------------------------------------------*/
static int DelcamPause(pHistDriver self, SConnection * pCon)
{
  pDelcam pPriv = NULL;

  pPriv = (pDelcam) self->pPriv;
  SCWrite(pCon, "WARNING: pausing pointless for CCD, IGNORED!", eWarning);
  return 1;
}

/*--------------------------------------------------------------------
 Continue histogramming, Returns HWFault on failure, 1 on success
----------------------------------------------------------------------*/
static int DelcamContinue(pHistDriver self, SConnection * pCon)
{
  pDelcam pPriv = NULL;

  pPriv = (pDelcam) self->pPriv;
  SCWrite(pCon, "WARNING: pausing pointless for CCD, IGNORED!", eWarning);
  return 1;
}

/*--------------------------------------------------------------------
 Free the data associated with the private data structure of the driver
----------------------------------------------------------------------*/
static int DelcamFree(pHistDriver self)
{
  pDelcam pPriv = NULL;

  pPriv = (pDelcam) self->pPriv;
  aud_close(pPriv->aud_handle);
  free(pPriv);
  return 1;
}

/*--------------------------------------------------------------------
 Set The HM data or a subset of it. Returns HWFault or 1
----------------------------------------------------------------------*/
static int DelcamSetHistogram(pHistDriver self,
                              SConnection * pCon,
                              int i, int iStart, int iEnd, HistInt * pData)
{
  pDelcam pPriv = NULL;
  int j;

  pPriv = (pDelcam) self->pPriv;
  SCWrite(pCon, "WARNING: driver does not support setting HM", eWarning);
  return 1;
}

/*--------------------------------------------------------------------
 Set HM to a preset value, Returns HWFault on failure, 1 on success
----------------------------------------------------------------------*/
static int DelcamPreset(pHistDriver self, SConnection * pCon,
                        HistInt value)
{
  pDelcam pPriv = NULL;
  int j;

  pPriv = (pDelcam) self->pPriv;
  for (j = 0; j < pPriv->width * pPriv->height; j++) {
    self->data->localBuffer[j] = value;
  }
  return 1;
}

/*--------------------------------------------------------------------
 get The HM data or a subset of it. Returns HWFault or 1
----------------------------------------------------------------------*/
static int DelcamGetHistogram(pHistDriver self,
                              SConnection * pCon,
                              int i, int iStart, int iEnd, HistInt * pData)
{
  pDelcam pPriv = NULL;
  HistInt *data = NULL;
  pPriv = (pDelcam) self->pPriv;

  data = self->data->localBuffer + iStart;
  if (iEnd > getHMDataLength(self->data) || iStart < 0) {
    SCWrite(pCon, "ERROR: data length out of range", eError);
    return 0;
  }
  memcpy(pData, data, (iEnd - iStart) * sizeof(HistInt));
  return 1;
}

/*--------------------------------------------------------------------
 Make the HMDriver, returns a driver or NULL on failure
----------------------------------------------------------------------*/
pHistDriver MakeDelcamHM(pStringDict pOption)
{
  pHistDriver pNew = NULL;
  pDelcam pPriv = NULL;

  /* create the general driver */
  pNew = CreateHistDriver(pOption);
  if (!pNew) {
    return NULL;
  }

  /*Create private data structure */
  pPriv = NULL;
  pPriv = (pDelcam) malloc(sizeof(Delcam));
  if (pPriv == NULL) {
    return NULL;
  }
  memset(pPriv, 0, sizeof(Delcam));
  pPriv->width = 768;
  pPriv->height = 512;
  pNew->pPriv = pPriv;


  /* add our options */
  StringDictAddPair(pOption, "amplictrl", "0");
  StringDictAddPair(pOption, "amplion", "1");
  StringDictAddPair(pOption, "shutterctrl", "0");
  StringDictAddPair(pOption, "clear", "1");
  StringDictAddPair(pOption, "schnarch", "1");
  StringDictAddPair(pOption, "counter", "unkown");

  /* configure all those functions */
  pNew->Configure = DelcamConfigure;
  pNew->Start = DelcamStart;
  pNew->Halt = DelcamHalt;
  pNew->GetCountStatus = DelcamCountStatus;
  pNew->GetError = DelcamGetError;
  pNew->TryAndFixIt = DelcamFixIt;
  pNew->GetData = DelcamGetData;
  pNew->GetHistogram = DelcamGetHistogram;
  pNew->SetHistogram = DelcamSetHistogram;
  pNew->GetMonitor = DelcamGetMonitor;
  pNew->GetTime = DelcamGetTime;
  pNew->Preset = DelcamPreset;
  pNew->FreePrivate = DelcamFree;
  pNew->Pause = DelcamPause;
  pNew->Continue = DelcamContinue;

  return pNew;
}
