//*************************************************************************
/*
    Copyright (c) 2010. Pandora Products All Rights Reserved.
*/
//*************************************************************************
/*
*  Module Name:         REST	- Application Web server
*
*  Description:         This server is used to serve web pages and 
*						do RESTful web operations
*
*  Author:              Jim Schimpf
*
*  Revision History:	26 Sep 2010 Initial version
*						10 Oct 2010 Add restcom comunications
*
*	Uses RSERV_DATA structure with the following field values
*					to start operation
*						http_port	- Port used for HTTP interaction
*						http_site	- Path to HTML files served
*						rest_base	- Base of REST command tree
*	Will run till run_flag set FALSE in the RSERV_DATA structure.
*
*	Callbacks:
*		Once running when a RESTful URI is received it will call the
*		rest() routine and from there the request will be sent via
*		restcom command link to the handler (Lua script)
*		Results will be returned the same way and the request returned
*		to libmicrohttpd 
*	This program uses libmicrohttpd from Christian Grothoff as the HTTP
*	protocol manager
*/
//************************************************************************

#include <fcntl.h>
#include <sys/stat.h>
#include <ctype.h>
#include "platform.h"
#include "microhttpd.h"
#include "internal.h"
#include "rest.h"
#include "restcom.h"

#if 0
	#pragma mark -
	#pragma mark -- Data --
#endif

typedef enum {
				NO_SITE,
				HTTP_SITE,
				REST_SITE,
				} URL_TYPE;

typedef struct {
				const char *method_name;
				HTTP_TYPE	type;
				} HTTP_METHOD_LIST;
				
static HTTP_METHOD_LIST methods[] = {
									MHD_HTTP_METHOD_CONNECT,	HTTP_CONNECT,
									MHD_HTTP_METHOD_DELETE,		HTTP_DELETE,
									MHD_HTTP_METHOD_GET,		HTTP_GET,
									MHD_HTTP_METHOD_HEAD,		HTTP_HEAD,
									MHD_HTTP_METHOD_OPTIONS,	HTTP_OPTIONS,
									MHD_HTTP_METHOD_POST,		HTTP_POST,
									MHD_HTTP_METHOD_PUT,		HTTP_PUT,
									MHD_HTTP_METHOD_TRACE,		HTTP_TRACE,
								};

static int distributor (void *cls,
          struct MHD_Connection *connection,
          const char *url,
          const char *method,
          const char *version,
          const char *upload_data, size_t *upload_data_size, void **ptr);
static HTTP_TYPE get_method( const char *method );
static URL_TYPE url_type( char *url,RSERV_DATA *rdata );

static int http (void *cls,
          struct MHD_Connection *connection,
          const char *url,
          HTTP_TYPE method,
          const char *version,
          const char *upload_data, size_t *upload_data_size, void **ptr);
static char *get_file( char *path, size_t *size );


static int rest (void *cls,
          struct MHD_Connection *connection,
          const char *url,
          HTTP_TYPE method,
          const char *version,
          const char *upload_data, size_t *upload_data_size, void **ptr);

#define MSG_404 "<html><head><title>404 PAGE</title></head><body><center><h1>404 Page NOT FOUND</h1></center></body></html>"
#define DEF_HTTP_PORT	8080

static RSERV_DATA local_data;


#if 0
	#pragma mark -
	#pragma mark -- Server External API --
#endif

/***********************************************************************
*
*	int rest_server( NONE )	- Run server
*
*	INPUT:	NONE
*
*	OUTPUT:	Return 0 if run ok (when server shutdown)
*			< 0 if server start/run problem
*
**********************************************************************/

int rest_server( void )	
{
	struct MHD_Daemon *d = NULL;
	int rtnval = -1;		// Set for failure
	RSERV_DATA *rdata = rest_data();
	
	// (1) Set up RESTCOM channel
	
	if( restcom_server_open() == 0 )
	{
		// (2) Start HTTP server
		
		rdata->run_flag = 1;		// Set for RUN
		d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DEBUG,
							rdata->http_port,
							NULL, NULL, &distributor, rdata , MHD_OPTION_END);
								
		// (3) If it started then keep running till user 
		// calls quits
		
		if( d != NULL )
		{
			rtnval = 0;		// Successful run
			rdata->d = (void *)d;
		}
	}
	return rtnval;
}

/***********************************************************************
*
*	int rest_server_close( NONE )	- Close server
*
*	INPUT:	NONE
*
*	OUTPUT:	NONE
*
**********************************************************************/

void rest_server_close( void )
{
	RSERV_DATA *rdata = rest_data();
	struct MHD_Daemon *d;
	
	// (1) Shutdown HTTP
	
	d = (struct MHD_Daemon *)rdata->d;
	MHD_stop_daemon (d);
		
	// (2) Shutdown Server comm
		
	restcom_server_close();
}

/***********************************************************************
*
*	RSERV_DATA *rest_data(void)	- Return pointer to server data struct
*
*	INPUT:	NONE
*
*	OUTPUT:	Pointer to local server data struct
*
**********************************************************************/

RSERV_DATA *rest_data(void)
{
	return( &local_data );
}

#if 0
	#pragma mark -
	#pragma mark -- MHD Support ---
#endif

/***********************************************************************
*
*	static int distributor ()	- Do HTTP Request
*
*	INPUT:	cls			- Registration option
*			connection	- Context
*			url			- URL requested
*			method		- Type of action needed
*			version		- HTTP version string
*			upload_data	- Passed in data on POST
*			upload_data_size	- Size in bytes of post
*
*	OUTPUT:	Return MHD_YES if successful, MHD_NO if not
*
**********************************************************************/

static int distributor (void *cls,
          struct MHD_Connection *connection,
          const char *url,
          const char *method,
          const char *version,
          const char *upload_data, size_t *upload_data_size, void **ptr)
{
	  RSERV_DATA *data = (RSERV_DATA *)cls;
	  char *site;
	  int ret = MHD_NO;
	  HTTP_TYPE call_method;
	  struct MHD_Response *response = NULL;
	  URL_TYPE type;
	  
	  // (1) Classify the requested method
	  
	  call_method = get_method(method);
	  
	  // (1) Where is this going ?
	  // See if it's to the HTTP or RESTful site
	  // Compare the URL (less the first character) with the 
	  // HTTP base
	  
	  site = (char *)&(url[1]);
	  type = url_type(site,data);
	  switch(type)
	  {
		case HTTP_SITE:			// Serve pages
				ret = http( cls,
						connection,
						url,
						call_method,
						version,
						upload_data,
						upload_data_size,
						ptr);
				break;
				
		case REST_SITE:			// Do RESTful stuff
				ret = rest( cls,
						connection,
						url,
						call_method,
						version,
						upload_data,
						upload_data_size,
						ptr);
				break;
				
		case NO_SITE:	// 404
				response = MHD_create_response_from_data (strlen (MSG_404),
												(void *) MSG_404, 
												MHD_NO, MHD_NO);
				if( response != NULL )
				{
					ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
					MHD_destroy_response (response);
				}
				break;
		}
	
	return ret;
}

/***********************************************************************
*
*	static HTTP_TYPE get_method( char *method )
*
*	INPUT:	method	- HTTP method string
*
*	OUTPUT:	Return method type or HTTP_NONE if not found
*
**********************************************************************/

static HTTP_TYPE get_method( const char *method )
{
	int i;
	int n = sizeof(methods)/sizeof(HTTP_METHOD_LIST);
	HTTP_TYPE rtn = HTTP_NONE;
	
	// Find the matching method
	
	for( i=0; i<n; i++ )
	{
		if( strcmp(method,methods[i].method_name) == 0 )
			rtn = methods[i].type;
	}
	
	return rtn;
}

/***********************************************************************
*
*	URL_TYPE url_type( char *url,RSERV_DATA *rdata ) - Classify URL
*
*	INPUT:	received URL
*
*	OUTPUT:	Return if URL is going to REST, HTTP or nothing
*			Compare URL up to first / and see if it matches
*			the HTTP or REST path or neither
*
**********************************************************************/

static URL_TYPE url_type( char *url,RSERV_DATA *rdata )
{	
	int i;
	URL_TYPE type = NO_SITE;
	char base[256];
	
	// (1) Copy first 256 bytes of the URL and use that for further work
	// also upper case it
	
	strncpy(base,url,256);
	for(i=0; i<256; i++ )
		base[i] = toupper(base[i]);
		
	// (2) Now compare it with the http_base & rest base
	
	if( strncmp(base,rdata->http_base,strlen(rdata->http_base)) == 0 )
	{
		type = HTTP_SITE;
	}
	else
	{
		if( strncmp(base,rdata->rest_base,strlen(rdata->rest_base)) == 0 )
		{
			type = REST_SITE;
		}
	}
	
	return type;
}

#if 0
	#pragma mark -
	#pragma mark -- HTTP support ---
#endif

/***********************************************************************
*
* static int http ()	- Do HTTP Request
*
*	INPUT:	cls			- Registration option
*			connection	- Context
*			url			- URL requested
*			method		- Type of action needed
*			version		- HTTP version string
*			upload_data	- Passed in data on POST
*			upload_data_size	- Size in bytes of post
*
*	OUTPUT:	Return MHD_YES if successful, MHD_NO if not
*
**********************************************************************/

static int http (void *cls,
          struct MHD_Connection *connection,
          const char *url,
          HTTP_TYPE method,
          const char *version,
          const char *upload_data, size_t *upload_data_size, void **ptr)
{
	RSERV_DATA *data = (RSERV_DATA *)cls;
	struct MHD_Response *response = NULL;
	int ret = MHD_NO;
	char fname[256];
	char *path;
	char *fdata;
	size_t fsize;
	
	// (1) Switch on requested method

	switch( method )
	{
		case HTTP_GET:	// GET METHOD
						// Build the file location, first strip off the 
						// http_base and the leading / from the URL

				path = (char *)&url[(int)(strlen(data->http_base)+1)];

				strcpy(fname,data->http_site);
				strcat(fname,path);		// Pointer to actual file inside http_base
				fdata = get_file(fname,&fsize);
				
				if( fdata != NULL)
				{
					// If we have file data then build a response from it
					response = MHD_create_response_from_data (fsize,(void *)fdata, MHD_YES, MHD_NO);
				}
				else
				{
					// Otherwise return a 404
					response = MHD_create_response_from_data (strlen (MSG_404),
															(void *) MSG_404, 
															MHD_NO, MHD_NO);
				}
				break;

		
		default:	// Unknown method
			break;
	}
		  
	// Return response and status
	
	if( response != NULL )
	{
		ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
		MHD_destroy_response (response);
	}
	
	return ret;
}

/***********************************************************************
*
*	char *get_file( char *path, size_t *size ) - Read in a file
*
*	INPUT:	path	- Path to file
*			size	- Size in bytes if read
*
*	OUTPUT:	Pointer to allocated buffer with file data
*
**********************************************************************/

static char *get_file( char *path, size_t *size )
{
	char *data = NULL;
	int fd;
	struct stat stats;
	int sz;
	
	// (1) Open and get the file size
	
	fd = open(path,O_RDONLY);
	if( fd >= 0 )
	{
		// (2) Get the size
		
		if( fstat(fd,&stats) == 0 )
		{
			// (3) Build the buffer
			
			data = MEMALLOC( stats.st_size );
			if( data != NULL )
			{
				// (4) Read the file & close it
				
				sz = read(fd, (void *)data, stats.st_size);
				if( sz <= 0 )
				{
					printf("Read [%s] Bad FILE\n",path);
					MEMFREE(data);
					data = NULL;
				}
				else
				{
					*size = (size_t)sz;
					printf("Read: [%s] %d bytes\n",path,(int)(*size));
				}
			}
		}
		close(fd);
	}
	
	return data;
}

#if 0
	#pragma mark -
	#pragma mark -- RESTful support ---
#endif

/***********************************************************************
*
* static int rest ()	- Do RESTful Request
*
*	INPUT:	cls			- Registration option
*			connection	- Context
*			url			- URL requested
*			method		- Type of action needed
*			version		- HTTP version string
*			upload_data	- Passed in data on POST
*			upload_data_size	- Size in bytes of post
*
*	OUTPUT:	Return MHD_YES if successful, MHD_NO if not
*
**********************************************************************/

static int rest (void *cls,
          struct MHD_Connection *connection,
          const char *url,
          HTTP_TYPE method,
          const char *version,
          const char *upload_data, size_t *upload_data_size, void **ptr)
{
	struct MHD_Response *response = NULL;
	int ret = MHD_NO;
	RESTQEL *el;
	int count = 0;
	int fail = 0;
	int size = 0;
	char *data = NULL;
	
	// (1) Send data to client
	// NOTE: On post send read buffer with POST data
	
	if( method == HTTP_POST )
	{
		size = strlen(connection->read_buffer);
		data = connection->read_buffer;
		
	}
	
	if( restcom_master_send( (void *)connection,
						method,
						(char *)url,
						size,
						(char *)data) == 0 )
	{
		// (2) Wait for the result
		
		while( (el = restcom_master_receive((void *)connection)) == NULL )
		{
			usleep( 100000 );		// Sleep for 100 ms
			count = count + 1;
			if( count > 200 )		// Wait 20 seconds
			{
				fail = 1;
				break;
			}
		}
		
		// (3) If OK, build the response
		
		if( fail == 0 )
		{
			response = MHD_create_response_from_data ((size_t)el->size,(void *)el->data, MHD_YES, MHD_NO);
		}
		
		// Return response and status
		
		if( response != NULL )
		{
			ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
			MHD_destroy_response (response);
		}
		
		// (4) Remove received element
		
		restcom_delete_packet( el );

	}
	
	return ret;
}

