/*  This is the simple HTTP server and client used for inter-process communication.

    *** This should only be used within libxm. It is subject to change without notice. ***
    
    This code was initially based (i.e. lots of copy-paste blocks) on examples posted on
    Bao Jie's CS586 page at Dept of Computer Science, Iowa State University:
        http://www.cs.iastate.edu/~baojie/acad/course/cs586/socket/server.c
        http://www.cs.iastate.edu/~baojie/acad/course/cs586/socket/client.c
    It has been heavilly modified since then.

    @author  Bao Jie (Sample code)
    @author  J. Czechowski IV (Many changes)
    @since   NeXtMidas 2.1.2
*/

#include "HttpConnection.h"

#define TRUE  1
#define FALSE 0

#define BUFFER_SIZE 4096 
#define MAX_STR_LEN 512


/* Function prototypes. */
static int runServer(server_info_t*);

/* See httpConnection.h for docs. */
int httpGet(char* hostname, int port, char* path, char* buffer, int lbuffer, http_info_t *httpInfo) {
  struct sockaddr_in localAddr;
  struct sockaddr_in servAddr;
  struct hostent *host;
  http_info_t requestInfo; /* default info. */

  char request[MAX_STR_LEN];   /* HTTP request.   */
  int  sd;                     /* Socket number.  */
  int  rc;                     /* Remote socket.  */
  int  inHeader = TRUE;        /* Reading header? */
  int  i;                      /* Counter.     */
  int  start;                  /* Loop marker. */
  char str[MAX_STR_LEN];       /* Temp string. */
  char length;                 /* Temp length. */
  
  if (httpInfo == NULL) {
    httpInfo = &requestInfo;
  }
  
  /* Initialize structure */
  httpInfo->numRead   =  0;
  httpInfo->status    = -1;
  httpInfo->length    = -1;
  httpInfo->dataStart = -1;

  snprintf(request, MAX_STR_LEN, "GET %s HTTP/1.1\r\n\r\n", path);

  host = gethostbyname(hostname);
  if(host == NULL) {
    fprintf(stderr, "unknown host: %s \n ", hostname);
    httpInfo->numRead = -1;
  }
  
  if (httpInfo->numRead != -1) {
    servAddr.sin_family = host->h_addrtype;
    servAddr.sin_port   = htons(port);
    memcpy((char *) &servAddr.sin_addr.s_addr, host->h_addr_list[0], host->h_length);

    // create socket 
    sd = socket(AF_INET, SOCK_STREAM, 0);
    if(sd<0) {
      fprintf(stderr, "Cannot open socket\n");
      httpInfo->numRead = -1;
    }
  }
  
  /*  bind port number */
  if (httpInfo->numRead != -1) {
    printf("Bind port number...  ");
    
    localAddr.sin_family      = AF_INET;
    localAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    localAddr.sin_port        = htons(0);
    
    rc = bind(sd, (struct sockaddr *) &localAddr, sizeof(localAddr));
    if(rc<0) {
      fprintf(stderr, "Cannot bind port TCP %u\n",port);
      httpInfo->numRead = -1;
    }
  }
  
  /* connect to server */
  if (httpInfo->numRead != -1) {
    rc = connect(sd, (struct sockaddr *) &servAddr, sizeof(servAddr));
    if(rc<0) {
      fprintf(stderr, "Cannot connect to server %s\n", hostname);
      httpInfo->numRead = -1;
    }
  }

  /* send request */
  if (httpInfo->numRead != -1) {
    rc = write(sd, request, strlen(request));
    if(rc < 0) {
      fprintf(stderr, "Error sending request to server: %s\n", request);
      httpInfo->numRead = -1;
      close(sd);
    }
  }

  /* display response */
  if (httpInfo->numRead != -1) {
    do {
      rc = read(sd, &buffer[httpInfo->numRead], lbuffer-httpInfo->numRead);
      if(rc > 0) {
        for (i = 0; (i < rc) && inHeader; i+=2) {
          start = i;
          
          while ((i+1 < rc) && (buffer[i] != '\r') && (buffer[i+1] != '\n')) { i++; }
          
          length = i - start;
          
          if ((buffer[i] != '\r') || (buffer[i+1] != '\n')) {
            /* Need to read more. */
          } else if ((start == 0) && (i > 12)) {
            memcpy(str, &buffer[9], 12);
            str[3] = '\0';
            httpInfo->status = atoi(str);
            
          } else if (start == i) {
            httpInfo->dataStart = i+2;
            inHeader = FALSE;
            
          } else if ((length > 16) && (strncmp(&buffer[start], "Content-Length: ", 16) == 0)) {
            memcpy(str, &buffer[start+16], i-start-16);
            str[i-start-16] = '\0';
            
            httpInfo->length = atoi(str);
          }
        }
        httpInfo->numRead += rc;
        
        if ((httpInfo->numRead - httpInfo->dataStart) >= httpInfo->length) { rc = -1; }
      }
    } while(rc > 0);
  }
  
  if (sd > 0) { close(sd); }
  return httpInfo->numRead;
}

/* See httpConnection.h for docs. */
int runHttpServer(int firstPort, int lastPort, server_info_t* serverInfo) {
  int   status    = TRUE;  /* Operating status (TRUE=good, FALSE=error) */
  int   foundPort = FALSE; /* Has a port been found? */
  int   pid       = 0;     /* Current process ID. */
  pid_t port      = 0;     /* Port number. */

  /* Init the serverInfo struct. */
  serverInfo->pid  = 0;
  serverInfo->port = 0;
  serverInfo->stop = FALSE;

  /* create socket */
  serverInfo->sd = socket(AF_INET, SOCK_STREAM, 0);
  if (serverInfo->sd < 0) {
    fprintf(stderr, "Can not open socket\n");
    status = FALSE;
  }
  
  /* bind server port */
  if (status) {
    for (port = firstPort; (serverInfo->port <= lastPort) && !foundPort; serverInfo->port++) {
      serverInfo->servAddr.sin_family      = AF_INET;
      serverInfo->servAddr.sin_port        = htons(serverInfo->port);
      serverInfo->servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
      
      if (bind(serverInfo->sd, (struct sockaddr *) &(serverInfo->servAddr), sizeof(serverInfo->servAddr)) == 0) {
        foundPort = TRUE;
        serverInfo->port = port;
        listen(serverInfo->sd, MAX_REQUESTS); /* listen to incoming connection requests. */
      }
    }
    
    if (!foundPort) {
      fprintf(stderr, "Can not find an available port in range %d-%d\n", firstPort, lastPort);
      status = FALSE;
    }
  }

  /* Start the server. */
  if (status) {
    if (serverInfo->childProcess != NULL) {
      if (pid < 0) {
        fprintf(stderr, "Unable to fork process for HTTP server.\n");
        status = FALSE;
      } else if (pid == 0) {
        serverInfo->childProcess(serverInfo);
      } else {
        serverInfo->pid = pid;
        status = runServer(serverInfo);
      }
    } else { /* Use single thread. */
      status = runServer(serverInfo);
    }
  }
  
  return status;
}

/** Runs the server. Called only by runHttpServer(..). */
static int runServer(server_info_t* serverInfo) {
  struct sockaddr_in cliAddr;       /* Client address. */
  int lcliAddr = sizeof(cliAddr);   /* Length of cliAddr */
  int newSd;                        /* Socket descriptor for a unique connection. */
  int done     = FALSE;             /* Server is done. */
  int status   = TRUE;              /* Status (TRUE=ok,FALSE=error) */

  while (!(serverInfo->stop) && status && !done) {
    newSd = accept(serverInfo->sd, (struct sockaddr *) &cliAddr, &lcliAddr);
    if(newSd < 0) {
      fprintf(stderr, "Error while trying to accept connection on port %d\n",serverInfo->port);
      status = FALSE;
    }
    
    if (status) {
      done = !serverInfo->requestHandler(serverInfo, newSd, &cliAddr);
      close(newSd);
    }
  }
  close(serverInfo->sd);
  return status;
}





/******************************************************************************/
/* Sample code                                                               **/
/******************************************************************************/
#if 1 /* Test client */
int main( int argc, char *argv[]) {
  http_info_t requestInfo;
  char buffer[BUFFER_SIZE];
  
  int numRead = -1;
  
  numRead = httpGet("nextmidas.com", 80, "/nextmidas/", buffer, BUFFER_SIZE, &requestInfo);

  if (numRead > 0) { printf("%.*s\n", numRead, buffer); }


  return (numRead < 0)? 1: 0;
}

#else /* Test server */
int handleRequest(server_info_t* serverInfo, int sock, socket_addr_t* addr) {
  ssize_t bytesRead;                      /* Number of bytes read into buffer. */
  ssize_t bytesSent;                      /* Number of bytes sent.             */
  char    buffer[BUFFER_SIZE];
  char    response[] = "HTTP/1.1 200 OK\r\n\r\nHello World!";
  int     lresponse;

  int status = TRUE;
  
  memset(buffer, 0x0, BUFFER_SIZE);
  
  /* receive request */
  bytesRead = read(sock, buffer, BUFFER_SIZE);
  printf("   Received request from %s:\n\n%s\n", inet_ntoa(addr->sin_addr), buffer);
  
  /* send response */
  printf("   Sending default response...\n");
  lresponse = strlen(response); 
  bytesSent = send(sock, response,lresponse,0);
  
  if (lresponse != bytesSent ) {
    printf("error during send response, total: %d, sent:%d\n", lresponse, bytesSent);
    status = FALSE;
  }

  return status;
}

int childThread(server_info_t* serverInfo) {
  while (TRUE) {
    printf("child processing...\n");
    sleep(5);
  }
}

int main( int argc, char *argv[ ]) {
  int status = TRUE;
  server_info_t serverInfo;
  serverInfo.requestHandler = handleRequest;
  serverInfo.childProcess   = childThread;
  
  return !runHttpServer(9798, 9798, &serverInfo);
}

#endif
