#include "pj1.h"
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <signal.h>

/* Local defines */
#define  BUFFER_SIZE			4096
#define  MAX_EXTREQ_LENGTH		100000

#define  HTTP_302_REDIRECT	"HTTP/1.0 302 Moved\r\n"
#define  HTTP_404_ERROR		"HTTP/1.0 404 Not Found\r\n\r\n"  
#define  HTTP_200_OK		"HTTP/1.0 200 OK\r\n"

#define  CONTENT_TYPE_HTML	"Content-Type: text/html\r\n"
#define  CONTENT_TYPE_JPEG	"Content-Type: image/jpeg\r\n"
#define  CONTENT_TYPE_GIF	"Content-Type: image/gif\r\n"


/* Global variables */
int port = -1;			/* server port number to bind */
char* rootdir = NULL;  	/* local root directory path */
int rootdirlen = -1;	/* length of root path string */

/* Function Declarations */
char* GetRequestPath(int fd);
char* GetLocalPath(const char* reqpath); 
int GetLocalType(const char* localpath);
void WriteErrorMessage(int fd); 
void WriteDirResponse(int fd, const char* reqpath, const char* localpath); 
void WriteFileResponse(int fd, const char* localpath);
void WriteResponse(int fd, const char* str); 
void BrokenPipeHandler(int sig);

/* Main Program body */
int main(int argc, char** argv) {

  int fd = -1; 

  /* Parse and verify commandline arguments */
  if (argc != 3) {
	fprintf(stderr, "ERROR: invalid number of arguments\n");
	goto Usage;
  }

  if ((port = atoi(*(++argv))) == 0 || port <= 1024) {
	fprintf(stderr, "ERROR: invalid port number\n");
	goto Usage;
  }

  if (open_directory(*(++argv)) == -1) {
	fprintf(stderr, "ERROR: invalid root directory\n");
	goto Usage;
  }

  else {
	rootdir = *argv;
	rootdirlen = strlen(rootdir);
	if (rootdir[rootdirlen - 1] == '/') {
	  
	  rootdirlen--;
	  rootdir[rootdirlen] = '\0';
	}
	close_directory();
  }

  /* Set broken pipe handler */
  sigset(SIGPIPE, &BrokenPipeHandler);

  /* Display startup configuration information */
  fprintf(stderr, "httpsvr: starting up...\n");
  fprintf(stderr, "httpsvr: using port number %d\n", port);
  fprintf(stderr, "httpsvr: using root directory %s\n\n", rootdir);
  

  /* Open a socket, listen on specified port */  
  fd = setup_server(port);
  os_assert(fd >= 0);


  /* Stay in a loop, serving connections */
  for(;;){
	int nfd = -1;
	char* reqpath = NULL;
	char* localpath = NULL;

	/* block until we get a new connection */
    	nfd = get_next_connection(fd);
    	os_assert(nfd >= 0); 

	/* try to parse the requested path, if we can't (bogus
	   request), close the connection and continue waiting */
	if ((reqpath = GetRequestPath(nfd)) == NULL) {
	  fprintf(stderr, "ERROR: couldn't get request path!\n");
	  WriteErrorMessage(nfd);
	  close(nfd);
	  continue;
	}

	/* map the requested path to a local path */
	localpath = GetLocalPath(reqpath); 
	os_assert(localpath); 

	/* determine the type of content requested, and 
	   write the appropriate HTTP response accordingly */

	switch(GetLocalType(localpath)) {

	case FILE_INFO_ERR_NOT_EXIST:
	case FILE_INFO_ERR_ACCESS_DENIED:
	case FILE_INFO_ERR_UNKNOWN:
	case FILE_OTHER:
	  fprintf(stderr, "returning error for file request %s\n", localpath);
	  WriteErrorMessage(nfd);
	  break;

	case FILE_DIR:
	  fprintf(stderr, "writing directory response for %s\n", localpath);
	  WriteDirResponse(nfd, reqpath, localpath);
	  break;

	case FILE_NORMAL:
	  fprintf(stderr, "writing file response for %s\n", localpath);
	  WriteFileResponse(nfd, localpath);
	  break;

	default:
	  fprintf(stderr, "ERROR: fell through fileinfo switch!\n");
	  os_assert(0);
	}

	/* don't need these after we've served the request */
	free(localpath);
	free(reqpath);

	/* close the connection */
	close(nfd);
  }

  /* close the server-bound port connection and exit */
  close(fd);
  return 0;

  /* print the usage information */
  Usage:
	fprintf(stderr,"usage: httpsvr [portnum] [root]\n");
	fprintf(stderr," [portnum]\tA valid port number, greater than 1024)\n");
	fprintf(stderr," [root]\t\tA valid, readable root directory for server\n");

  return 1;
}


/* 
   void BrokenPipeHandler(int sig)
   
   The signal handler for broken pipes. We just note the
   event on the console and ignore it... 
*/

void BrokenPipeHandler(int sig) {
  fprintf(stderr, "Received SIGPIPE signal for broken pipe!\n");
}

/* 
   char* GetRequestPath(int fd) 
 
     This function reads the first line of the HTTP request stream 
   using a read call with the provided file descriptor, and 
   attempts to extract and return a file path request. NULL is 
   returned if no path could be extracted (i.e. improperly formatted
   /bogus request). 

*/
char* GetRequestPath(int fd) {

  char buffer[BUFFER_SIZE];
  char* buffptr = buffer;
  char* reqpath = NULL;
  unsigned char doneflag = 0;
  int extchars = 0;

  while (read(fd, buffptr, 1) == 1 
		 && *buffptr != '\n'
		 && ++buffptr - buffer < BUFFER_SIZE) {} 
  
  
  *buffptr = '\0';
  fprintf(stderr, "received HTTP request:\n%s\n", buffer);

  /* the first line of the HTTP request should have the form:
	 "GET [requested_path] HTTP/x.x"; we verify that this is
	 indeed the case... */
  if (   strncmp(buffer, "GET ", 4) 
	  || strncmp(buffptr - 10, " HTTP/", 5)) {

	fprintf(stderr, "ERROR: improperly formatted request:\n");
	fprintf(stderr, "%s\n", buffer);
	return NULL;
  }

  *(buffptr - 10) = '\0';
  buffptr = buffer + 4;
  
  fprintf(stderr, "extracted request path: %s\n", buffptr);

  reqpath = malloc((strlen(buffptr) + 1) * sizeof(char));
  os_assert(reqpath);

  strcpy(reqpath, buffptr);
  
  /* Now we read off the rest of the headers until we 
	 hit an empty "\r\n" line */

  buffptr = buffer;
  *buffptr = '\0';
  while (read(fd, buffptr, 1) == 1 && extchars < MAX_EXTREQ_LENGTH) {

	extchars++;
	if (*buffptr == '\n') {
	  buffptr++;
	  read(fd, buffptr, 2);
	  *(buffptr + 2) = '\0';
	  if (!strcmp(buffer, "\n\r\n")) {
		doneflag = 1;
		break;
	  }
	}
	
	/* reset the buffer pointer */
	buffptr = buffer;
  }

  /* If we read the empty "\r\n" line, the flag will 
	 be set, so we return the extracted request path */
  if (doneflag) return reqpath;

  /* Otherwise, the request was improperly formatted, 
	 so we return NULL */
  else {
	return NULL;
  }
}

/*
  char* GetLocalPath (const char* reqpath) 

  Maps a request path to a qualified local file path 
  by prepending the web server root directory to the
  requested path. The function returns the mapped 
  local path. 
*/
char* GetLocalPath (const char* reqpath) {
  
  char* localpath = NULL;
  int reqpathlen = -1;
  
  os_assert(reqpath);

  reqpathlen = strlen(reqpath);
  
  localpath = malloc((rootdirlen + reqpathlen + 1) * sizeof(char));
  os_assert(localpath); 
  
  /* Prepend the rootdirectory to the requested path to 
	 form the full local path for the request */
  strcpy(localpath, rootdir);
  strcat(localpath, reqpath); 
  
  fprintf(stderr, "mapped local path: %s\n", localpath); 

  return localpath;
}

/*	
  int GetLocalType (const char* localpath) 

  Returns an integer value indicating the type of the file
  specified by the provided path string. 

  Possible return values:
   -3  	FILE_INFO_ERR_UNKNOWN		unknown error accessing localpath 
   -2	FILE_INFO_ERR_ACCESS_DENIED	access denied for localpath  
   -1   FILE_INFO_ERR_NOT_EXIST		localpath does not exist	   
    0   FILE_DIR			localpath is a directory  
    1   FILE_NORMAL			localpath is a regular file 
    2   FILE_OTHER			localpath is a special file 
*/
int GetLocalType (const char* localpath) {
  int ret = -1; 
  SDirEntry entry;

  /* if get_file_info returns a non-zero value, it's one of the error
	 codes (-3, -2, -1) so we return it */
  if (ret = get_file_info(localpath, &entry)) { 
	fprintf(stderr, "error %d retrieving file info for %s\n", ret, localpath);
	return ret; 
  }
  
  fprintf(stderr, "  file entry: %d\t%s\n", entry.fileSize, entry.fileName);

  /* Otherwise, we return the fileType field 
	 set in the retrieved SDirEntry struct */
  return entry.fileType;
}


/*
  void WriteDirResponse (int fd, const char* reqpath, const char* localpath)
  
    Write an appropriate HTTP response when the requested path specifies
  a directory. We check to see if an index.html file exists in the requested
  directory. If it does, we return the contents of that file; otherwise, 
  we print an HTML file containing a directory listing, and we provide relative
  hyperlinks to each file in the directory. 
*/
void WriteDirResponse (int fd, const char* reqpath, const char* localpath) {

  int ret = -1;
  int localpathlen = -1;
  char* indexpath = NULL;
  SDirEntry entry;

  os_assert(reqpath);
  os_assert(localpath); 

  localpathlen = strlen(localpath);
  
  /* Directory requests must end with a / character;
	 if the path does not end with a / we return a 
	 302 response with the correct path */

  if (reqpath[strlen(reqpath) - 1] != '/') {
	fprintf(stderr, "redirecting to %s/ for request %s\n", reqpath, reqpath);
	
	WriteResponse(fd, HTTP_302_REDIRECT);
	WriteResponse(fd, "Location: "); 
	WriteResponse(fd, reqpath);
	WriteResponse(fd, "/\r\n");
	WriteResponse(fd, "\r\n");
	return;
  }
  
  /* Next, we check if there's a readable index.html in 
	 the specified directory, if so, we return its contents */
  indexpath = malloc((localpathlen + 11) * sizeof(char)); 
  strcpy(indexpath, localpath);
  strcat(indexpath, "index.html");

  fprintf(stderr, "checking index path %s\n", indexpath);

  ret = GetLocalType(indexpath);

  /* If index.html exists and is readable, return its contents */
  if (ret == FILE_NORMAL) {
	WriteFileResponse(fd, indexpath); 
  }

  /* If index.html exists but we can't read it, or if it's 
	 a directory or special file, return an error */
  else if (ret == FILE_INFO_ERR_ACCESS_DENIED 
		   || ret == FILE_INFO_ERR_UNKNOWN
		   || ret == FILE_OTHER
		   || ret == FILE_DIR) {
	WriteErrorMessage(fd); 
  }

  /* otherwise, index.html does not exist, spit out a directory listing */
  else if (ret == FILE_INFO_ERR_NOT_EXIST && open_directory(localpath) == 0) {
	char* filename = NULL; 

	fprintf(stderr, "returning directory listing for %s\n", localpath); 

	/* First, print the header, specify HTML content */
	WriteResponse(fd, HTTP_200_OK);
	WriteResponse(fd, CONTENT_TYPE_HTML);
	WriteResponse(fd, "\r\n");

	/* Next, write some HTML content to indicate that this is a 
	   directory listing for the requested path */
	WriteResponse(fd, "<HTML><HEAD><TITLE>Index of ");
	WriteResponse(fd, reqpath); 
	WriteResponse(fd, "</TITLE></HEAD><BODY>\n");
	WriteResponse(fd, "<h1>Index of ");
	WriteResponse(fd, reqpath);
	WriteResponse(fd, "</h1><hr>\n");
	
	WriteResponse(fd, "<TABLE CELLSPACING=\"5\">");

	/* Now loop over all the entries in the directory, 
	   print out a hyperlink to each file (directories 
	   are displayed using italics) */
	while ((filename = read_an_entry()) != NULL) {
	  int localtype = -1;
	  char* localfilename = NULL; 

	  if (!strcmp(filename, ".")) continue;

	  localfilename = malloc((localpathlen + strlen(filename) + 1) 
			 * sizeof(char));
	  os_assert(localfilename);

	  strcpy(localfilename, localpath);
	  strcat(localfilename, filename); 
	  localtype = GetLocalType(localfilename);
	  
	  WriteResponse(fd, "<TR><TD>");

	  if (localtype == FILE_DIR) {
		WriteResponse(fd, "<IMG SRC=\"/icons/folder.gif\">");
		WriteResponse(fd, "</TD><TD><A HREF=\"");
		WriteResponse(fd, filename);
		WriteResponse(fd, "/\"><I>");
		WriteResponse(fd, filename); 
		WriteResponse(fd, "/</I></A></TD></TR>");
	  }
	  else if (localtype == FILE_NORMAL) {
		WriteResponse(fd, "<IMG SRC=\"/icons/file.gif\">");
		WriteResponse(fd, "</TD><TD><A HREF=\"");
		WriteResponse(fd, filename);
		WriteResponse(fd, "\">");
		WriteResponse(fd, filename);
		WriteResponse(fd, "</A></TD></TR>");
	  }
	  else {
		fprintf(stderr, "Omitting directory listing for FILE_OTHER: %s\n", 
				localfilename);
	  }

	  free(localfilename);
	}
	
	WriteResponse(fd, "</TABLE><HR></HTML>\n");

	/* We're done with the directory, so close it */
	close_directory();
  }
  
  /* If we got here, we couldn't open the directory, 
	 so we print the 404 error message and return */
  else {
	WriteErrorMessage(fd);
  }

  /* Don't need this anymore */
  free (indexpath);
}

/* 
   void WriteFileResponse (int fd, const char* localpath) 

     This function prints out an HTTP response consisting 
   of a bare-bones header, with the HTTP/1.0 200 OK status 
   message, a content-type if known, and the contents of 
   the requested local file. 
     The content type is determined by file extension, if the 
   localpath string ends in .jpeg, .jpg, .html, .htm, or .gif, 
   then we return one of three content types: image/jpeg, 
   image/gif, or text/html. 
*/
   
void WriteFileResponse (int fd, const char* localpath) {
  int localpathlen = -1;
  int localfd = -1;
  int ret = -1;
  char fileBuff[BUFFER_SIZE];
  
  os_assert(localpath);

  if ((localfd = open(localpath, O_RDONLY)) == -1) {
	WriteErrorMessage(fd);
	return;
  } 
  
  localpathlen = strlen(localpath);
  WriteResponse(fd, HTTP_200_OK);

  if ((localpathlen > 5 && !strcmp(&localpath[localpathlen-5], ".html")) 
	  || (localpathlen > 4 && !strcmp(&localpath[localpathlen-4], ".htm"))) {
	fprintf(stderr, "setting type text/html for %s\n", localpath);
	WriteResponse(fd, CONTENT_TYPE_HTML);
  }
  else if ((localpathlen > 5 && !strcmp(&localpath[localpathlen-5], ".jpeg"))
		   || (localpathlen > 4 && !strcmp(&localpath[localpathlen-4], ".jpg"))) {
	fprintf(stderr, "setting type image/jpeg for %s\n", localpath);
	WriteResponse(fd, CONTENT_TYPE_JPEG);
  }
  else if (localpathlen > 4 && !strcmp(&localpath[localpathlen-4], ".gif")) {
	fprintf(stderr, "setting type image/gif for %s\n", localpath);
	WriteResponse(fd, CONTENT_TYPE_GIF);
  }
  WriteResponse(fd, "\r\n");

  while ((ret = read(localfd, fileBuff, BUFFER_SIZE)) > 0) {
	if (write(fd, fileBuff, ret) < 0) break; 
  }
}

/* 
   WriteResponse (int fd, const char* str) 

   This leaf function is used to write the specified
   string in an HTTP response. 
*/
void WriteResponse (int fd, const char* str) {
  os_assert(str); 
  write(fd, str, strlen(str));
}

/*
  WriteErrorMessage (int fd)

  Writes an HTTP 404 Error response on the connection. 
*/
void WriteErrorMessage (int fd) {
  WriteResponse(fd, HTTP_404_ERROR);
}


