#include <iostream>
#include <string>
//#include <thread>

#include <openssl/bio.h>
#include <openssl/ssl.h>
#include <openssl/err.h>

#define K_MAX_MB 8
#define K_VER_MAJOR 0
#define K_VER_MINOR 1
#define K_FN_LENGTH 5

//int http_redirect(const char* http_port, const char* https_port) {
//	// ** TODO: untested
//	// ** TODO: sync process exits, thread monitoring...
//	// redirects http requests on port http to https
//	BIO* s_accept = BIO_new_accept(http_port);
//	BIO_set_bind_mode(s_accept, BIO_BIND_REUSEADDR);
//	if (BIO_do_accept(s_accept) != 1) {
//		std::cout << "\ncreate HTTP socket attempt failed.\n" << ERR_error_string(ERR_get_error(), NULL) << "\nfatal error. exiting...\n\n";
//		return -1;
//	}
//	while (true) {
//		if (BIO_do_accept(s_accept) != 1) { 
//			std::cout << "\nlisten HTTP socket attempt failed!\n" << ERR_error_string(ERR_get_error(), NULL) << "\nretrying...\n\n";
//			continue;
//		} BIO* s_socket = BIO_pop(s_accept);
//		const char* reply = "HTTP/1.1 301 Moved Permanently\r\nContent-Type: text/html\r\nConnection: close\r\nLocation: https:///\r\nContent-Length: 9\r\n\r\nuse https";
//		BIO_write(s_socket, reply, strlen(reply));
//		BIO_reset(s_socket);
//		BIO_free(s_socket);
//	}
//	BIO_free(s_accept);
//	return 0;
//}

int rand_name(char* out, int length) {
	char* buf = new char[length+1]; buf[length] = 0x00;
	srand(static_cast<int>(time(NULL))); 
	for (int i = 0; i < length; i++) {
		int z = -1 + (rand() % (1 - -1 + 1));
		if (z == 1 || z == -1) buf[i] = 65 + (rand() % (122 - 97 + 1));
		else buf[i] = 97 + (rand() % (90 - 65 + 1));
	} int ret = strcpy(out, buf); // assumed your buffer is large enough for 0x00 !!!
	delete[] buf; return ret;
}

int main() {
	std::cout << "kaoru, version " << K_VER_MAJOR << '.' << K_VER_MINOR << "\n\n";
	// ** TODO: cmdline arguments for http_port, https_port, listen interfaces etc

	// start http:80 redirector
	// http_redirect("80", "443");

	// listen
	// setup
	SSL_load_error_strings();
	SSL_CTX* s_ctx = SSL_CTX_new(TLS_server_method());
	SSL_CTX_set_min_proto_version(s_ctx, TLS1_2_VERSION);
	SSL_CTX_set_options(s_ctx, SSL_OP_CIPHER_SERVER_PREFERENCE | SSL_OP_NO_RENEGOTIATION);
	// insert cert code here
	SSL_CTX_use_certificate_chain_file(s_ctx, "cert.pem");
	SSL_CTX_use_PrivateKey_file(s_ctx, "key.pem", SSL_FILETYPE_PEM);
	SSL_CTX_set_verify(s_ctx, SSL_VERIFY_NONE, NULL);

	//SSL* s_ssl = NULL;
	//BIO* s_bssl = BIO_new_ssl(s_ctx, 0);
	BIO* s_accept = BIO_new_accept("420");
	BIO_set_bind_mode(s_accept, BIO_BIND_REUSEADDR);

	//BIO_make_bio_pair(s_accept, s_bssl);
	//BIO_get_ssl(s_bssl, &s_ssl);
	//SSL_set_read_ahead(s_ssl, 0);
	//ERR_clear_error();

	if (BIO_do_accept(s_accept) != 1) {
		std::cout << "\ncreate socket attempt failed.\n" << ERR_error_string(ERR_get_error(), NULL) << "\nfatal error. exiting...\n\n";
		return -1;
	} std::cout << "created ssl socket, listen on " << BIO_get_accept_name(s_accept) << ' ' << BIO_get_accept_port(s_accept) << "\n\n";

net_accept:
	if (BIO_do_accept(s_accept) != 1) {
		std::cout << "\nlisten socket attempt failed!\n" << ERR_error_string(ERR_get_error(), NULL) << "\nretrying...\n\n";
		goto net_accept;
	} // connection is made

	BIO* s_socket = BIO_pop(s_accept);
	SSL* s_ssl = SSL_new(s_ctx); SSL_set_bio(s_ssl, s_socket, s_socket);
	if (SSL_accept(s_ssl) != 1) {
		std::cout << "\nssl handshake attempt failed.\n" << ERR_error_string(ERR_get_error(), NULL) << "\nclosing socket...\n\n";
		SSL_free(s_ssl);
		goto net_accept;
	} std::cout << "Established SSL connection with " << BIO_get_peer_name(s_accept) << ' ' << BIO_get_peer_port(s_accept) << '\n';

	while (true) { // begin the request/response loop
		std::string buf_s = ""; size_t len = 0; unsigned char* b_cache = NULL; size_t b_len = 0;
		while (true) { static bool first = true;
			// header
			char* buf = new char[201];
			size_t len_n = 0;
			if (SSL_read_ex(s_ssl, buf, 200, &len_n) != 1) {
				if (BIO_eof(s_socket)) { BIO_reset(s_socket); goto net_accept; } // reset
				int err = ERR_get_error();
				// if (err == SSL_ERROR_WANT_READ) continue; // wait 
				std::cout << "read attempt failed.\n" << ERR_error_string(ERR_get_error(), NULL) << "\nfatal error. exiting...\n";
				delete[] buf;
				return -1;
			}
			buf[200 - (200 - len_n)] = 0x00;
			buf_s.append(buf);
			delete[] buf;
			len = len + len_n;
			
			if (first) { first = false; std::cout << "Data received, length read: " << len_n; }
			else std::cout << ' ' << len_n;

			// if (strcmp(buf_s.c_str() + (buf_s.size() - 4), "\r\n\r\n") == 0) break; // end http header
			const char* pos = strstr(buf_s.c_str(), "\r\n\r\n");
			if (pos != NULL) {
				if (((buf_s.c_str()+len) - pos) > 4) {
					// split header, cache body
					b_len = (buf_s.c_str() + len) - pos - 4;
					b_cache = new unsigned char[b_len + 1];
					// strcpy_s(b_cache, b_len + 1, pos+4);
					memcpy(b_cache, pos+4, b_len);
				}
				buf_s = buf_s.substr(0, ((pos+4) - buf_s.c_str())-1); 
				first = true;
				break; 
			}
		} std::cout << '\n';

		std::cout << "request-> " << buf_s.substr(0, buf_s.find("\r\n")) << '\n';

		// handle request
		if (strncmp(buf_s.c_str(), "GET /", 5) == 0) { // GET request
			if (strncmp(buf_s.c_str(), "GET / HTTP/1.", 13) == 0) { // home page
				std::cout << "HTTP request for / detected, serving home page... \n";
				const char* header = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nConnection: close\r\nContent-Length: 236\r\n\r\n";
				const char* body = "<html><head><script>function p(){let e=document.getElementById(\"f\").files[0];var r=new XMLHttpRequest();r.open(\"POST\",\"/p\",true);r.send(e);}</script></head>ayo<br><input id=\"f\" type=\"file\"/><br><button onclick=\"p()\">post</button></html>";
				SSL_write(s_ssl, header, strlen(header));
				SSL_write(s_ssl, body, strlen(body));
			} else {
				if (strncmp(buf_s.c_str(), "GET /s/", 7) == 0) {
					const char* s_s = strstr(buf_s.c_str(), "GET /s/");
					const char* s_e = strstr(buf_s.c_str(), "HTTP/1.");
					if ( s_s == NULL || s_e == NULL) { std::cout << "malformed request detected, ignoring and closing... \n"; goto net_clean; }
					int req_len = (s_e - 1) - (s_s + 7);
					if (req_len <1) { // 403 forbidden to view root of hosted files
						std::cout << "forbidden http get request detected, 403ing and closing... \n";
						const char* reply = "HTTP/1.1 403 Forbidden\r\nContent-Type: text/html\r\nConnection: close\r\nContent-Length: 16\r\n\r\n<html>403</html>\r\n\r\n";
						SSL_write(s_ssl, reply, strlen(reply));
						goto net_clean;
					}
					char* fn = new char[req_len+1]; fn[req_len] = 0x00;
					strncpy(fn, buf_s.c_str()+7, req_len);
					std::cout << "request for file \"" << fn << "\"\n";
					FILE* fp = fopen(fn, "rb");
					if (fp == NULL) { // maybe file doesn't exist, handle gracefully
						// even if notfound or not, remain ambigious
						std::cout << "unable to open file \""<< fn << "\", 404ing and closing... \n";
						const char* reply = "HTTP/1.1 404 Not Found\r\nContent-Type: text/html\r\nConnection: close\r\nContent-Length: 16\r\n\r\n<html>404</html>\r\n\r\n";
						SSL_write(s_ssl, reply, strlen(reply));
						goto net_clean;
					} fseek(fp, 0, SEEK_END);
					size_t sz = ftell(fp);
					if (sz < 1) { // don't serve 0 files
						std::cout << "file is 0 bytes, or couldn't determine file size \""<< fn << "\", 500ing and closing... \n";
						const char* reply = "HTTP/1.1 500 Internal Server Error\r\nContent-Type: text/html\r\nConnection: close\r\nContent-Length: 16\r\n\r\n<html>500</html>\r\n\r\n";
						SSL_write(s_ssl, reply, strlen(reply));
						goto net_clean;
					} else if (sz > (K_MAX_MB * 1000000)) { // don't serve files larger than limit
						std::cout << "file to be served is too large. \""<< fn << "\" (" << sz << "). 500ing and closing... \n";
						const char* reply = "HTTP/1.1 500 Internal Server Error\r\nContent-Type: text/html\r\nConnection: close\r\nContent-Length: 16\r\n\r\n<html>500</html>\r\n\r\n";
						SSL_write(s_ssl, reply, strlen(reply));
						goto net_clean;
					} fseek(fp, 0, SEEK_SET);
					unsigned char* data = new unsigned char[sz];
					std::cout << "read " << fread(data, 1, sz, fp) << " bytes.\n";
					fclose(fp);
					// *** determine content-type

					// serve file
					const char* header = "HTTP/1.1 200 OK\r\nContent-Type: application/octet-stream\r\nConnection: close\r\nContent-Length: ";
					// *** TODO: get better snprintf_s to replace below code for buffer allocation
					int h = 12; // 12 digits
					// int h = sprintf_s(NULL, NULL, "%u", sz);
					char* cl_s = new char[h+1];
					sprintf(cl_s, "%u", sz);

					// ** add error handling
					SSL_write(s_ssl, header, strlen(header));
					SSL_write(s_ssl, cl_s, strlen(cl_s));
					SSL_write(s_ssl, "\r\n\r\n", 4);
					SSL_write(s_ssl, data, sz);
					// ** add error handling

					delete[] cl_s;
					delete[] data;
					delete[] fn;
				} else {
					std::cout << "unknown request detected, 404ing and closing... \n";
					const char* reply = "HTTP/1.1 404 Not Found\r\nContent-Type: text/html\r\nConnection: close\r\nContent-Length: 16\r\n\r\n<html>404</html>\r\n\r\n";
					SSL_write(s_ssl, reply, strlen(reply));
				}
			}
		} else if (strncmp(buf_s.c_str(), "POST /p HTTP/1.", 15) == 0) {
			// POST request
			const char* cl_s = strstr(buf_s.c_str(), "Content-Length:");
			if (cl_s == NULL)if (cl_s == NULL) { // no content-length
				std::cout << "bad http post request detected, 400ing and closing... \n";
				const char* reply = "HTTP/1.1 400 Bad Request\r\nContent-Type: text/html\r\nConnection: close\r\nContent-Length: 16\r\n\r\n<html>400</html>\r\n\r\n";
				SSL_write(s_ssl, reply, strlen(reply));
				goto net_clean;
			}
			cl_s = cl_s + 15; // Content-Length:
			//const char* cl_e = strstr(cl_s, "\r\n");
			//if (cl_e == NULL) { // bad content-length
			//	std::cout << "bad http post request detected, 400ing and closing... \n";
			//	const char* reply = "HTTP/1.1 400 Bad Request\r\nContent-Type: text/html\r\nConnection: close\r\nContent-Length: 16\r\n\r\n<html>400</html>\r\n\r\n";
			//	SSL_write(s_ssl, reply, strlen(reply));
			//}
			// if (cl_s + 1 == ' ') cl_s++; // not compact
			// int cl_l = cl_e - cl_s;
			int cl = strtol(cl_s, NULL, 10);
			if (cl < 1 || cl > (K_MAX_MB * 1000000)){ // bad content-length, or reuqest too large
				std::cout << "bad http post request detected, 400ing and closing... \n";
				const char* reply = "HTTP/1.1 400 Bad Request\r\nContent-Type: text/html\r\nConnection: close\r\nContent-Length: 16\r\n\r\n<html>400</html>\r\n\r\n";
				SSL_write(s_ssl, reply, strlen(reply));
				goto net_clean;
			}

			// *** find content-type
			const char* filetype = "bin";

			// gen filename
			char* filename = new char[K_FN_LENGTH+4+1]; filename[K_FN_LENGTH+4] = 0x00; // <K_FN_LENGTH>.xyz
			if (rand_name(filename, K_FN_LENGTH) != 0) {
				std::cout << "cannot create file to write, 500ing and closing... \n";
				const char* reply = "HTTP/1.1 500 Internal Server Error\r\nContent-Type: text/html\r\nConnection: close\r\nContent-Length: 16\r\n\r\n<html>500</html>\r\n\r\n";
				SSL_write(s_ssl, reply, strlen(reply));
				return -1;
			} sprintf(filename+K_FN_LENGTH, ".%s", filetype);
			// open file for writing
			FILE* out = fopen(filename, "wb");
			if (out == NULL) {
				std::cout << "cannot create file to write, 500ing and closing... \n";
				const char* reply = "HTTP/1.1 500 Internal Server Error\r\nContent-Type: text/html\r\nConnection: close\r\nContent-Length: 16\r\n\r\n<html>500</html>\r\n\r\n";
				SSL_write(s_ssl, reply, strlen(reply));
				return -1;
			}
			size_t len_p = 0;
			unsigned char* buf2 = new unsigned char[cl+1];
			buf2[cl] = 0x00;
			// b_cache
			if (b_cache != NULL) { memcpy(buf2, b_cache, b_len); delete[] b_cache; b_cache = NULL; }
			size_t h = cl - b_len; size_t offset = 0;
			if (h > 0) { // SSL_read 0 bytes still blocks...
			while (true) {
				if (SSL_read_ex(s_ssl, buf2 + b_len + offset, h, &len_p) != 1) { // bad read
					std::cout << "read POST attempt failed.\n" << ERR_error_string(ERR_get_error(), NULL) << "\nfatal error. exiting...\n";
					delete[] buf2;
					return -1;
				}
				if (len_p >= h) { offset = offset + len_p; break; }
				else { h = h - len_p; offset = len_p; }
			} } 
			// std::cout << "POST request receieved, len: " << len_p << " body:\n" << buf2 << '\n';
			std::cout << "POST request receieved, len: " << offset << ", writing to file...\n";
			std::cout << "wrote " << fwrite(buf2, 1, offset, out) << " bytes to file.\n";
			fclose(out);
			delete[] buf2;

			// reply
			std::cout << "File created. 201ing...\n";
			const char* reply = "HTTP/1.1 201 Created\r\nContent-Type: text/html\r\nConnection: close\r\nLocation: /s/";
			const char* reply2 = "\r\nContent-Length: 16\r\n\r\n<html>201</html>\r\n\r\n";
			SSL_write(s_ssl, reply, strlen(reply));
			SSL_write(s_ssl, filename, strlen(filename));
			SSL_write(s_ssl, reply2, strlen(reply2));

			delete[] filename;
		} else {
			std::cout << "unknown request detected, 404ing and closing... \n";
			const char* reply = "HTTP/1.1 404 Not Found\r\nContent-Type: text/html\r\nConnection: close\r\nContent-Length: 16\r\n\r\n<html>404</html>\r\n\r\n";
			SSL_write(s_ssl, reply, strlen(reply));
		}

net_clean:
		std::cout << "Goodbye. Closing socket...\n\n";

		SSL_free(s_ssl);
		// BIO_free(s_socket);
		// BIO_reset(s_socket); // reset and close connection
		goto net_accept;
	}

	
	BIO_reset(s_socket); // reset and close connection
	BIO_reset(s_accept);
	BIO_free_all(s_socket);
	BIO_free_all(s_accept);
	SSL_CTX_free(s_ctx);
	return 0;
}