/**
 * This is an asynchronous protocol implementation for HTTP. 
 * It includes special features to store binary data coming 
 * from a SINQ http histogram memory in a sinqdata object. 
 * Which has to be specified on initialisation.
 * 
 * copyright: see file COPYRIGHT
 * 
 * Mark Koennecke, June 2008
 * 
 * Expanded to handle post:url:data style  and node:path:data syntax
 * in preparation for a full second generation HM object.
 * 
 * Mark Koennecke, May 2009 
 */
#include <stdio.h>
#include  <ascon.h>
#include  <ascon.i>
#include  <ghttp.h>
#include  <sicsdata.h>
#include <HistMem.h>
#include "uselect.h"
#include "sicshipadaba.h"
/*---------------------------------------------------------------------*/
typedef struct {
  ghttp_request *request;
  char *userName;
  char *password;
  pSICSData binData;
  pHdb node;
} HttpProt, *pHttpProt;
/*---------------------------------------------------------------------*/
static int configRequest(Ascon * a)
{
  pHttpProt pHttp = (pHttpProt) a->private;
  pDynString request;
  char *uri = NULL;
  char *data, *pPtr, *path, *dataCopy;
  
  pHttp->node = NULL;
  ghttp_clean(pHttp->request);
  ghttp_set_header(pHttp->request, "connection", "keep-alive");

  request = CreateDynString(64, 64);
  if (request == NULL) {
    AsconError(a, "Out of memory", 0);
    return 0;
  }
  DynStringConcat(request, "http://");
  DynStringConcat(request, a->hostport);
  DynStringConcatChar(request, '/');
  data = GetCharArray(a->wrBuffer);
  if(strstr(data,"node:") == data){
	  dataCopy = strdup(data);
	  path = strchr(dataCopy,':')+1;
	  uri = strchr(path,':');
	  *uri = '\0';
	  uri++;
	  pHttp->node = FindHdbNode(NULL,path,pServ->dummyCon);
	  DynStringConcat(request,uri);
	  ghttp_set_type(pHttp->request, ghttp_type_get);
	  free(dataCopy);
  } else if(strstr(data,"post:") == data){
	  dataCopy = strdup(data);
	  uri = strchr(dataCopy,':') + 1;
	  pPtr = strchr(uri,':');
	  *pPtr = '\0';
	  ghttp_set_type(pHttp->request, ghttp_type_post);
	  DynStringConcat(request,uri);
	  pPtr++;
	  ghttp_set_body(pHttp->request,strdup(pPtr), strlen(pPtr));
	  free(dataCopy);
  } else {
	  ghttp_set_type(pHttp->request, ghttp_type_get);
	  DynStringConcat(request, GetCharArray(a->wrBuffer));
  }
  uri = GetCharArray(request);
  
  if (ghttp_set_uri(pHttp->request, uri) < 0) {
    AsconError(a, "Bad URL", 0);
    return 0;
  }
  if (pHttp->userName != NULL && pHttp->password != NULL) {
    ghttp_set_authinfo(pHttp->request, pHttp->userName, pHttp->password);
  }
  ghttp_set_sync(pHttp->request, ghttp_async);
  ghttp_prepare(pHttp->request);
  DeleteDynString(request);

  return 1;
}

/*---------------------------------------------------------------------*/
static void handleReply(Ascon * a)
{
  char *pPtr = NULL, *pType = NULL, *path = NULL;
  int len, i, *dataPtr = NULL;
  HistInt *hmData = NULL;
  pHttpProt pHttp = (pHttpProt) a->private;

  pPtr = ghttp_get_body(pHttp->request);
  len = ghttp_get_body_len(pHttp->request);
  if (strstr(pPtr, "ERROR") != NULL) {
    AsconError(a, pPtr, 0);
    DynStringConcat(a->rdBuffer, pPtr);
  } else if (strstr(pPtr, "Authentication Error") != NULL) {
    DynStringConcat(a->rdBuffer, pPtr);
    AsconError(a, pPtr, 0);
  } else {
    pType = (char *) ghttp_get_header(pHttp->request, "Content-Type");
    if (strstr(pType, "sinqhm") == NULL) {
      /* text data */
      for (i = 0; i < len; i++) {
        DynStringConcatChar(a->rdBuffer, pPtr[i]);
      }
    } else {
      hmData = (HistInt *) pPtr;
      len = len / sizeof(HistInt);
      if(pHttp->node == NULL){
    	  clearSICSData(pHttp->binData);
    	  dataPtr = getSICSDataPointer(pHttp->binData, 0, len);
    	  for (i = 0; i < len; i++) {
    		  dataPtr[i] = htonl(hmData[i]);
    	  }
    	  assignSICSType(pHttp->binData, 0, len, INTTYPE);
    	  DynStringCopy(a->rdBuffer, "SICSDATA");
      } else {
    	  if(pHttp->node->value.arrayLength != len){
    		  if(pHttp->node->value.v.intArray != NULL){
    			  free(pHttp->node->value.v.intArray);
    		  }
    		  pHttp->node->value.v.intArray = malloc(len*sizeof(int));
    		  if(pHttp->node->value.v.intArray == NULL){
    			  AsconError(a,"Out of memory ",0);
    			  return;
    		  }
    		  pHttp->node->value.arrayLength = len;
    	  }
    	  for(i = 0; i < len; i++){
    		  pHttp->node->value.v.intArray[i] = htonl(hmData[i]);
     	  }
    	  NotifyHipadabaPar(pHttp->node,NULL);
    	  /*
    	  path = GetHipadabaPath(pHttp->node);
     	  if(path != NULL){
    		  printf("Sinqhttpprot has updated node: %s\n", path);
    		  free(path);
    	  }
    	   */
      }
    }
  }
}

/*---------------------------------------------------------------------*/
static int HttpHandler(Ascon * a)
{
  ghttp_status status;
  pHttpProt pHttp = (pHttpProt) a->private;
  int socke, selStat;
  fd_set rmask;
  struct timeval tmo = { 0, 0 };
  char buffer[1024];
  ghttp_current_status procStatus;

  switch (a->state) {
  case AsconConnectStart:
    a->state = AsconConnecting;
    break;
  case AsconConnecting:
    a->state = AsconConnectDone;        /* success */
    break;
  case AsconWriteStart:
    if (configRequest(a)) {
      a->state = AsconWriting;
    }
    return 1;
    break;
  case AsconWriting:
    status = ghttp_process(pHttp->request);
    if (status == ghttp_error) {
      ghttp_close(pHttp->request);
      configRequest(a);
      status = ghttp_process(pHttp->request);
    }
    if (status == ghttp_error) {
      AsconError(a, "Server error", 0);
      DynStringConcat(a->rdBuffer, "Server error");
      a->state = AsconReadDone;
    } else {
      procStatus = ghttp_get_status(pHttp->request);
      if (procStatus.proc == ghttp_proc_response_hdrs
          || procStatus.proc == ghttp_proc_response) {
          a->state = AsconWriteDone; 
      } else {
    	  return 0;
      }
    }
    a->start = DoubleTime();
    DynStringClear(a->rdBuffer);
    break;
  case AsconReadStart:
    socke = ghttp_get_socket(pHttp->request);
    FD_ZERO(&rmask);
    FD_SET(socke, &rmask);
    selStat = uselect(socke + 1, &rmask, NULL, NULL, &tmo);
    if (selStat > 0 && FD_ISSET(socke, &rmask)) {
      status = ghttp_process(pHttp->request);
      a->state = AsconReading;
    } else {
      if (DoubleTime() > a->start + a->timeout) {
        AsconError(a, "read timeout", 0);
        a->state = AsconTimeout;
        /* this to clear the line */
        ghttp_close(pHttp->request);
      }
    }
    return 1;
    break;
  case AsconReading:
    socke = ghttp_get_socket(pHttp->request);
    /*
    FD_ZERO(&rmask);
    FD_SET(socke, &rmask);
    selStat = uselect(socke + 1, &rmask, NULL, NULL, &tmo);
    if (selStat == 0) {
      return 1;
    }
    */
    status = ghttp_process(pHttp->request);
    switch (status) {
    case ghttp_not_done:
      if (DoubleTime() > a->start + a->timeout) {
        AsconError(a, "read timeout", 0);
        a->state = AsconTimeout;
        /* this to clear the line */
        ghttp_close(pHttp->request);
      }
      return 0;
    case ghttp_done:
      handleReply(a);
      a->state = AsconReadDone;
      break;
    case ghttp_error:
      /*
       * A first error may not be an error but a 
       * reconnect
       */
      ghttp_close(pHttp->request);
      configRequest(a);
      status = ghttp_process(pHttp->request);
      if (status == ghttp_error) {
        AsconError(a, "Server error", 0);
        DynStringConcat(a->rdBuffer, "Server error");
        a->state = AsconReadDone;
      }
      return 1;
      break;
    }
    break;
  default:
    return AsconStdHandler(a);
  }
  return 1;
}

/*------------------------------------------------------------------------*/
static void killHttp(void *data)
{
  pHttpProt prot = (pHttpProt) data;
  if (prot == NULL) {
    return;
  }
  if (prot->request != NULL) {
    ghttp_close(prot->request);
  }
  if (prot->password != NULL) {
    free(prot->password);
  }
  if (prot->userName != NULL) {
    free(prot->userName);
  }
  free(prot);
}

/*------------------------------------------------------------------------*/
static int HttpProtInit(Ascon * a, SConnection * con,
                        int argc, char *argv[])
{
  pHttpProt pHttp = NULL;

  pHttp = calloc(sizeof(HttpProt), 1);
  if (pHttp == NULL) {
    SCWrite(con, "ERROR: out of memory in HttpProtInit", eError);
    return 0;
  }
  if (argc < 3) {
    return 0;
  }
  a->hostport = strdup(argv[1]);
  pHttp->binData = (pSICSData) FindCommandData(pServ->pSics,
                                               argv[2], "SICSData");
  if (pHttp->binData == NULL) {
    SCWrite(con, "ERROR: SICSData objct not found", eError);
    return 0;
  }
  if (argc > 3) {
    a->timeout = atof(argv[3]);
  } else {
    a->timeout = 10.;
  }
  if (argc > 5) {
    pHttp->userName = strdup(argv[4]);
    pHttp->password = strdup(argv[5]);
  }
  pHttp->request = ghttp_request_new();
  a->private = pHttp;
  a->killPrivate = killHttp;
  a->state = AsconConnectStart;
  return 1;
}

/*-------------------------------------------------------------------------*/
void AddHttpProtocoll()
{
  AsconProtocol *prot = NULL;

  prot = calloc(sizeof(AsconProtocol), 1);
  prot->name = strdup("sinqhttp");
  prot->init = HttpProtInit;
  prot->handler = HttpHandler;
  AsconInsertProtocol(prot);
}
