/*
  This is a histogram memory driver for the 2005-6 version of the
  histogram memory software based on RTAI-Linux and an embedded WWW-server
  for communications. For all http work the ghttp library from the gnome 
  project is used.
  
  This HM is meant to be used in conjunction with a counter module
  chained through the hmcontrol module. No need to handle counters here
  when hmcontrol can do the chaining.
  
  copyright: see file COPYRIGHT
  
  Mark Koennecke, January 2005
  
----------------------------------------------------------------------*/
#include <stdlib.h>
#include <assert.h>
#include <HistDriv.i>
#include <tcl.h>
#include <ghttp.h>
#include <stptok.h>
#include <countdriv.h>
#include <trace.h>

extern char *trim(char *);
/*===================================================================
 The request strings to append to the computer address
====================================================================*/
static char startdaq[] = { "/admin/startdaq.egi" };
static char stopdaq[] = { "/admin/stopdaq.egi" };
static char pausedaq[] = { "/admin/pausedaq.egi" };
static char continuedaq[] = { "/admin/continuedaq.egi" };
static char statusdaq[] = { "/admin/textstatus.egi" };
static char gethm[] = { "/admin/readhmdata.egi" };
static char configure[] = { "/admin/configure.egi" };
static char preset[] = { "/admin/presethm.egi" };
static char subsample[] = { "/admin/processhmdata.egi" };

/*====================================================================
  error codes
======================================================================*/
#define BADURL -701
#define HTTPERROR -702
#define NOBODY -703
#define BODYSHORT -704
#define NOTIMPLEMENTED -705
#define SERVERERROR  -706
#define BADSTATUS    -707
#define BADAUTH      -708
#define HMNOMEMORY     -709
#define BADSUBSAMPLE   -710
/*=====================================================================
     our driver private data structure 
======================================================================*/
typedef struct {
  ghttp_request *syncRequest;
  char hmAddress[512];
  char userName[132];
  char passWord[132];
  char hmError[512];
  int errorCode;
  int pause;
  int failCount;
  int asyncRunning;
  int tricsswap;
} sinqHttp, *pSinqHttp;
/*------------------------------------------------------------------*/
static int sinqHttpGetPrepare(pSinqHttp self, char *request)
{
  char url[512];
  ghttp_status httpStatus;

  if (self->asyncRunning) {
    while ((httpStatus = ghttp_process(self->syncRequest))
           == ghttp_not_done) {
    }
    self->asyncRunning = 0;
  }

  self->errorCode = 0;
  ghttp_clean(self->syncRequest);
  memset(self->hmError, 0, 512 * sizeof(char));
  snprintf(url, 511, "%s%s", self->hmAddress, request);
  ghttp_set_type(self->syncRequest, ghttp_type_get);
  ghttp_set_header(self->syncRequest, "connection", "keep-alive");
  if (ghttp_set_uri(self->syncRequest, url) < 0) {
    self->errorCode = BADURL;
    return 0;
  }
  ghttp_set_authinfo(self->syncRequest, self->userName, self->passWord);
  ghttp_set_sync(self->syncRequest, ghttp_sync);
  return 1;
}

/*-------------------------------------------------------------------*/
static int sinqHttpCheckResponse(pSinqHttp self)
{
  char *pPtr = NULL;
  int len;

  self->failCount = 0;
  pPtr = ghttp_get_body(self->syncRequest);
  if (pPtr == NULL) {
    return 1;
  }
  len = ghttp_get_body_len(self->syncRequest);
  if (len > 511) {
    len = 510;
  }
  if (strstr(pPtr, "ERROR") != NULL) {
    memset(self->hmError, 0, 512 * sizeof(char));
    strlcpy(self->hmError, pPtr, sizeof(self->hmError));
    self->errorCode = HTTPERROR;
    return 0;
  } else if (strstr(pPtr, "Authentication Error") != NULL) {
    memset(self->hmError, 0, 512 * sizeof(char));
    strlcpy(self->hmError, pPtr, sizeof(self->hmError));
    self->errorCode = BADAUTH;
    return 0;
  }
  return 1;
}

/*-------------------------------------------------------------------*/
static int sinqHttpGet(pSinqHttp self, char *request)
{
  ghttp_status httpStatus;
  char *pPtr = NULL;

  if (!sinqHttpGetPrepare(self, request)) {
    return 0;
  }
  /*
   * try two times: a reconnect is no error
   */
  ghttp_prepare(self->syncRequest);
  traceIO(self->hmAddress,"OUT:%s",request);
  httpStatus = ghttp_process(self->syncRequest);
  if (httpStatus != ghttp_done) {
    ghttp_close(self->syncRequest);
    sinqHttpGetPrepare(self, request);
    ghttp_prepare(self->syncRequest);
    httpStatus = ghttp_process(self->syncRequest);
  }
  if (httpStatus != ghttp_done) {
    strlcpy(self->hmError, "Reconnect", 511);
    self->errorCode = SERVERERROR;
    return 0;
  } else {
    traceIO(self->hmAddress,"IN:%s",request);
    return sinqHttpCheckResponse(self);
  }
  return 1;
}

/*====================================================================*/
static int SinqHttpConfigure(pHistDriver self, SConnection * pCon,
                             pStringDict pOpt, SicsInterp * pSics)
{
  char hmname[512], buffer[132];
  char confCommand[512], url[512];
  pSinqHttp pPriv = NULL;
  int status, iInit;
  float fVal;
  int i;
  char *confData = NULL;
  ghttp_status httpStatus;

  pPriv = (pSinqHttp) self->pPriv;
  assert(pPriv != NULL);

  /*
   * The HM computer address
   */
  if (StringDictGet(pOpt, "hmaddress", pPriv->hmAddress, 511) != 1) {
    SCWrite(pCon,
            "ERROR: required configuration parameter hmaddress not found",
            eError);
    return 0;
  }

  /* 
   *  looser credentials
   */
  if (StringDictGet(pOpt, "username", pPriv->userName, 131) != 1) {
    SCWrite(pCon,
            "ERROR: required configuration parameter username not found",
            eError);
    return 0;
  }
  if (StringDictGet(pOpt, "password", pPriv->passWord, 131) != 1) {
    SCWrite(pCon,
            "ERROR: required configuration parameter password not found",
            eError);
    return 0;
  }

  /* actual configuration. Check for flag INIT in
     options. We do not need to configure, if the HM has configured
     itself already. What is does, these days.
   */
  status = StringDictGetAsNumber(pOpt, "init", &fVal);
  iInit = 0;
  if (status == 1) {
    if (fVal > 0.9) {
      iInit = 1;
    }
  }

  status = StringDictGet(pOpt,"tricsswap", buffer, sizeof(buffer));
  if(status == 1){
    pPriv->tricsswap = atoi(buffer);
  }
  /*
     actually do configure
   */
  if (iInit == 1) {
    for (i = 0; i < 2; i++) {
      memset(confCommand, 0, 512 * sizeof(char));
      if (StringDictGet(pOpt, "hmconfigscript", confCommand, 511) != 1) {
        SCWrite(pCon,
                "ERROR: required parameter hmconfigscript not found!",
                eError);
        return 0;
      }
      status = Tcl_Eval(pSics->pTcl, confCommand);
      if (status != TCL_OK) {
        snprintf(confCommand, 511,
                 "ERROR: Tcl reported %s while evaluating hmconfigscript",
                 Tcl_GetStringResult(pSics->pTcl));
        SCWrite(pCon, confCommand, eError);
        return 0;
      } else {
        /*
           uplod new configuration to HM
         */
        ghttp_clean(pPriv->syncRequest);
        snprintf(url, 511, "%s%s", pPriv->hmAddress, configure);
        status = ghttp_set_uri(pPriv->syncRequest, url);
        if (status < 0) {
          SCWrite(pCon, "ERROR: invalid URI for HM request", eError);
          return 0;
        }
        status = ghttp_set_type(pPriv->syncRequest, ghttp_type_post);
        confData = (char *) Tcl_GetStringResult(pSics->pTcl);
        /* puts(confData); */
        status = ghttp_set_body(pPriv->syncRequest, confData,
                                strlen(confData));
        ghttp_set_authinfo(pPriv->syncRequest, pPriv->userName,
                           pPriv->passWord);
        ghttp_set_sync(pPriv->syncRequest, ghttp_sync);
        status = ghttp_prepare(pPriv->syncRequest);
        httpStatus = ghttp_process(pPriv->syncRequest);
        confData = (char *) ghttp_get_body(pPriv->syncRequest);
        if (httpStatus != ghttp_done) {
          /* we may need to reconnect.....
           */
          if (i == 0) {
            ghttp_close(pPriv->syncRequest);
            continue;
          }
          /*
           * no we have a real error
           */
          confData = (char *) ghttp_get_error(pPriv->syncRequest);
          snprintf(confCommand, 511, "ERROR: http error %s occurred",
                   confData);
          SCWrite(pCon, confCommand, eError);
          return 0;
        } else if (confData != NULL) {
          if (strstr(confData, "ERROR") != NULL) {
            snprintf(confCommand, 511, "%s", confData);
            SCWrite(pCon, confCommand, eError);
            return 0;
          }
          if (strstr(confData, "Authentication Error") != NULL) {
            snprintf(confCommand, 511, "%s", confData);
            SCWrite(pCon, confCommand, eError);
            return 0;
          }
        }
      }
    }
  }
  return 1;
}
/*---------------------------------------------------------------------*/
static int SinqHttpPreset(pHistDriver self, SConnection * pCon, int val)
{
  pSinqHttp pPriv = NULL;
  int status;
  char command[512];

  pPriv = (pSinqHttp) self->pPriv;
  assert(pPriv != NULL);

  snprintf(command, 512, "%s?value=%d", preset, val);
  status = sinqHttpGet(pPriv, command);
  if (status != 1) {
    return HWFault;
  }
  return 1;
}
/*--------------------------------------------------------------------*/
static int SinqHttpStart(pHistDriver self, SConnection * pCon)
{
  pSinqHttp pPriv = NULL;
  int status;

  pPriv = (pSinqHttp) self->pPriv;
  assert(pPriv != NULL);

  status = sinqHttpGet(pPriv, startdaq);
  if (status != 1) {
    return HWFault;
  }
  return 1;
}

/*---------------------------------------------------------------------*/
static int SinqHttpHalt(pHistDriver self)
{
  pSinqHttp pPriv = NULL;
  int status;

  pPriv = (pSinqHttp) self->pPriv;
  assert(pPriv != NULL);

  status = sinqHttpGet(pPriv, stopdaq);
  if (status != 1) {
    return HWFault;
  }
  return 1;
}

/*---------------------------------------------------------------------*/
static int SinqHttpPause(pHistDriver self, SConnection * pCon)
{
  pSinqHttp pPriv = NULL;
  int status;

  pPriv = (pSinqHttp) self->pPriv;
  assert(pPriv != NULL);

  status = sinqHttpGet(pPriv, pausedaq);
  if (status != 1) {
    return HWFault;
  }
  pPriv->pause = 1;
  return 1;
}

/*---------------------------------------------------------------------*/
static int SinqHttpContinue(pHistDriver self, SConnection * pCon)
{
  pSinqHttp pPriv = NULL;
  int status;

  pPriv = (pSinqHttp) self->pPriv;
  assert(pPriv != NULL);

  status = sinqHttpGet(pPriv, continuedaq);
  if (status != 1) {
    return HWFault;
  }
  pPriv->pause = 0;
  return 1;
}

/*--------------------------------------------------------------------*/
static int readStatus(pHistDriver pDriv)
{
  char *pPtr = NULL, *pLinePtr;
  char line[132];
  char name[80], value[80];
  pSinqHttp self = NULL;
  char *daqPtr = NULL, *daqValPtr = NULL;

  self = (pSinqHttp) pDriv->pPriv;
  assert(self != NULL);

  pPtr = ghttp_get_body(self->syncRequest);
  if (pPtr == NULL) {
    strlcpy(self->hmError, "No body in status response", 131);
    self->errorCode = NOBODY;
    return 0;
  }
  pPtr = stptok(pPtr, line, 132, "\n");
  while (pPtr != NULL) {
    pLinePtr = line;
    pLinePtr = stptok(pLinePtr, name, 80, ":");
    pLinePtr = stptok(pLinePtr, value, 80, ":");
    strtolower(name);
    if (StringDictExists(pDriv->pOption, trim(name)) == 1) {
      StringDictUpdate(pDriv->pOption, trim(name), trim(value));
    } else {
      StringDictAddPair(pDriv->pOption, trim(name), trim(value));
    }
    pPtr = stptok(pPtr, line, 131, "\n");
  }
  return 1;
}

/*---------------------------------------------------------------------*/
static int SinqHttpStatus(pHistDriver self, SConnection * pCon)
{
  pSinqHttp pPriv = NULL;
  ghttp_status httpStatus;
  char daqStatus[20];
  int status, len;
  char *pPtr = NULL;

  pPriv = (pSinqHttp) self->pPriv;
  assert(pPriv != NULL);

  if (pPriv->pause == 1) {
    return HWPause;
  }

  if (pPriv->asyncRunning == 0) {
    status = sinqHttpGetPrepare(pPriv, statusdaq);
    traceIO(pPriv->hmAddress,"OUT:%s", statusdaq);
    ghttp_set_sync(pPriv->syncRequest, ghttp_async);
    ghttp_prepare(pPriv->syncRequest);
    if (status != 1) {
      return HWFault;
    }
    pPriv->asyncRunning = 1;
  }
  httpStatus = ghttp_process(pPriv->syncRequest);
  switch (httpStatus) {
  case ghttp_error:
    ghttp_close(pPriv->syncRequest);
    sinqHttpGetPrepare(pPriv, statusdaq);
    ghttp_prepare(pPriv->syncRequest);
    httpStatus = ghttp_process(pPriv->syncRequest);
    if (httpStatus != ghttp_done) {
      strlcpy(pPriv->hmError, "Reconnect", 511);
      pPriv->errorCode = SERVERERROR;
      pPriv->asyncRunning = 0;
      return HWFault;
    }
    break;
  case ghttp_not_done:
    return HWBusy;
    break;
  case ghttp_done:
    pPriv->asyncRunning = 0;
    status = sinqHttpCheckResponse(pPriv);
    if (status != 1) {
      return HWFault;
    }
    traceIO(pPriv->hmAddress,"IN:%s", statusdaq);
    break;
  }

  status = readStatus(self);
  if (status != 1) {
    return HWFault;
  }

  if (StringDictGet(self->pOption, "daq", daqStatus, 20) != 1) {
    pPriv->errorCode = BADSTATUS;
    strlcpy(pPriv->hmError, "ERROR: status does not contain DAQ field",
            511);
    return HWFault;
  }
  if (strstr(daqStatus, "1") != NULL) {
    return HWBusy;
  } else if (strstr(daqStatus, "0") != NULL) {
    return HWIdle;
  } else {
    pPriv->errorCode = BADSTATUS;
    snprintf(pPriv->hmError, 511, "ERROR: invalid DAQ  status %s",
             daqStatus);
    return HWFault;
  }

  return HWIdle;
}

/*---------------------------------------------------------------------*/
static int SinqHttpError(pHistDriver self, int *code,
                         char *error, int errLen)
{
  pSinqHttp pPriv = NULL;
  int status;

  pPriv = (pSinqHttp) self->pPriv;
  assert(pPriv != NULL);

  strlcpy(error, pPriv->hmError, errLen);
  *code = pPriv->errorCode;
  return 1;
}

/*--------------------------------------------------------------------*/
static int SinqHttpFixIt(pHistDriver self, int code)
{
  pSinqHttp pPriv = NULL;

  pPriv = (pSinqHttp) self->pPriv;
  assert(pPriv != NULL);

  /*
   * not much to fix here: ghttplib already tries hard to fix
   * connection problems... But abort any pending transactions...
   */
  ghttp_close(pPriv->syncRequest);
  if (code == SERVERERROR) {
    pPriv->failCount++;
    if (pPriv->failCount > 2) {
      return COTERM;
    } else {
      return COREDO;
    }
  }
  return COTERM;
}

/*--------------------------------------------------------------------*/
static int SinqHttpGetData(pHistDriver self, SConnection * pCon)
{
  /*
   * do noting, monitors are with the counter, histogram memory
   * caching and retrieval is handled via GetHistogram
   */
  return 1;
}
/*-------------------------------------------------------------------
 * This is an ugly hack to swap TRICS data into the
 * correct order.
 */
static void swapTrics(HistInt *data, pHMdata hmdata, int length)
{
	int x,y, xdim, ydim;
	HistInt *tmpData = NULL, val;

	/*
	 * do nothing if no match
	 */
	xdim = hmdata->iDim[0];
	ydim = hmdata->iDim[1];
	if(xdim*ydim < length){
		return;
	}
	tmpData = malloc(length*sizeof(HistInt));
	if(tmpData == NULL){
		return;
	}
	memcpy(tmpData, data, length*sizeof(HistInt));
	for(y = 0; y < ydim; y++){
		for(x = 0; x < xdim; x++){
			val = tmpData[x*ydim + y];
			data[y*xdim+x] = val;
		}
	}
	free(tmpData);
}
/*-------------------------------------------------------------------*/
static int SinqHttpGetHistogram(pHistDriver self, SConnection * pCon,
                                int bank, int start, int end,
                                HistInt * data)
{
  char command[256];
  HistInt *hmdata;
  pSinqHttp pPriv = NULL;
  int status, len, i;
  long dataSum = 0;

  pPriv = (pSinqHttp) self->pPriv;
  assert(pPriv != NULL);

  snprintf(command, 255, "%s?bank=%d&start=%d&end=%d", gethm, bank,
           start, end);

  /* printf("SinqHttpGetHistogram:%d-%d\n", start,end); */

  status = sinqHttpGet(pPriv, command);
  if (status != 1) {
    return HWFault;
  }

  len = ghttp_get_body_len(pPriv->syncRequest);
  if (len < (end - start) * sizeof(int)) {
    pPriv->errorCode = BODYSHORT;
    strlcpy(pPriv->hmError, "Not enough data received from HM", 511);
    return HWFault;
  }
  hmdata = (HistInt *) ghttp_get_body(pPriv->syncRequest);
  for (i = 0; i < (end - start); i++) {
    data[i] = ntohl(hmdata[i]);
    dataSum += data[i];
  }
 /* printf("Read %ld counts from HM\n", dataSum); */

  if(pPriv->tricsswap == 1){
	  swapTrics(data, self->data, end-start);
  } else if(pPriv->tricsswap == 2) {
    memcpy(data,hmdata,(end-start)*sizeof(HistInt));
  }
  return 1;
}

/*-------------------------------------------------------------------*/
static HistInt *SinqHttpSubSample(pHistDriver self, SConnection * pCon,
                                  int bank, char *hmcommand)
{
  char command[1024];
  HistInt *hmdata = NULL;
  HistInt *resultdata = NULL;
  pSinqHttp pPriv = NULL;
  int status, len, i;

  pPriv = (pSinqHttp) self->pPriv;
  assert(pPriv != NULL);

  snprintf(command, 1023, "%s?bank=%d&command=%s", subsample, bank,
           hmcommand);

  status = sinqHttpGet(pPriv, command);
  if (status != 1) {
	if(pCon != NULL){
		SCPrintf(pCon,eError,"ERROR: subsampling failed with %s", pPriv->hmError);
	}
    return NULL;
  }

  len = ghttp_get_body_len(pPriv->syncRequest);
  if (len <= 0) {
    strlcpy(pPriv->hmError, "ERROR: no data returned from subsampling",
            511);
    pPriv->errorCode = BADSUBSAMPLE;
    return NULL;
  }
  resultdata = malloc(len + 4);
  if (resultdata == NULL) {
    strlcpy(pPriv->hmError,
            "ERROR: failed to allocate buffer for subsampling results",
            511);
    pPriv->errorCode = HMNOMEMORY;
    return NULL;
  }
  resultdata[0] = len / sizeof(HistInt);
  hmdata = (HistInt *) ghttp_get_body(pPriv->syncRequest);
  for (i = 0; i < len / sizeof(HistInt); i++) {
    resultdata[i + 1] = ntohl(hmdata[i]);
  }
  return resultdata;
}

/*--------------------------------------------------------------------*/
static int SinqHttpSetHistogram(pHistDriver self, SConnection * pCon,
                                int bank, int start, int end,
                                HistInt * data)
{
  pSinqHttp pPriv = NULL;

  pPriv = (pSinqHttp) self->pPriv;
  assert(pPriv != NULL);

  pPriv->errorCode = NOTIMPLEMENTED;
  strlcpy(pPriv->hmError, "Not implemented", 511);
  return HWFault;
}

/*---------------------------------------------------------------------*/
static long SinqHttpGetMonitor(pHistDriver self, int i, SConnection * pCon)
{
  return 0;
}

/*---------------------------------------------------------------------*/
static float SinqHttpGetTime(pHistDriver self, SConnection * pCon)
{
  return -999.99;
}


/*---------------------------------------------------------------------*/
static int SinqHttpFreePrivate(pHistDriver self)
{
  pSinqHttp pPriv = NULL;

  pPriv = (pSinqHttp) self->pPriv;
  if (pPriv == NULL) {
    return 1;
  }

  if (pPriv->syncRequest != NULL) {
    ghttp_request_destroy(pPriv->syncRequest);
  }
  free(pPriv);
  return 1;
}

/*-------------------------------------------------------------------*/
pHistDriver CreateSinqHttpDriver(pStringDict pOption)
{
  pHistDriver pNew = NULL;
  pSinqHttp pInternal = NULL;

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

  /* add our options */
  StringDictAddPair(pOption, "hmaddress", "http://unknown.psi.ch:9090");
  StringDictAddPair(pOption, "hmconfigurescript", "hmconfigure");

  /* initialise our private data structure */
  pInternal = (pSinqHttp) malloc(sizeof(sinqHttp));
  if (!pInternal) {
    free(pNew);
    return NULL;
  }
  memset(pInternal, 0, sizeof(sinqHttp));
  pNew->pPriv = pInternal;
  pInternal->syncRequest = ghttp_request_new();
  if (pInternal->syncRequest == NULL) {
    free(pNew);
    free(pInternal);
    return NULL;
  }


  /* configure all those functions */
  pNew->Configure = SinqHttpConfigure;
  pNew->Start = SinqHttpStart;
  pNew->Halt = SinqHttpHalt;
  pNew->GetCountStatus = SinqHttpStatus;
  pNew->GetError = SinqHttpError;
  pNew->TryAndFixIt = SinqHttpFixIt;
  pNew->GetData = SinqHttpGetData;
  pNew->GetHistogram = SinqHttpGetHistogram;
  pNew->GetHistogram = SinqHttpGetHistogram;
  pNew->SubSample = SinqHttpSubSample;
  pNew->GetMonitor = SinqHttpGetMonitor;
  pNew->GetTime = SinqHttpGetTime;
  pNew->Preset = SinqHttpPreset;
  pNew->FreePrivate = SinqHttpFreePrivate;
  pNew->Pause = SinqHttpPause;
  pNew->Continue = SinqHttpContinue;

  return pNew;
}
/*-------------------------------------------------------------------------*/
int isSINQHTTP(pHistDriver self){
	if(self->Start == SinqHttpStart){
		return 1;
	} else {
		return 0;
	}
}
