/**
 * 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
 * 
 * After finding that libghttp actually blocks on sockets, this is
 * a reimplementation doing everything itself.
 * 
 * Mark Koennecke, November 2010
 */
#include <stdio.h>
#include  <ascon.h>
#include  <ascon.i>
#include <unistd.h>
#include  <sicsdata.h>
#include <HistMem.h>
#include "sicshipadaba.h"
#include <stptok.h>
extern char *trim(char *txt);
/*-------------------------------------------------------------------------------
 * taken from libghttp
 */
static const char b64_alphabet[65] = {
    "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    "abcdefghijklmnopqrstuvwxyz"
    "0123456789+/=" };

static char *
http_base64_encode(const char *text) {
  /* The tricky thing about this is doing the padding at the end,
   * doing the bit manipulation requires a bit of concentration only */
  char *buffer = NULL;
  char *point = NULL;
  int inlen = 0;
  int outlen = 0;

  /* check our args */
  if (text == NULL)
    return NULL;

  /* Use 'buffer' to store the output. Work out how big it should be...
   * This must be a multiple of 4 bytes */

  inlen = strlen( text );
  /* check our arg...avoid a pesky FPE */
  if (inlen == 0)
    {
      buffer = malloc(sizeof(char));
      buffer[0] = '\0';
      return buffer;
    }
  outlen = (inlen*4)/3;
  if( (inlen % 3) > 0 ) /* got to pad */
    outlen += 4 - (inlen % 3);

  buffer = malloc( outlen + 1 ); /* +1 for the \0 */
  memset(buffer, 0, outlen + 1); /* initialize to zero */

  /* now do the main stage of conversion, 3 bytes at a time,
   * leave the trailing bytes (if there are any) for later */

  for( point=buffer; inlen>=3; inlen-=3, text+=3 ) {
    *(point++) = b64_alphabet[ *text>>2 ];
    *(point++) = b64_alphabet[ (*text<<4 & 0x30) | *(text+1)>>4 ];
    *(point++) = b64_alphabet[ (*(text+1)<<2 & 0x3c) | *(text+2)>>6 ];
    *(point++) = b64_alphabet[ *(text+2) & 0x3f ];
  }

  /* Now deal with the trailing bytes */
  if( inlen ) {
    /* We always have one trailing byte */
    *(point++) = b64_alphabet[ *text>>2 ];
    *(point++) = b64_alphabet[ (*text<<4 & 0x30) |
			     (inlen==2?*(text+1)>>4:0) ];
    *(point++) = (inlen==1?'=':b64_alphabet[ *(text+1)<<2 & 0x3c ] );
    *(point++) = '=';
  }

  *point = '\0';

  return buffer;
}
/*---------------------------------------------------------------------*/
typedef struct {
  char *userName;
  char *password;
  pSICSData binData;
  pHdb node;
  int sockHandle;
  int bytesExpected;
  char *contentType;
  int headerReceived;
  int byteSwap;
} HttpProt, *pHttpProt;
/*---------------------------------------------------------------------*/
static int moreToReadThen(Ascon *a, int length)
{
  pHttpProt pHttp = NULL;
  pHttp = (pHttpProt)a->private;

  if(pHttp->headerReceived == 0) {
    return 0;
  }
 
  if(pHttp->bytesExpected - GetDynStringLength(a->rdBuffer) > length){
    return 1;
  } else {
    return 0;
  }
}
/*---------------------------------------------------------------------*/
static int HTTPcallback(int handle, void *userData)
{
  Ascon *a = (Ascon *)userData;
  pHttpProt pHttp = NULL;
  unsigned char ch;
  int length;
  char *data;
  int blockSize = 65536; 

  if(a == NULL){
    printf("really serious error in HTTPcallback\n");
    return 0;
  }
  pHttp = (pHttpProt)a->private;

  data = (char *)ANETreadPtr(handle,&length);
  if(data != NULL){
    /*
      This code here should optimize away excessive calls 
      to memcpy which I have seen in a sysprof test on 
      SICS @ BOA
     */
    if(moreToReadThen(a,blockSize) == 1 && length < blockSize){
      // skip
    } else {
      DynStringConcatBytes(a->rdBuffer,data,length);
      ANETreadConsume(handle,length);
    }
  }
  return 1;

}
/*---------------------------------------------------------------------*/
static void sendAuth(pHttpProt pHttp)
{
	char buffer[256];
	char *encoded = NULL;

	if(pHttp->userName != NULL){
		snprintf(buffer,255,"%s:%s", pHttp->userName,pHttp->password);
		encoded = http_base64_encode(buffer);
		if(encoded != NULL){
			snprintf(buffer,255,"Authorization: Basic %s\r\n\r\n", encoded);
			free(encoded);
		}
	} else {
		strcpy(buffer,"\r\n");
	}
	ANETwrite(pHttp->sockHandle,buffer,strlen(buffer));
}
/*---------------------------------------------------------------------*/
static void sendGet(pHttpProt pHttp, char *path)
{
	char buffer[1024];
	char hostname[256];

	gethostname(hostname,255);
	snprintf(buffer,1024,"GET %s HTTP/1.1\r\nHost: %s\r\nConnection: keep-alive\r\n",
			path, hostname);
	ANETwrite(pHttp->sockHandle, buffer,strlen(buffer));
	sendAuth(pHttp);
}
/*---------------------------------------------------------------------*/
static void sendPost(pHttpProt pHttp, char *path, char *data)
{
	char buffer[1024];
	char hostname[256];

	gethostname(hostname,255);
	snprintf(buffer,1024,"POST %s HTTP/1.1\r\nHost: %s\r\nConnection: keep-alive\r\n",
			path, hostname);
	ANETwrite(pHttp->sockHandle, buffer,strlen(buffer));
	snprintf(buffer,1024,"Content-Length: %d\r\n", (int)strlen(data));
	ANETwrite(pHttp->sockHandle, buffer,strlen(buffer));
	sendAuth(pHttp);
	ANETwrite(pHttp->sockHandle,data,strlen(data));
}
/*---------------------------------------------------------------------*/
static void sendRequest(pHttpProt pHttp, char *data)
{
	char *path, *dataCopy, *pPtr;

	dataCopy = strdup(data);

	pHttp->node = NULL;
	pHttp->headerReceived = 0;
	pPtr = strchr(dataCopy,':');
	if(pPtr == NULL){
		path = dataCopy;
		sendGet(pHttp,path);
		free(dataCopy);
		return;
	} else {
		if(strstr(dataCopy,"node") != NULL){
			path = pPtr+1;
			pPtr = strchr(path,':');
			*pPtr = '\0';
			pHttp->node = FindHdbNode(NULL,path,pServ->dummyCon);
			path = pPtr+1;
			sendGet(pHttp,path);
			free(dataCopy);
			return;
		} else if(strstr(dataCopy,"post") != NULL){
			path = pPtr+1;
			pPtr = strchr(path,':');
			*pPtr = '\0';
			sendPost(pHttp,path, pPtr+1);
			free(dataCopy);
		} else if(strstr(dataCopy,"processhmdata.egi") != NULL){
			path = dataCopy;
			sendGet(pHttp,path);
			free(dataCopy);
			return;
		}
	}
}
/*---------------------------------------------------------------------*/
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 = GetCharArray(a->rdBuffer);
  len = GetDynStringLength(a->rdBuffer);
  if (strstr(pPtr, "ERROR") != NULL) {
    AsconError(a, pPtr, 0);
  } else if (strstr(pPtr, "Authentication Error") != NULL) {
    AsconError(a, pPtr, 0);
  } else {
    pType = pHttp->contentType;
    if (strstr(pType, "sinqhm") != NULL) {
      hmData = (HistInt *) pPtr;
      len = len / sizeof(HistInt);
      /*
      if(len == 0) {
    	  printf("Blllllllllaaaaaaaaaaeeeeeeeeeerrrrrrrrrrrkkkk!\n");
      }
      */
      if(pHttp->node == NULL){
    	  clearSICSData(pHttp->binData);
    	  dataPtr = getSICSDataPointer(pHttp->binData, 0, len);
	  if(pHttp->byteSwap == 1){
	    for (i = 0; i < len; i++) {
	      dataPtr[i] = ntohl(hmData[i]);
	    }
	  } else {
	    for (i = 0; i < len; i++) {
	      dataPtr[i] = hmData[i];
	    }
	  }
    	  assignSICSType(pHttp->binData, 0, len, INTTYPE);
    	  DynStringClear(a->rdBuffer);
    	  DynStringCopy(a->rdBuffer, "SICSDATA");
    	  /*
    	  printf("SICSDATA received..........\n");
    	  */
      } 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;
		  /* printf("SINQHTTPOPT setting length to %d\n", len); */
    	  }
	  if(pHttp->byteSwap == 1){
	    for(i = 0; i < len; i++){
	      pHttp->node->value.v.intArray[i] = ntohl(hmData[i]);
	    }
	  } else {
	    for(i = 0; i < len; i++){
	      pHttp->node->value.v.intArray[i] = hmData[i];
	    }
          }
    	  NotifyHipadabaPar(pHttp->node,NULL);
    	  DynStringClear(a->rdBuffer);
    	  DynStringCopy(a->rdBuffer, "NODEDATA");
    	  /*
    	  path = GetHipadabaPath(pHttp->node);
     	  if(path != NULL){
    		  printf("Sinqhttpopt has updated node: %s, length = %d\n", path, len);
    		  free(path);
    	  }
    	  */
      }
    }
  }
}
/*---------------------------------------------------------------------*/
static int processHeader(Ascon *a)
{
	char line[132], *pPtr, *data, *colon;
	pHttpProt pHttp = (pHttpProt) a->private;
	int status, length;

	data = GetCharArray(a->rdBuffer);
	assert(data != NULL);
	/*
	 * printf("%s",data);
	 */

	pPtr = data;
	pPtr = stptok(pPtr, line, 132, "\n");
	sscanf(line,"HTTP/1.%*d %03d", &status);
	if(status != 200 && status != 201){
		AsconError(a,line,AsconFailure);
		return 0;
	}
	while((pPtr = stptok(pPtr,line,132,"\n")) != NULL){
		colon = strchr(line,':');
		if(colon != NULL){
			*colon = '\0';
			colon++;
			strtolower(line);
			if(strstr(line,"content-length") != NULL){
				pHttp->bytesExpected = atoi(trim(colon));
			}
			if(strstr(line,"content-type") != NULL){
				if(pHttp->contentType != NULL){
					free(pHttp->contentType);
				}
				pHttp->contentType = strdup(trim(colon));
			}
			if(strstr(line,"connection") != NULL){
				strtolower(colon);
				if(strstr(colon,"close") != NULL){
					pHttp->bytesExpected = INT32_MAX;
				}
			}
		}
	}
	pHttp->headerReceived = 1;
	/**
	 * Hack off the header
	 */
	pPtr = strstr(data, "\r\n\r\n");
	length = GetDynStringLength(a->rdBuffer);
	length -= 4 + (pPtr - data);
	if(length > 0){
		data = malloc(length*sizeof(char));
		if(data == NULL){
			AsconError(a,"Out of memory", AsconFailure);
			return 0;
		}
		memcpy(data,pPtr+4,length);
		DynStringClear(a->rdBuffer);
		DynStringConcatBytes(a->rdBuffer,data,length);
		free(data);
	} else {
		DynStringClear(a->rdBuffer);
	}
	DynStringCapacity(a->rdBuffer,pHttp->bytesExpected);

	if(pHttp->bytesExpected < 0){
    return 0;
  } else {
    return 1;
  }
}
/*---------------------------------------------------------------------*/
static int HttpHandler(Ascon * a)
{
  pHttpProt pHttp = (pHttpProt) a->private;
  int socke, selStat, port, status;
  fd_set rmask;
  struct timeval tmo = { 0, 0 };
  char buffer[1024];
  char *hostport, *colon;

  switch (a->state) {
  case AsconConnectStart:
    a->state = AsconConnecting;
    break;
  case AsconConnecting:
    a->state = AsconConnectDone;        /* success */
    break;
  case AsconWriteStart:
        if(strcmp(GetCharArray(a->wrBuffer),"byteswapoff") == 0) {
	    pHttp->byteSwap = 0;
	    a->state = AsconReadDone;
	    return 0;
        }
	if(!ANETvalidHandle(pHttp->sockHandle)){
		hostport = strdup(a->hostport);
		colon = strchr(hostport, ':');
		if (colon == NULL){
			  port = 80;
		} else {
			  *colon = '\0';
			  port = atoi(colon + 1);
		}
		if (port <= 0) {
		    AsconError(a, "bad port number", 0);
		    return 1;
		}
		pHttp->sockHandle = ANETconnect(hostport,port);
		free(hostport);
		if(pHttp->sockHandle < 0){
			AsconError(a,"Failed to connect", 0);
		} else {
			ANETsetReadCallback(pHttp->sockHandle,
					HTTPcallback, a,NULL);
		    a->state = AsconWriting;
		}
	} else {
	    a->state = AsconWriting;
	}
    return 1;
    break;
  case AsconWriting:
    sendRequest(pHttp,GetCharArray(a->wrBuffer));
    a->state = AsconWriteDone;
    a->start = DoubleTime();
    pHttp->bytesExpected = -1;
    DynStringClear(a->rdBuffer);
    return 1;
    break;
  case AsconReadStart:
	  a->state= AsconReading;
	  return 1;
	  break;
  case AsconReading:
	ANETprocess();
	/**
	 * Here we have basically four conditions to check:
	 * - <cr><ld><cr><lf> detected means that we received
	 *   the complete header.
	 * - enough bytes read: termination
	 * - socket closed means all data has been read
	 * - timeout waiting for a response
	 */
	if(strstr(GetCharArray(a->rdBuffer), "\r\n\r\n") != NULL){
		status = processHeader(a);
		if(status != 1){
			a->state = AsconReadDone;
			break;
		}
	}
	if(!ANETvalidHandle(pHttp->sockHandle)){
		if(pHttp->headerReceived) {
			handleReply(a);
			a->state = AsconReadDone;
		} else {
			/*
			 * We only noticed when attempting to read that the WWW-server has closed
			 * the connection. ANETclose will have killed the read and write buffers.
			 *  So redo everything......
			 */
			a->state = AsconWriteStart;
			return 1;
		}
		break;
	}
	if(pHttp->bytesExpected > 0 && GetDynStringLength(a->rdBuffer) >= pHttp->bytesExpected ){
		handleReply(a);
		a->state = AsconReadDone;
		break;
	}
    if (a->timeout > 0) {
      if (DoubleTime() - a->start > a->timeout) {
        AsconError(a, "no response", 0);
        printf("Timeout on httpopt\n");
        ANETclose(pHttp->sockHandle);
        a->state = AsconTimeout;
      }
    }
	return 0;
    break;
  default:
    return AsconBaseHandler(a);
  }
  return 1;
}

/*------------------------------------------------------------------------*/
static void killHttp(void *data)
{
  pHttpProt prot = (pHttpProt) data;
  if (prot == NULL) {
    return;
  }
  if (prot->password != NULL) {
    free(prot->password);
  }
  if (prot->userName != NULL) {
    free(prot->userName);
  }
  if(prot->contentType != NULL){
	  free(prot->contentType);
  }
  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 object 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->sockHandle = -10;
  pHttp->contentType = strdup("text/html");
  pHttp->byteSwap = 1;
  a->private = pHttp;
  a->killPrivate = killHttp;
  a->state = AsconConnectStart;
  return 1;
}

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

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