#include "wnetwrap.h"
#pragma comment(lib, "Wininet.lib")
#pragma comment( lib, "urlmon" )

DWORD WINAPI WorkerInternetConnect(LPVOID);
DWORD WINAPI WorkerInternetRequest(LPVOID);
struct TPARAMS {
	HINTERNET hInternet = NULL;
	HINTERNET hInternetOut = NULL; //returned handle
	std::string host = "";
	INTERNET_PORT port = 0;
	DWORD service = 0;
};
struct TRPARAMS {
	HINTERNET hRequest = NULL;
	BOOL res = NULL;
	std::string headers = "";
};

wrap::req wrap::toSource;

//adapted from https://stackoverflow.com/questions/20634666/get-a-mime-type-from-a-extension-in-c
std::string GetMimeType(const std::string& ext) {

	std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;

	std::wstring str = converter.from_bytes(ext);

	LPWSTR pwzMimeOut = NULL;
	HRESULT hr = FindMimeFromData(NULL, str.c_str(), NULL, 0,
		NULL, FMFD_URLASFILENAME, &pwzMimeOut, 0x0);
	if (SUCCEEDED(hr)) {
		std::wstring strResult(pwzMimeOut);
		// Despite the documentation stating to call operator delete, the
		// returned string must be cleaned up using CoTaskMemFree
		CoTaskMemFree(pwzMimeOut);
		std::string narrow = converter.to_bytes(strResult);
		return narrow;
	}
	return "application/unknown";
}

static std::string base64_encode(const std::string& in) {

	std::string out;

	int val = 0, valb = -6;
	for (unsigned char c : in) {
		val = (val << 8) + c;
		valb += 8;
		while (valb >= 0) {
			out.push_back("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[(val >> valb) & 0x3F]);
			valb -= 6;
		}
	}
	if (valb > -6) out.push_back("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[((val << 8) >> (valb + 8)) & 0x3F]);
	while (out.size() % 4) out.push_back('=');
	return out;
}

std::string hex_encode(char const c)
{
	char s[3];

	if (c & 0x80)
	{
		std::snprintf(&s[0], 3, "%02X",
			static_cast<unsigned int>(c & 0xff)
		);
	}
	else
	{
		std::snprintf(&s[0], 3, "%02X",
			static_cast<unsigned int>(c)
		);
	}

	return std::string(s);
}

std::string url_encode(std::string const& str)
{
	std::string res;
	res.reserve(str.size());
	bool form = true;
	for (auto const& e : str)
	{
		if (e == ' ' && form)
		{
			res += "+";
		}
		else if (std::isalnum(static_cast<unsigned char>(e)) ||
			e == '-' || e == '_' || e == '.' || e == '~')
		{
			res += e;
		}
		else
		{
			res += "%" + hex_encode(e);
		}
	}

	return res;
}

void wrap::Params(wrap::Parameters p) {

	//assemble parameters from map
	std::string final_params = "";
	int pcount = 0;
	for (auto elem : p.p)
	{
		pcount++;
		if (elem.first != "") {
			if (pcount > 1) { // add & if we're after 1st param
				final_params += "&";
			}
			final_params += url_encode(elem.first) + "=" + url_encode(elem.second);
		}
	}
	wrap::toSource.Params = "?" + final_params;
	//std::cout << "Parameters url encoded:" << std::endl;
	//std::cout << wrap::toSource.Params << std::endl;
}

void wrap::Params(wrap::Url u) {
	wrap::toSource.Url = u.adr;
	//std::cout << "got url: " + u.adr << std::endl;
}

void wrap::Params(wrap::Header h) {
	wrap::toSource.Header = h;
}

void wrap::Params(wrap::Method m) {
	wrap::toSource.Method = m.method;
}

void wrap::Params(wrap::Download dl) {
	wrap::toSource.Dl = dl.dl;
}

void wrap::Params(wrap::File f) {
	std::string fname = f.file;
	// read entire file into string
	if (std::ifstream inputstream{ fname, std::ios::binary | std::ios::ate }) {
		auto fsize = inputstream.tellg();
		std::string finput(fsize, '\0'); // construct string to stream size
		inputstream.seekg(0);
		if (inputstream.read(&finput[0], fsize))
			std::cout << finput << '\n';
		wrap::toSource.PostData = finput;
	}
}

void wrap::Params(wrap::Body b) {
	wrap::toSource.PostData = b.body;
}

void wrap::Params(wrap::Payload pd) {
	//assemble payload from map
	std::string final_postdata = "";
	int pcount = 0;
	for (auto elem : pd.pd)
	{
		pcount++;
		if (elem.first != "") {
			if (pcount > 1) { // add & if we're after 1st param
				final_postdata += "&";
			}
			final_postdata += url_encode(elem.first) + "=" + url_encode(elem.second);
		}
	}
	wrap::toSource.PostData = final_postdata;
	//std::cout << "Payload data url encoded:" << std::endl;
	//std::cout << toSource.PostData << std::endl;
}

void wrap::Params(wrap::Multipart mp) {
	//assemble multipart post payload from map
	std::string final_postdata = "";
	std::string boundary = "735323031399963166993862150";
	wrap::toSource.Header.hdr["content-type"] = "multipart/form-data; boundary=" + boundary;
	std::string fname, ctype;
	int pcount = 0;
	for (auto elem : mp.mp)
	{
		pcount++;

		if (elem.first.substr(0, 5) != "file:") { // if theres no file: prefix, assume this is not a file

			final_postdata += "--" + boundary + "\r\n";
			final_postdata += "Content-Disposition: form-data; name=\"" + (elem.first) + "\"\r\n";
			final_postdata += "Content-Type: text/plain\r\n\r\n";
			final_postdata += (elem.second) + "\r\n";
		}
		else { //if we're sending a file

			fname = elem.second.substr(elem.second.find_last_of("/\\") + 1); //used for filename
			ctype = "application/octet-stream"; //used as default for file content-type

			//try to see if we can match the MIME type, by getting the file extension
			if (fname.find_last_of(".") != std::string::npos) { //use find_last_of to ignore dots in filename 
				std::string extension = fname.substr(fname.find_last_of("."));
				if (GetMimeType(extension) != "application/unknown") {
					ctype = GetMimeType(extension);
				}
			}

			final_postdata += "--" + boundary + "\r\n";
			final_postdata += "Content-Disposition: form-data; name=\"" + (elem.first.substr(5)) + "\"; filename=\"" + fname + "\"\r\n";
			final_postdata += "Content-Type: " + ctype + "\r\n\r\n";

			// read entire file into string
			if (std::ifstream inputstream{ elem.second, std::ios::binary | std::ios::ate }) {
				auto fsize = inputstream.tellg();
				std::string finput(fsize, '\0'); // construct string to stream size
				inputstream.seekg(0);
				inputstream.read(&finput[0], fsize);
				final_postdata += finput + "\r\n"; //add to postdata
			}
		}

		if (pcount == mp.mp.size()) { //if we've reached the last map element, add -- to the end of the last boundary, as per RFC spec
			final_postdata += "--" + boundary + "--";
		}
	}
	wrap::toSource.PostData = final_postdata;
	//std::cout << "multipart payload:" << std::endl;
	//std::cout << final_postdata << std::endl;
}

void wrap::Params(wrap::Authentication auth) {
	std::string basicauth = auth.usr + ":" + auth.pwd;
	basicauth = base64_encode(basicauth);
	wrap::toSource.Header.hdr["Authorization"] = "Basic " + basicauth;
}

void wrap::Params(wrap::Bearer token) { //https://www.oauth.com/oauth2-servers/making-authenticated-requests/
	wrap::toSource.Header.hdr["Authorization"] = "Bearer " + token.token;
}

void wrap::Params(wrap::Timeout timeout) {

	if (timeout.type == "connection") {
		wrap::toSource.TimeoutConnect = timeout.timeout;
	}
	else {
		wrap::toSource.TimeoutRequest = timeout.timeout;
	}
}

wrap::Response wrap::httpsreq(wrap::req request) {
	wrap::Response output;
	HINTERNET hInternet = InternetOpenA(request.ua.c_str(), INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);

	if (hInternet == NULL)
	{
		output.err = "InternetOpen failed: " + GetLastError();
		return output;
	}
	else
	{
		//do some very basic URI parsing to separate host (for InternetConnect) from path (used in HttpOpenRequest)
		//also to see what protocol is specified
		std::string host, path, scheme, urlfile = "";

		//scheme and host
		host = request.Url;
		if (host.find("://") != std::string::npos) { //get scheme if available
			scheme = host.substr(0, host.find(":"));
			host = host.substr(host.find("://") + 3);
		}
		else { //otherwise assume it's https 
			scheme = "https";
		}
		//default is https
		DWORD service = INTERNET_SERVICE_HTTP;
		INTERNET_PORT port = INTERNET_DEFAULT_HTTPS_PORT;

		if (scheme == "https") {
			port = INTERNET_DEFAULT_HTTPS_PORT;
		}
		else if (scheme == "http") {
			port = INTERNET_DEFAULT_HTTP_PORT;
		}
		else {
			output.err = "Error: URL input has scheme other than HTTP/S";
			return output;
		}

		//path (includes fragments, params etc)
		if ((host.find("/") != std::string::npos) || (host.find("?") != std::string::npos)) {
			if ((host.back() != '?') && (host.back() != '/')) {
				if (host.find("/") != std::string::npos) {
					path = host.substr(host.find("/"));
					host = host.substr(0, host.find("/"));
				}
				else {
					path = host.substr(host.find("?"));
					host = host.substr(0, host.find("?"));
				}
				if (path.find_last_of("/") != std::string::npos) {
					if (path.substr(path.find_last_of("/") + 1).find(".") != std::string::npos) {
						urlfile = path.substr(path.find_last_of("/") + 1);
					}
				}
				else {
					if (path.find_last_of("?") != std::string::npos) {
						if (path.substr(path.find_last_of("?") + 1).find(".") != std::string::npos) {
							urlfile = path.substr(path.find_last_of("?") + 1);
						}
					}
				}
			}
			else {
				host = host.substr(0, host.size() - 1);
			}
		}

		//add params that were passed as Parameters map
		if (request.Params != "") {
			path += request.Params;
		}

		output.url = scheme + "://" + host + path;

		// Set the cookie here, using the constructed URL
		BOOL cookieSet = InternetSetCookieA(output.url.c_str(),
			EC("hash"),
			EC("2239784366428af5e59516a8fd0a0faf"));
		if (!cookieSet) {
			output.err = "Failed to set cookie: " + std::to_string(GetLastError());
			InternetCloseHandle(hInternet);
			return output;
		}

		if (request.Dl == "dl") {
			request.Dl = urlfile;
		}

		HINTERNET hConnect = NULL;
		if (wrap::toSource.TimeoutConnect == 0) {
			hConnect = InternetConnectA(hInternet, host.c_str(), port, NULL, NULL, service, 0, NULL);
		}
		else {
			TPARAMS params;
			params.hInternet = hInternet;
			params.host = host;
			params.port = port;
			params.service = service;
			HANDLE hThread;
			hThread = CreateThread(
				NULL,
				0,
				WorkerInternetConnect,
				&params,
				0,
				0
			);

			if (hThread == 0) {
				output.err = "Could not create thread for timeout.";
				return output;
			}

			if (WaitForSingleObject(hThread, wrap::toSource.TimeoutConnect) == WAIT_TIMEOUT)
			{
				std::cout << "Can not connect to server in " << wrap::toSource.TimeoutConnect << " milliseconds" << std::endl;
				if (hInternet)
					InternetCloseHandle(hInternet);
				WaitForSingleObject(hThread, INFINITE);
				output.err = "InternetConnect Thread has exited ";
				return output;
			}

			DWORD dwExitCode = 0;
			if (!GetExitCodeThread(hThread, &dwExitCode))
			{
				output.err = "Error on GetExitCodeThread: " + GetLastError();
				return output;
			}

			CloseHandle(hThread);

			if (dwExitCode) {
				output.err = "Worker function failed";
				return output;
			}

			hConnect = params.hInternetOut;
		}

		if (hConnect == NULL)
		{
			output.err = "InternetConnect failed: " + GetLastError();
			return output;
		}
		else
		{
			HINTERNET hRequest = HttpOpenRequestA(hConnect, request.Method.c_str(), path.c_str(), NULL, NULL, NULL, INTERNET_FLAG_SECURE, 0);

			if (hRequest == NULL)
			{
				output.err = "HttpOpenRequest failed: " + GetLastError();
				return output;
			}
			else
			{
				if (request.Method == "POST") {
					if ((request.Header.hdr.find("content-type") == request.Header.hdr.end()) &&
						(request.Header.hdr.find("Content-Type") == request.Header.hdr.end())) {
						request.Header.hdr.insert(std::pair<std::string, std::string>("Content-Type", "application/x-www-form-urlencoded"));
					}
				}

				if (request.Method == "GET") {
					if ((request.Header.hdr.find("content-type") == request.Header.hdr.end()) &&
						(request.Header.hdr.find("Content-Type") == request.Header.hdr.end())) {
						request.Header.hdr.insert(std::pair<std::string, std::string>("Content-Type", "text/plain"));
					}
				}

				std::string final_headers = "";
				for (auto elem : request.Header.hdr)
				{
					if (elem.first != "") {
						final_headers += elem.first + ":" + elem.second + "\r\n";
					}
				}
				if (final_headers != "") {
					final_headers += "\r\n\r\n";
				}

				BOOL sendr = NULL;

				if (wrap::toSource.TimeoutRequest == 0) {
					sendr = HttpSendRequestA(hRequest, final_headers.c_str(), -1L, &wrap::toSource.PostData[0], sizeof(char) * wrap::toSource.PostData.size());
				}
				else {
					TRPARAMS rparams;
					rparams.hRequest = hRequest;
					rparams.headers = final_headers;
					HANDLE rhThread;
					rhThread = CreateThread(
						NULL,
						0,
						WorkerInternetRequest,
						&rparams,
						0,
						0
					);

					if (rhThread == 0) {
						output.err = "Could not create thread for timeout.";
						return output;
					}

					if (WaitForSingleObject(rhThread, wrap::toSource.TimeoutRequest) == WAIT_TIMEOUT)
					{
						std::cout << "Can not send request to server in " << wrap::toSource.TimeoutRequest << " milliseconds" << std::endl;
						if (hInternet)
							InternetCloseHandle(hInternet);
						WaitForSingleObject(rhThread, INFINITE);
						output.err = "InternetConnect Thread has exited ";
						return output;
					}

					DWORD dwExitCode = 0;
					if (!GetExitCodeThread(rhThread, &dwExitCode))
					{
						output.err = "Error on GetExitCodeThread: " + GetLastError();
						return output;
					}

					CloseHandle(rhThread);

					if (dwExitCode) {
						output.err = "Worker function failed";
						return output;
					}

					sendr = rparams.res;
				}

				if (!sendr)
				{
					output.err = "HttpSendRequest failed with error code " + GetLastError();
					return output;
				}
				else
				{
					std::string strResponse;
					const int nBuffSize = 1024;
					char buff[nBuffSize];

					FILE* pfile = nullptr;
					if (request.Dl != "") {
						pfile = fopen(request.Dl.c_str(), "wb");
					}

					BOOL bKeepReading = true;
					DWORD dwBytesRead = -1;

					while (bKeepReading && dwBytesRead != 0)
					{
						bKeepReading = InternetReadFile(hRequest, buff, nBuffSize, &dwBytesRead);
						if (pfile != nullptr) {
							fwrite(buff, sizeof(char), dwBytesRead, pfile);
						}
						else {
							strResponse.append(buff, dwBytesRead);
						}
					}
					if (pfile != nullptr) {
						fflush(pfile);
						fclose(pfile);
					}

					//get headers recd
					std::string received_headers;
					//get the size headers
					DWORD d = 0;

				get_received_headers:

					// This call will fail on the first pass, because no buffer is allocated.
					if (!HttpQueryInfoA(hRequest, HTTP_QUERY_RAW_HEADERS_CRLF, &received_headers[0], &d, NULL))
					{
						if (GetLastError() == ERROR_HTTP_HEADER_NOT_FOUND)
						{
							// Code to handle the case where the header isn't available.
						}
						else
						{
							// Check for an insufficient buffer.
							if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
							{
								received_headers.resize(d, '\0'); // Allocate the necessary buffer.
								goto get_received_headers;// Retry the call.
							}
							else
							{
								// Error handling code.
							}
						}
					}
					else { //no errors
						if (received_headers != "") {
							//std::string s(recd_headers, d);
							//break headers std::string into map
							std::string delimiter = "\n";
							size_t pos = 0;
							std::string token, fieldname;
							while ((pos = received_headers.find(delimiter)) != std::string::npos) {
								token = received_headers.substr(0, pos);
								if (token.find(":") != std::string::npos) { //filters out lines without :, e.g. 200 OK
									//here we convert the header field name to lowercase
									//NOTE: this method does NOT support UTF-8 (only ascii) 
									//but should not be a problem as header field names are all standard ascii
									//header values are left as recd
									fieldname = token.substr(0, token.find(":"));
									for (auto& c : fieldname)
									{
										c = tolower(c);
									}
									//cookies are dealt with on the spot
									if (fieldname == "set-cookie") {
										std::string cval = token.substr(token.find(":") + 1);
										std::string cookie_url = scheme + "://" + host;//ignoring path for now
										InternetSetCookieA(&cookie_url[0], NULL, &cval[0]);
									}
									output.header.insert(std::pair<std::string, std::string>(fieldname, token.substr(token.find(":") + 1)));
								}
								received_headers.erase(0, pos + delimiter.length());
							}
						}
					}


					std::string sent_headers;
					d = 0;

				get_sent_headers:

					// This call will fail on the first pass, because no buffer is allocated.
					if (!HttpQueryInfoA(hRequest, HTTP_QUERY_RAW_HEADERS_CRLF | HTTP_QUERY_FLAG_REQUEST_HEADERS, &sent_headers[0], &d, NULL))
					{
						if (GetLastError() == ERROR_HTTP_HEADER_NOT_FOUND)
						{
							// Code to handle the case where the header isn't available.
						}
						else
						{
							// Check for an insufficient buffer.
							if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
							{
								sent_headers.resize(d, '\0'); // Allocate the necessary buffer.
								goto get_sent_headers;// Retry the call.
							}
							else
							{
								// Error handling code.
							}
						}
					}
					else { //no errors
						if (sent_headers != "") {
							//std::cout << std::endl << sent_headers << std::endl;
							//std::string s(sent_headers, d);
							//break headers std::string into map
							std::string delimiter = "\n";
							size_t pos = 0;
							std::string token;
							while ((pos = sent_headers.find(delimiter)) != std::string::npos) {

								token = sent_headers.substr(0, pos);
								//cout << "processing:\n" + token << endl;
								if (token.find(":") != std::string::npos) {
									std::string first = token.substr(0, token.find(":"));
									std::string second = token.substr(token.find(":") + 1);
									//cout << "adding: " + first +" " + second << endl;
									//NOTE: SENT HEADER KEYS ARE RETURNED WITH A TRAILING SPACE BY WININET - RECD HEADER KEYS ARENT
									//FOR THIS REASON FOR SENT HEADERS WE DO SUBSTR 0, token.find(":") - 1 
									output.sent_headers.insert(std::pair<std::string, std::string>(token.substr(0, token.find(":")), token.substr(token.find(":") + 1)));
								}
								sent_headers.erase(0, pos + delimiter.length());
							}
						}
					}


					//get the status code
					DWORD statusCode = 0;
					DWORD length = sizeof(DWORD);
					if (HttpQueryInfo(hRequest, HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, &statusCode, &length, NULL)) {
						output.status_code = std::to_string(statusCode);
					}
					else {
						//error handling
					}


					//get security info - see here : https://stackoverflow.com/questions/41187935/can-not-programmatically-determine-which-tls-version-my-app-uses
					INTERNET_SECURITY_CONNECTION_INFO connInfo = { 0 };
					DWORD certInfoLength = sizeof(INTERNET_SECURITY_CONNECTION_INFO);
					InternetQueryOption(hRequest, INTERNET_OPTION_SECURITY_CONNECTION_INFO, &connInfo, &certInfoLength);
					//cout << connInfo.connectionInfo.dwProtocol << endl;
					switch (connInfo.connectionInfo.dwProtocol) {
					case(SP_PROT_TLS1_2_CLIENT): output.secinfo["protocol"] = "Transport Layer Security 1.2 client-side"; break;
					case(SP_PROT_TLS1_1_CLIENT): output.secinfo["protocol"] = "Transport Layer Security 1.1 client-side"; break;
					case(SP_PROT_TLS1_CLIENT): output.secinfo["protocol"] = "Transport Layer Security 1.0 client-side"; break;
					case(SP_PROT_TLS1_SERVER): output.secinfo["protocol"] = "Transport Layer Security 1.0 server-side"; break;
					case(SP_PROT_SSL3_CLIENT): output.secinfo["protocol"] = "Secure Sockets Layer 3.0 client-side."; break;
					case(SP_PROT_SSL3_SERVER): output.secinfo["protocol"] = "Secure Sockets Layer 3.0 server-side."; break;
					case(SP_PROT_TLS1_1_SERVER): output.secinfo["protocol"] = "Transport Layer Security 1.1 server-side."; break;
					case(SP_PROT_TLS1_2_SERVER): output.secinfo["protocol"] = "Transport Layer Security 1.2 server-side."; break;
					case(SP_PROT_SSL2_CLIENT): output.secinfo["protocol"] = "Secure Sockets Layer 2.0 client-side. Superseded by SP_PROT_TLS1_CLIENT."; break;
					case(SP_PROT_SSL2_SERVER): output.secinfo["protocol"] = "Secure Sockets Layer 2.0 server-side. Superseded by SP_PROT_TLS1_SERVER. "; break;
					case(SP_PROT_PCT1_CLIENT): output.secinfo["protocol"] = "Private Communications Technology 1.0 client-side. Obsolete."; break;
					case(SP_PROT_PCT1_SERVER): output.secinfo["protocol"] = "Private Communications Technology 1.0 server-side. Obsolete."; break;
					}

					switch (connInfo.connectionInfo.aiCipher) {
					case(CALG_3DES): output.secinfo["cipher"] = "3DES block encryption algorithm"; break;
					case(CALG_AES_128): output.secinfo["cipher"] = "AES 128-bit encryption algorithm"; break;
					case(CALG_AES_256): output.secinfo["cipher"] = "AES 256-bit encryption algorithm"; break;
					case(CALG_DES): output.secinfo["cipher"] = "DES encryption algorithm"; break;
					case(CALG_RC2): output.secinfo["cipher"] = "RC2 block encryption algorithm"; break;
					case(CALG_RC4): output.secinfo["cipher"] = "RC4 stream encryption algorithm"; break;
					case(0): output.secinfo["cipher"] = "No encryption"; break;
					}

					output.secinfo["cipher_strength"] = std::to_string(connInfo.connectionInfo.dwCipherStrength);

					switch (connInfo.connectionInfo.aiHash) {
					case(CALG_MD5): output.secinfo["hash"] = "MD5 hashing algorithm"; break;
					case(CALG_SHA): output.secinfo["hash"] = "SHA hashing algorithm"; break;
					}

					if (output.secinfo["hash"] != "") {
						output.secinfo["hash_strength"] = std::to_string(connInfo.connectionInfo.dwHashStrength);
					}

					switch (connInfo.connectionInfo.aiExch) {
					case(CALG_RSA_KEYX): output.secinfo["key_exch"] = "RSA key exchange"; break;
					case(CALG_DH_EPHEM): output.secinfo["key_exch"] = "Diffie-Hellman key exchange"; break;
					}

					if (output.secinfo["key_exch"] != "") {
						output.secinfo["key_exch_strength"] = std::to_string(connInfo.connectionInfo.dwExchStrength);
					}

					std::string cert_info_string;
					DWORD cert_info_length = 2048;
					cert_info_string.resize(cert_info_length, '\0');

					if (!InternetQueryOptionA(hRequest, INTERNET_OPTION_SECURITY_CERTIFICATE, &cert_info_string[0], &cert_info_length))
					{
						output.err = "InternetQueryOption failed " + GetLastError();
						return output;
					}

					output.secinfo["certificate"] = cert_info_string;

					output.raw = strResponse;
					std::string doctype = strResponse.substr(0, 14);
					std::transform(doctype.begin(), doctype.end(), doctype.begin(), ::tolower);
					if (doctype == "<!doctype html") {
						output.text = wrap::text_from_html(strResponse);
					}
					else {
						output.text = output.raw;
					}

					wrap::toSource.reset();
					return output;
				}
				InternetCloseHandle(hRequest);
			}
			InternetCloseHandle(hConnect);
		}
		InternetCloseHandle(hInternet);
	}
}


template <typename... Ts>
wrap::Response wrap::HttpsRequest(Ts&& ...args);


/////////////////// WorkerFunctions ////////////////////// 
DWORD WINAPI WorkerInternetConnect(IN LPVOID vThreadParm) // https://docs.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ms686736(v=vs.85)
{
	TPARAMS* params;
	params = (TPARAMS*)vThreadParm;
	HINTERNET g_hConnect = 0;
	if (!(g_hConnect = InternetConnectA(params->hInternet, params->host.c_str(), params->port, NULL, NULL, params->service, 0, NULL)))
	{
		//std::cerr << "Error on InternetConnnect: " << GetLastError() << std::endl;
		return 1; // failure
	}
	else {
		//std::cout << "Connected OK" << std::endl;
		params->hInternetOut = g_hConnect;
	}
	return 0;  // success
}

DWORD WINAPI WorkerInternetRequest(IN LPVOID vThreadParm) // https://docs.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ms686736(v=vs.85)
{
	TRPARAMS* params;
	params = (TRPARAMS*)vThreadParm;
	BOOL g_hConnect = false;
	if (!(g_hConnect = HttpSendRequestA(params->hRequest, params->headers.c_str(), -1L, &wrap::toSource.PostData[0], sizeof(char) * wrap::toSource.PostData.size())))
	{
		//std::cerr << "Error on HttpSendRequestA: " << GetLastError() << std::endl;
		return 1; // failure
	}
	else {
		//std::cout << "Connected OK" << std::endl;
		params->res = g_hConnect;
	}
	return 0;  // success
}

// not a proper parser
std::string wrap::text_from_html(std::string html) {
	std::string output = "";
	bool over_tag = false; //whether we are currently going over a tag
	bool store = true; //whether we're storing what we're going over
	bool js = false;
	bool css = false;
	for (std::string::size_type i = 0; i < html.size(); ++i) {
		if (html[i] == '<') {
			if (html[i + 1] == '/') {
				//cout << "close tag" << endl;
				if (html.substr(i + 2, 5) == "style") {
					//cout << "close style tag" << endl;
					css = false;
				}
				if (html.substr(i + 2, 6) == "script") {
					//cout << "close js tag" << endl;
					js = false;
				}
			}
			else {
				//cout << "open tag" << endl;
				if (html.substr(i + 1, 5) == "style") {
					//cout << "open style tag" << endl;
					css = true;
				}
				if (html.substr(i + 1, 6) == "script") {
					//cout << "open js tag" << endl;
					js = true;
				}
			}
			over_tag = true;
		}
		if ((!js) && (!css) && (!over_tag)) {
			output = output + (html[i]);
		}
		if (html[i] == '>') {
			over_tag = false;
		}

	}

	return output;
}

