/*--------------------------------------------------------------------------
  This is a driver for the TDC histogram memory as supplied with the
  Risoe instruments. This system uses a Z80 processor internally. 
  Communication is via a GPIB interface. For more information on this
  device consult documentation available from Risoe.

  The TDC needs a separate counter for controlling the count operation. 
  The counter is connected to the TDC and inhibits histogramming when the 
  counter is not ready. This is alos why many of the functions in this
  file chain down to a counter.

  copyright: see file COPYRIGHT

  Mark Koennecke, February 2003
  --------------------------------------------------------------------------*/
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <math.h>
#include <fortify.h>
#include <sics.h>
#include <countdriv.h>
#include <counter.h>
#include <HistMem.h>
#include <stringdict.h>
#include <HistDriv.i>
#include "ecb.h"

/*-------------------------- private definitions -----------------------*/
typedef enum { HMX, HMY, HMXY } TDCModes;

#define MAXTDCCHANNELS (128*128)

#define COMMERROR    -301
#define MAXEXCEEDED  -302

/*------------------------- private data structure ---------------------*/

typedef struct {
  TDCModes mode;
  int map;
  int bank;
  unsigned char fillByte;
  int range;
  int n;
  int errorCode;
  pCounter counter;
  pECB tdc;
} Tdc, *pTdc;

/*---------------------- Monitor event data structure (from counter.c) */
typedef struct {
  float fPreset;
  float fCurrent;
  char *pName;
} MonEvent, *pMonEvent;

/*=======================================================================*/
static int downloadConfiguration(pTdc self)
{
  int status;
  Z80_reg in, out;
  int function;

  /*
     map 
   */
  in.d = (unsigned char) self->map;
  status = ecbExecute(self->tdc, 134, in, &out);
  if (status != 1) {
    return status;
  }

  /*
     mode
   */
  switch (self->mode) {
  case HMX:
    function = 129;
    break;
  case HMY:
    function = 130;
    break;
  case HMXY:
    function = 128;
    break;
  default:
    assert(0);
  }
  status = ecbExecute(self->tdc, function, in, &out);
  if (status != 1) {
    return status;
  }

  return 1;
}

/*----------------------------------------------------------------------*/
static int TDCConfigure(pHistDriver self, SConnection * pCon,
                        pStringDict pOpt, SicsInterp * pSics)
{
  pTdc tdc = NULL;
  char pValue[80];
  float value;
  int status;

  assert(self);
  tdc = (pTdc) self->pPriv;
  assert(tdc);

  status = StringDictGet(pOpt, "mode", pValue, 79);
  assert(status);               /* defaults should have been set in driver creation */
  if (strcmp(pValue, "HMX") == 0) {
    tdc->mode = HMX;
  } else if (strcmp(pValue, "HMY") == 0) {
    tdc->mode = HMY;
  } else if (strcmp(pValue, "HMXY") == 0) {
    tdc->mode = HMXY;
  } else {
    SCWrite(pCon, "ERROR: invalid HM mode defaulted to HMXY", eError);
    tdc->mode = HMXY;
  }

  status = StringDictGetAsNumber(pOpt, "map", &value);
  if (!status) {
    SCWrite(pCon, "ERROR: invalid map value", eError);
  } else {
    tdc->map = (int) rint(value);
  }

  status = StringDictGetAsNumber(pOpt, "bank", &value);
  if (!status) {
    SCWrite(pCon, "ERROR: invalid bank value", eError);
  } else {
    tdc->bank = (int) rint(value);
    if (tdc->bank != 0 && tdc->bank != 1) {
      SCWrite(pCon, "ERROR: invalid bank value defaulted to 0", eError);
      tdc->bank = 0;
    }
  }

  status = StringDictGetAsNumber(pOpt, "range", &value);
  if (!status) {
    SCWrite(pCon, "ERROR: invalid range value", eError);
  } else {
    tdc->range = (int) rint(value);
  }

  status = StringDictGetAsNumber(pOpt, "n", &value);
  if (!status) {
    SCWrite(pCon, "ERROR: invalid n value", eError);
  } else {
    tdc->n = (int) rint(value);
  }

  status = StringDictGet(pOpt, "counter", pValue, 79);
  /*
     ignore errors here, in order to support operations without counter
   */
  if (!status) {
    tdc->counter = NULL;
  }
  tdc->counter = FindCommandData(pSics, pValue, "SingleCounter");

  tdc->fillByte = 0;

  status = StringDictGet(pOpt, "ecb", pValue, 79);
  assert(status);
  tdc->tdc = FindCommandData(pSics, pValue, "ECB");
  if (tdc->tdc == NULL) {
    SCWrite(pCon, "ERROR: failed to locate ECB system, critical!", eError);
    return 0;
  }

  status = downloadConfiguration(tdc);
  if (!status) {
    tdc->errorCode = status;
    return 0;
  }


  self->iReconfig = 0;

  return 1;
}

/*=======================================================================*/
static int clearTdc(pTdc self, unsigned char fill)
{
  Z80_reg in, out;

  in.b = self->bank;
  in.c = fill;
  in.d = 0;
  return ecbExecute(self->tdc, 133, in, &out);
}

/*---------------------------------------------------------------------*/
static int enableTdc(pTdc self)
{
  Z80_reg in, out;

  return 1;

  /*
     range and n are obscure parameters
   */
  in.c = 0;
  in.b = self->range;
  in.d = (char) ((self->n >> 8) & 255);
  in.e = (char) (self->n & 255);

  return ecbExecute(self->tdc, 131, in, &out);
}

/*-----------------------------------------------------------------------*/
static int TDCStart(pHistDriver self, SConnection * pCon)
{
  pTdc tdc = NULL;
  int status;

  assert(self);
  tdc = (pTdc) self->pPriv;
  assert(tdc);
  tdc->errorCode = 0;

  status = clearTdc(tdc, 0);
  if (status != 1) {
    tdc->errorCode = COMMERROR;
    return HWFault;
  }

  status = enableTdc(tdc);
  if (status != 1) {
    tdc->errorCode = COMMERROR;
    return HWFault;
  }

  /*
     start the counter if available
   */
  if (tdc->counter != NULL) {
    tdc->counter->pDriv->fPreset = self->fCountPreset;
    tdc->counter->pDriv->eMode = self->eCount;
    InvokeCallBack(tdc->counter->pCall,COUNTSTART,NULL);
    return tdc->counter->pDriv->Start(tdc->counter->pDriv);
  }
  return 1;
}

/*=====================================================================*/
static int disableTdc(pTdc self)
{
  Z80_reg in, out;

  return 1;

  return ecbExecute(self->tdc, 132, in, &out);
}

/*-------------------------------------------------------------------*/
static int TDCHalt(pHistDriver self)
{
  pTdc tdc = NULL;
  int status;

  assert(self);
  tdc = (pTdc) self->pPriv;
  assert(tdc);
  tdc->errorCode = 0;

  if (tdc->counter != NULL) {
    tdc->counter->pDriv->Halt(tdc->counter->pDriv);
  }

  status = disableTdc(tdc);
  if (status != 1) {
    tdc->errorCode = COMMERROR;
    return HWFault;
  }

  return 1;
}

/*=====================================================================*/
static int TDCCountStatus(pHistDriver self, SConnection * pCon)
{
  pTdc tdc = NULL;
  int tdcstatus, status = HWFault;
  float fControl, fOldControl;
  MonEvent sMon;

  assert(self);
  tdc = (pTdc) self->pPriv;
  assert(tdc);

  if (tdc->counter != NULL) {
    fOldControl = tdc->counter->pDriv->fLastCurrent;
    status =
        tdc->counter->pDriv->GetStatus(tdc->counter->pDriv, &fControl);
    if(fOldControl != fControl) {
        sMon.fCurrent = fControl;
	sMon.fPreset = tdc->counter->pDriv->fPreset;
	sMon.pName = tdc->counter->name;
	InvokeCallBack(tdc->counter->pCall, MONITOR, &sMon);
    }
  }
  /*
     The TDC does not seem to have a means to figure if it is counting or not
     or to do some sort of progress report. So it has to have an associated 
     counter in order to stop it at the end.
   */

  /* 
     This is no proper fix. The proper fix would be to keep the state
     in the private data structure as well and disable the TDC
     on HWNoBeam and enable the TDC again if the
     beam comes on. However, this is done in hardware at SANS-II. So we 
     leave it for now.
   */
  if (status == HWIdle || status == HWFault) {
    if(tdc->counter != NULL){
      tdc->counter->pDriv->ReadValues(tdc->counter->pDriv);
    }
    InvokeCallBack(tdc->counter->pCall,COUNTEND,NULL);
    tdcstatus = disableTdc(tdc);
    if (tdcstatus != 1) {
      tdc->errorCode = COMMERROR;
      return HWFault;
    }
  }
  return status;
}

/*=====================================================================*/
static int TDCGetError(pHistDriver self, int *iCode, char *perror,
                       int iErrlen)
{
  pTdc tdc = NULL;

  assert(self);
  tdc = (pTdc) self->pPriv;
  assert(tdc);

  *iCode = tdc->errorCode;
  switch (tdc->errorCode) {
  case COMMERROR:
    strlcpy(perror, "Communication problem with TDC", iErrlen);
    break;
  case MAXEXCEEDED:
    strlcpy(perror, "Requested to many channels for read", iErrlen);
    break;
  default:
    if (tdc->counter != NULL) {
      tdc->counter->pDriv->GetError(tdc->counter->pDriv,
                                    iCode, perror, iErrlen);
    } else {
      strlcpy(perror, "Undefined error code", iErrlen);
    }
    break;
  }
  return 1;
}

/*=====================================================================*/
static int TDCFixIt(pHistDriver self, int iCode)
{
  pTdc tdc = NULL;
  int result;

  assert(self);
  tdc = (pTdc) self->pPriv;
  assert(tdc);

  switch (iCode) {
  case COMMERROR:
    ecbClear(tdc->tdc);
    result = COREDO;
    break;
  default:
    if (tdc->counter != NULL) {
      result = tdc->counter->pDriv->TryAndFixIt(tdc->counter->pDriv,
                                                iCode);
    } else {
      result = COTERM;
    }
    break;
  }
  return result;
}

/*=====================================================================*/
static int TDCGetData(pHistDriver self, SConnection * pCon)
{
  pTdc tdc = NULL;

  assert(self);
  tdc = (pTdc) self->pPriv;
  assert(tdc);

  if (tdc->counter != NULL) {
    return tdc->counter->pDriv->ReadValues(tdc->counter->pDriv);
  }
  return 1;
}

/*======================================================================*/
static long TDCGetMonitor(pHistDriver self, int i, SConnection * pCon)
{
  pTdc tdc = NULL;

  assert(self);
  tdc = (pTdc) self->pPriv;
  assert(tdc);

  if (tdc->counter != NULL) {
    return GetMonitor(tdc->counter, i, pCon);
  }
  return -9999;                 /* no monitor available */
}

/*======================================================================*/
static float TDCGetTime(pHistDriver self, SConnection * pCon)
{
  pTdc tdc = NULL;

  assert(self);
  tdc = (pTdc) self->pPriv;
  assert(tdc);

  if (tdc->counter != NULL) {
    return GetCountTime(tdc->counter, pCon);
  }
  return -9999.99;              /* no time available */
}

/*=====================================================================*/
static int TDCPause(pHistDriver self, SConnection * pCon)
{
  pTdc tdc = NULL;
  int status;

  assert(self);
  tdc = (pTdc) self->pPriv;
  assert(tdc);

  status = disableTdc(tdc);
  if (status != 1) {
    tdc->errorCode = COMMERROR;
    return HWFault;
  }


  if (tdc->counter != NULL) {
    return tdc->counter->pDriv->Pause(tdc->counter->pDriv);
  }
  return 1;
}

/*=====================================================================*/
static int TDCContinue(pHistDriver self, SConnection * pCon)
{
  pTdc tdc = NULL;
  int status;

  assert(self);
  tdc = (pTdc) self->pPriv;
  assert(tdc);

  status = enableTdc(tdc);
  if (status != 1) {
    tdc->errorCode = COMMERROR;
    return HWFault;
  }


  if (tdc->counter != NULL) {
    return tdc->counter->pDriv->Continue(tdc->counter->pDriv);
  }
  return 1;
}

/*=======================================================================*/
static int TDCFree(pHistDriver self)
{
  free(self->pPriv);
  return 1;
}

/*======================================================================*/
static int TDCSetHistogram(pHistDriver self, SConnection * pCon,
                           int i, int iStart, int iEnd, HistInt * pData)
{
  pTdc tdc = NULL;
  int status;
  char pBueffel[256];
  Ecb_pack data;

  assert(self);
  tdc = (pTdc) self->pPriv;
  assert(tdc);

  sprintf(pBueffel,
          "WARNING: SetHistogram can only fill HM with a fill byte %s",
          "\n using firts value for that");
  SCWrite(pCon, pBueffel, eWarning);

  data.result = pData[0];
  status = clearTdc(tdc, data.b.byt0);
  if (status != 1) {
    tdc->errorCode = COMMERROR;
    return HWFault;
  }
  return 1;
}

/*======================================================================*/
static int TDCPreset(pHistDriver self, SConnection * pCon, HistInt iVal)
{
  pTdc tdc = NULL;
  int status;
  char pBueffel[256];
  Ecb_pack data;

  assert(self);
  tdc = (pTdc) self->pPriv;
  assert(tdc);

  sprintf(pBueffel,
          "WARNING: Preset can only fill HM with a fill byte %s",
          "\n using first value for that");
  SCWrite(pCon, pBueffel, eWarning);

  data.result = iVal;
  status = clearTdc(tdc, data.b.byt0);
  if (status != 1) {
    tdc->errorCode = COMMERROR;
    return HWFault;
  }
  return 1;
}

/*======================================================================*/
static int TDCGetHistogram(pHistDriver self, SConnection * pCon,
                           int bank, int iStart, int length,
                           HistInt * pData)
{
  pTdc tdc = NULL;
  int status;
  unsigned short address, byteCount;

  assert(self);
  tdc = (pTdc) self->pPriv;
  assert(tdc);

  if (length > MAXTDCCHANNELS) {
    tdc->errorCode = MAXEXCEEDED;
    return HWFault;
  }

  address = (unsigned short) iStart;
  /*
     this is a fix because the TDC cannot send more then 64KB
   */
  if (length >= 16384) {
    length = 16383;
  }
  byteCount = length * 4;
  status = ecbDMARead(tdc->tdc, address, pData, byteCount);
  if (status != 1) {
    tdc->errorCode = COMMERROR;
    return HWFault;
  }
  return 1;
}

/*====================================================================*/
pHistDriver MakeTDCHM(pStringDict pOption)
{
  pHistDriver pNew = NULL;
  pTdc tdc = NULL;

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

  /* add our options */
  StringDictAddPair(pOption, "ecb", "blub");
  StringDictAddPair(pOption, "range", "0");
  StringDictAddPair(pOption, "n", "0");
  StringDictAddPair(pOption, "bank", "0");
  StringDictAddPair(pOption, "mode", "HMXY");
  StringDictAddPair(pOption, "counter", "Gwendolin");
  StringDictAddPair(pOption, "map", "9");

  /* initialise our private data structure */
  tdc = (pTdc) malloc(sizeof(Tdc));
  if (tdc == NULL) {
    free(pNew);
    return NULL;
  }
  memset(tdc, 0, sizeof(Tdc));
  tdc->map = 9;
  tdc->mode = HMXY;
  pNew->pPriv = tdc;

  /* configure all those functions */
  pNew->Configure = TDCConfigure;
  pNew->Start = TDCStart;
  pNew->Halt = TDCHalt;
  pNew->GetCountStatus = TDCCountStatus;
  pNew->GetError = TDCGetError;
  pNew->TryAndFixIt = TDCFixIt;
  pNew->GetData = TDCGetData;
  pNew->GetHistogram = TDCGetHistogram;
  pNew->SetHistogram = TDCSetHistogram;
  pNew->GetMonitor = TDCGetMonitor;
  pNew->GetTime = TDCGetTime;
  pNew->Preset = TDCPreset;
  pNew->SubSample = DefaultSubSample;
  pNew->FreePrivate = TDCFree;
  pNew->Pause = TDCPause;
  pNew->Continue = TDCContinue;

  return pNew;
}
