// webserver.c


#include "./fileSystem.h"

#include "./text.h"

#include "./mimeTypes.h"



#include <arpa/inet.h>

#include <errno.h>

#include <cache.h>


#include <stdio.h>

#include <string.h>

#include <sys/socket.h>

#include <unistd.h>

#include <request.h>

#include <headers.h>


#include <openssl/err.h>

#include <openssl/ssl.h>


#include <sys/resource.h>




class http{

	int socket;

	struct sockaddr_in * hostAddress	= malloc( sizeof( struct sockaddr_in ) );

	int hostAddresslength				= sizeof( struct sockaddr_in );

	struct fileSystem * filesystem		= new fileSystem();

	struct mimeTypes * mimetypes		= new mimeTypes();	

	struct headerManager * headers		= new headerManager();

	int useSSL							= -1;


	SSL_CTX * sslContext;



	void ( * requestCallback )( struct request * requestInstance,  struct text * response );


	void createServer( void ( * requestCallback )( request * req, text * response ) ) {

		this->requestCallback	= requestCallback;


		if( this->useSSL == 1 ) {

			this->initializeOpenSSL();

		}

		printf("after initializeOpenSSL\n\n");

	}

	initializeOpenSSL() {

		// OpenSSL 1.1.0 or above initializes by itself
		//SSL_load_error_strings();
		//ssl_load_ciphers();
		//OpenSSL_add_all_algorithms();

		/* Lets get nice error messages */
		SSL_load_error_strings();

		OpenSSL_add_ssl_algorithms();

		SSLeay_add_ssl_algorithms();

		return;

	}

	int listen( int port ) {

		this->socket 		= socket( AF_INET, SOCK_STREAM, 0 );
	
		int iSetOption 		= 1;

		setsockopt( this->socket, SOL_SOCKET, SO_REUSEADDR, ( char * ) & iSetOption, sizeof( iSetOption ) );

		if ( this->socket == -1 ) {

		    perror("webserver (socket)");

		   exit( 0 );

		} else {

			printf("socket created successfully\n");

		}

		
		// Create the address to bind the socket to
		this->hostAddress->sin_family 			= AF_INET;

		this->hostAddress->sin_port 			= htons( port );

		this->hostAddress->sin_addr.s_addr 		= htonl( INADDR_ANY );


		// Bind the socket to the address
		if ( bind( this->socket, ( struct sockaddr * ) this->hostAddress, this->hostAddresslength ) != 0 ) {

			perror("webserver (bind)");

			exit( 0 );

		} else {

			printf("socket successfully bound to address\n");

		}

		// Listen for incoming connections
		if ( listen( this->socket, SOMAXCONN ) != 0 ) {

			perror("webserver (listen)");

			exit( 0 );

		} else {
		
			printf("server listening for connections\n");

		}



		for (;;) {

			this->acceptConnection( );

		}


		//this->DestroySSL();

		printf("exit");


		return 0;

	}

	char * getExtension( char * path ) {

		array * parts		= path->split(".");

		int count			= parts->length();

		return parts->get( count - 1 );

	}

	logRequest( request	* requestInstance ) {

		printf("server address:		[%s:%u]\n", 	requestInstance->address, requestInstance->port );
	
		printf("mimeType:		%-30s \n", 			requestInstance->mimeType);

		printf("header:			%-30s \n",			requestInstance->extension );

		printf("method:			%-30s		 \n", 	requestInstance->method );

		printf("version:		%-30s		 \n\n", requestInstance->version );

	}

	acceptConnection(  ) {

		fileSystem * filesystem 	= this->filesystem;

		mimeTypes * mimetypes		= this->mimetypes;

		text	* response			= new text("");


		
		char buffer[ 4096 ];

		// Create client address
		struct sockaddr_in client_addr;

		int client_addrlen 	= sizeof(client_addr);

		// Accept incoming connections
		int socketConnection 		= accept( 	this->socket, 
										(struct sockaddr *)this->hostAddress,
		                  				( socklen_t * ) & this->hostAddresslength );


/*

		struct rusage r_usage;
		
		getrusage(RUSAGE_SELF,&r_usage);
		// Print the maximum resident set size used (in kilobytes).

		printf("Memory usage: %ld kilobytes\n\n",r_usage.ru_maxrss);

		free( r_usage );
*/

		SSL * ssl  = NULL;

		if( this->useSSL == 1 ) {

			const SSL_METHOD * method	= SSLv23_server_method();

			this->sslContext = SSL_CTX_new( method );

			if ( this->sslContext == NULL ) {

				printf("Error loading SSL_CTX_NEW '%s'\n\n", stderr);

			}

			SSL_CTX_set_options( this->sslContext, SSL_OP_SINGLE_DH_USE );

			int use_cert 	= SSL_CTX_use_certificate_file( this->sslContext, "/etc/letsencrypt/live/unifyjs.org/fullchain.pem" , SSL_FILETYPE_PEM );

			int use_prv 	= SSL_CTX_use_PrivateKey_file( this->sslContext, "/etc/letsencrypt/live/unifyjs.org/privkey.pem", SSL_FILETYPE_PEM );

			if( use_cert != 1 ) {

				printf( "error:	SSL_CTX_use_certificate_file\n\n" );

			}

			if( use_prv != 1 ) {

				printf( "error:	SSL_CTX_use_PrivateKey_file\n\n" );

			}

			if ( !SSL_CTX_check_private_key(this->sslContext) ) {

				printf("Private key does not match the certificate public key\n");

				// initiate page describing error.
			}

			ssl = SSL_new( this->sslContext );

			SSL_set_fd( ssl, socketConnection );

			//Here is the SSL Accept portion.  Now all reads and writes must use SSL
			int ssl_err = SSL_accept( ssl ); // SSL routines:SSL_UNDEFINED_FUNCTION:called a function you should not call

			//printf ("SSL connection using %s\n", SSL_get_cipher( ssl ) );

			//ERR_print_errors_fp( stderr );

			if( ssl_err == 0 ){

				printf("SSL_accept returned zero\n");

			}


			int n;

			if( ssl_err < 0 ) {

				int err;

				if( ( err = SSL_get_error(ssl,n ) ) == SSL_ERROR_WANT_READ) {

					printf("SSL_accept wants more data\n");

					return ;

				}

				exit(7);

			}


/*

			printf ("SSL connection using %s\n", SSL_get_cipher (this->sslContext));

			#define CHK_NULL(x) if ((x)==NULL) exit (1)

			X509*    client_cert;

			char*    str;

			// Get client's certificate (note: beware of dynamic allocation) - opt 

			client_cert = SSL_get_peer_certificate (this->sslContext);

			if ( client_cert != NULL ) {

				printf ("Client certificate:\n");

				str = X509_NAME_oneline (X509_get_subject_name (client_cert), 0, 0);

				CHK_NULL(str);

				printf ("\t subject: %s\n", str);

				OPENSSL_free (str);

				str = X509_NAME_oneline (X509_get_issuer_name  (client_cert), 0, 0);

				CHK_NULL(str);

				printf ("\t issuer: %s\n", str);

				OPENSSL_free (str);

				X509_free (client_cert);

			} else {

				printf ("Client does not have certificate.\n");

			}



			if ( socketConnection == -1 ) {

				perror("webserver (accept)");

				return;

			}
			*/

			//printf("connection accepted2\n\n");



		} else {
			printf("connection accepted\n\n");

			// Get client address
			int sockn = getsockname(	socketConnection, 
										( struct sockaddr * ) &client_addr,
			                    		( socklen_t * ) &client_addrlen);


			if ( sockn == -1 ) {

				perror("webserver (getsockname)");

			}

		}

		int valread;

		if( this->useSSL == 1 ) { 

			valread = SSL_read( ssl, buffer, 4096 - 1 );

		} else {

			printf("read without ssl\n\n");

			int valread = read( socketConnection, buffer, 4096 -1 );
			
			if ( valread == -1 ) {

				perror("webserver (read)");

				//return;

			}

		}


		if ( valread == -1 ) {

			perror("webserver (read)");


		}


		request	* requestInstance	= new request();


		sscanf( buffer, "%s %s %s", requestInstance->method, requestInstance->url, requestInstance->version );


		requestInstance->address		= inet_ntoa( client_addr.sin_addr );

		requestInstance->port			= ntohs( client_addr.sin_port );

		requestInstance->extension		= this->getExtension( requestInstance->url );

		requestInstance->mimeType		= mimetypes->getByExtension( requestInstance->extension );

		this->requestCallback( requestInstance, response );




		//printf("response:		%s",response->value);

		int writeResponse;

		if( this->useSSL == 1 ) { 

			writeResponse = SSL_write( ssl, response->value, response->length );

		} else {

			// Write to the socket
			int writeResponse = write( socketConnection, response->value, response->length );

			printf("close connection");

			if ( writeResponse == -1 ) {

				perror("webserver (write)");

				return;

			}
		}

		if ( writeResponse == -1 ) {

			perror("webserver (write)");

			return;

		} else {

			//printf("written a response\n");

		}


		//free( buffer );

		//SSL_free( writeResponse );

		//response->free();

		//requestInstance->free();

		close( socketConnection );

		//SSL_free( response->value );

		

		// SSL_CTX_free( this->sslContext );
		// close( this->socket );		
			

	}

	void dump_buffer(void *buffer, int buffer_size)
	{
		int i;

		for(i = 0;i < buffer_size;++i){

			printf( "%c", (  (char *) buffer )[i]);

		}

	}

}

void abort() {


}
//kill -9 $(lsof -t -i tcp:8080)

