// Linux tiny HTTP server.
// Nicole Hamilton  nham@umich.edu

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <pthread.h>
#include <fcntl.h>
#include <iostream>
#include <sys/stat.h>
#include <sys/types.h>
#include <string.h>
#include <string>

#include <cassert>
//using namespace std;
//  Multipurpose Internet Mail Extensions (MIME) types

struct MimetypeMap
	{
	const char *Extension,
			*Mimetype;
	};

const MimetypeMap MimeTable[] =
		{
				// List of some of the most common MIME types.
				// https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Complete_list_of_MIME_types
				".aac", "audio/aac",
				".abw", "application/x-abiword",
				".arc", "application/octet-stream",
				".avi", "video/x-msvideo",
				".azw", "application/vnd.amazon.ebook",
				".bin", "application/octet-stream",
				".bz", "application/x-bzip",
				".bz2", "application/x-bzip2",
				".csh", "application/x-csh",
				".css", "text/css",
				".csv", "text/csv",
				".doc", "application/msword",
				".docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
				".eot", "application/vnd.ms-fontobject",
				".epub", "application/epub+zip",
				".gif", "image/gif",
				".htm", "text/html",
				".html", "text/html",
				".ico", "image/x-icon",
				".ics", "text/calendar",
				".jar", "application/java-archive",
				".jpeg", "image/jpeg",
				".jpg", "image/jpeg",
				".js", "application/javascript",
				".json", "application/json",
				".mid", "audio/midi",
				".midi", "audio/midi",
				".mpeg", "video/mpeg",
				".mpkg", "application/vnd.apple.installer+xml",
				".odp", "application/vnd.oasis.opendocument.presentation",
				".ods", "application/vnd.oasis.opendocument.spreadsheet",
				".odt", "application/vnd.oasis.opendocument.text",
				".oga", "audio/ogg",
				".ogv", "video/ogg",
				".ogx", "application/ogg",
				".otf", "font/otf",
				".png", "image/png",
				".pdf", "application/pdf",
				".ppt", "application/vnd.ms-powerpoint",
				".pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation",
				".rar", "application/x-rar-compressed",
				".rtf", "application/rtf",
				".sh", "application/x-sh",
				".svg", "image/svg+xml",
				".swf", "application/x-shockwave-flash",
				".tar", "application/x-tar",
				".tif", "image/tiff",
				".tiff", "image/tiff",
				".ts", "application/typescript",
				".ttf", "font/ttf",
				".vsd", "application/vnd.visio",
				".wav", "audio/x-wav",
				".weba", "audio/webm",
				".webm", "video/webm",
				".webp", "image/webp",
				".woff", "font/woff",
				".woff2", "font/woff2",
				".xhtml", "application/xhtml+xml",
				".xls", "application/vnd.ms-excel",
				".xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
				".xml", "application/xml",
				".xul", "application/vnd.mozilla.xul+xml",
				".zip", "application/zip",
				".3gp", "video/3gpp",
				".3g2", "video/3gpp2",
				".7z", "application/x-7z-compressed"
		};

const char *Mimetype( const std::string filename )
	{
	const char *begin = filename.c_str( ),
			*p = begin + filename.length( ) - 1;
	// Scan back from the end for an extension.
	while ( p >= begin && *p != '.' )
		p--;
	if ( *p == '.' )
		{
		// Found an extension.  Skip over the dot, then
		// binary search for a matching mimetype.

		p++;
		int i = 0, j = sizeof( MimeTable ) / sizeof( MimetypeMap ) - 1;
		while ( i <= j )
			{
			int mid = (i + j) / 2,
					compare = strcasecmp( p, MimeTable[ mid ].Extension + 1 );
			if ( compare == 0 )
				return MimeTable[ mid ].Mimetype;
			if ( compare < 0 )
				j = mid - 1;
			else
				i = mid + 1;
			}
		}

	// Anything not matched is an "octet-stream", treated
	// as an unknown binary, which can be downloaded.
	return "application/octet-stream";
	}

char *ParseGetRequest( char *request )
	{
	// If it's a GET request, null-terminate and
	// return the URL.

	if ( strncmp( request, "GET ", 4 ) == 0 )
		{
		// Skip the get and any additional white space.
		for ( request += 4; *request == ' '; request++ );

		// Now have the start of the URL.
		char *p;
		for ( p = request; *p != ' '; p++ );
		*p = 0;  // null-terminate the URL.

		return request;   // Return the URL.

		}
	return nullptr;   // Not a GET request.
	}


char *RootDirectory;


off_t FileSize( int f )
	{
	struct stat fileInfo;
	fstat( f, &fileInfo );
	return fileInfo.st_size;
	}


void *Talk( void *p )
	{
	// Look for a GET message, then reply with the
	// requested file.

	char buffer[10240];
	int bytes;
	int *pp = (int *) p, s = *pp;
	delete pp;

	std::cout << "Starting recv, s = " << s << std::endl;

	while ((bytes = recv( s, buffer, sizeof( buffer ) - 1, 0 )) > 0 )
		{
		buffer[ bytes ] = 0;
		std::cout << "Request received, s = " << s << std::endl;
		std::cout << buffer << std::endl;
		char *fullUrl = ParseGetRequest( buffer );
		std::string potentialSearch = std::string( fullUrl, strlen( fullUrl ));
		if ( fullUrl )
			{
			std::cout << "Requested url = " << fullUrl << std::endl;
			std::string search = "/search";
			bool isSearch = true;
			for ( int i = 0; i < search.size( ); i++ )
				{
				if ( potentialSearch[ i ] != search[ i ] )
					isSearch = false;
				break;

				}
			if ( isSearch )
				{


				std::cout << "---Making a search GET---" << std::endl;
				std::cout << potentialSearch << std::endl;
				}
			else
				{

				std::string completePath = "";
				std::string Root = std::string( RootDirectory, strlen( RootDirectory ));
				std::string cwd = getenv( "PWD" );
				completePath = cwd + Root + fullUrl;


				//completePath += fullUrl;
				std::cout << "Reqested file = " << completePath << std::endl;

				int f = open( completePath.c_str( ), O_RDONLY );

				if ( f != -1 )
					{
					off_t filesize = FileSize( f );
					std::string okMessage = "HTTP/1.1 200 OK\r\n"
							"Content-Length: ";
					okMessage += std::to_string( filesize );
					okMessage += "\r\nConnection: close\r\nContent-Type: ";
					okMessage += Mimetype( completePath );
					okMessage += "\r\n\r\n";

					std::cout << "Sending" << std::endl;
					std::cout << okMessage;

					send( s, okMessage.c_str( ), okMessage.length( ), 0 );

					while ( bytes = read( f, buffer, sizeof( buffer )))
						send( s, buffer, bytes, 0 );

					close( f );
					}
				else
					{
					std::string fileNotFound = "HTTP/1.1 404 Not Found\r\n"
							"Content-Length: 0\r\n"
							"Connection: close\r\n\r\n";
					send( s, fileNotFound.c_str( ), fileNotFound.length( ), 0 );
					}
				}
			}
		}
	close( s );
	}


void PrintAddress( const sockaddr_in *s, const size_t saLength )
	{
	const struct in_addr *ip = &s->sin_addr;
	uint32_t a = ntohl( ip->s_addr );

	std::cout << "Host address length = " << saLength << " bytes" << std::endl;
	std::cout << "Family = " << s->sin_family <<
				 ", port = " << ntohs( s->sin_port ) <<
				 ", address = " << (a >> 24) << '.' <<
				 ((a >> 16) & 0xff) << '.' <<
				 ((a >> 8) & 0xff) << '.' <<
				 (a & 0xff) << std::endl;
	}


int main( int argc, char **argv )
	{
	if ( argc != 3 )
		{
		std::cerr << "Usage:  LinuxTinyServer port rootdirectory" << std::endl;
		return 1;
		}

	int port = atoi( argv[ 1 ] );
	RootDirectory = argv[ 2 ];

	// Create TCP/IP sockets for listening and talking.

	struct sockaddr_in listenAddress, talkAddress;
	socklen_t talkAddressLength;
	int listenSocket, talkSocket;

	memset( &listenAddress, 0, sizeof( listenAddress ));
	memset( &talkAddress, 0, sizeof( talkAddress ));

	listenAddress.sin_family = AF_INET;
	listenAddress.sin_port = htons( port );
	listenAddress.sin_addr.s_addr = htonl( INADDR_ANY );

	listenSocket = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );
	assert( listenSocket != -1 );

	int bindResult = bind( listenSocket, (sockaddr *) &listenAddress, sizeof( listenAddress ));
	assert( bindResult == 0 );

	int listenResult = listen( listenSocket, SOMAXCONN );
	assert( listenResult == 0 );

	std::cout << "Listening" << std::endl;
	PrintAddress( &listenAddress, sizeof( listenAddress ));

	while ((talkAddressLength = sizeof( talkAddress ),
			talkSocket = accept( listenSocket, (sockaddr *) &talkAddress,
										&talkAddressLength )) && talkSocket != -1 )
		{
		std::cout << "Connection accepted, talkSocket = " << talkSocket << std::endl;
		PrintAddress( &talkAddress, talkAddressLength );
		pthread_t child;
		pthread_create( &child, nullptr, Talk, new int( talkSocket ));
		}

	close( listenSocket );
	}