using SSL;
using Posix;

string debug_bytes(uint8* s, long len) {
	var ret = new StringBuilder();
	ret.append("expected length: %lu actual length: %u ".printf(len, (uint)len));
	for (int i = 0; i < len; i++) {
		if (s[i] == '\\') {
			ret.append("\\\\");
		} else if (s[i] == '\'') {
			ret.append("\'");
		} else if (s[i] == 0x0d || s[i] == 0x0a || (0x19 < s[i] && s[i] < 0x7E)) {
			ret.append("%c".printf(s[i]));
		} else {
			ret.append("\\x%02hhx".printf(s[i]));
		}
	}
	return ret.str;
}

// Static fields
static string host;
static int port = 443;

private const OptionEntry[] options = {
	{ "ip", 'i', 0, OptionArg.STRING,
	  ref host, "Server IP address [default: 74.125.232.115]", "I" },
	{ "port", 'p', 0, OptionArg.INT,
	  ref port, "Server SSL port number [default: 443]", "P" },
	{ null }
};

int main(string[] args) {
	host = "";
    try{
		var opt_context = new OptionContext(" - OpenSSL Bio Test");
		opt_context.set_help_enabled (true);
		opt_context.add_main_entries (options, null);
		opt_context.parse (ref args);
	} catch (OptionError e) {
		GLib.stderr.printf("option parsing failed: %s\n", e.message);
		GLib.stderr.printf ("\nTry --help.\n");
		return 1;
	}

	if (host == "") {
		host = "74.125.232.115";
		GLib.stderr.printf ("No host address specified, using %s\n\n", host);
	}
	
	SSL.library_init();
	var ctx = new SSL.Context(SSLv23_method());
	var c = new SSL.Connection(ctx);
	var rbio = new BIO(BIO_s_mem());
	var wbio = new BIO(BIO_s_mem());
	if (rbio == null || wbio == null) {
		return 1;
	}
	c.set_bio(rbio, wbio);
	c.set_connect_state();
	c.do_handshake();
    int s = Posix.socket(Posix.AF_INET, Posix.SOCK_STREAM, 6);
//    int flags = fcntl (s, F_GETFL, 0);
//    fcntl (s, F_SETFL, flags | O_NONBLOCK);
	GLib.stdout.printf("socket: %d\n", s);
//	c.set_fd(s);
	GLib.stdout.printf("ssh socket\n");
	
	var sa = SockAddrIn();
	SockAddrIn* sa_p = &sa;
	sa.sin_family = AF_INET;
	sa.sin_port = htons((uint16) port);
//	sa.sin_addr = InAddr();
	sa.sin_addr.s_addr = inet_addr(host);
//	int connected = c.connect(&sa);
	int connected = connect(s, (SockAddr*)sa_p, sizeof(SockAddrIn));
	GLib.stdout.printf("connected: %d\n", connected);
	uint8* buf = new uint8[65536];
	var request = "GET / HTTP/1.0\r\nHost: google.com\r\n\r\n";
//	try {
	int sockreceived = 0;
	int received = 0;
	while (true) {
		int sent = c.send(request, request.length);
		GLib.stdout.printf("sent: %d\n", sent);
		int encrypted = wbio.read(buf, 65536);
		GLib.stdout.printf("encrypted: %d\n", encrypted);
		int socksent = (int)send(s, buf, encrypted, 0);
		GLib.stdout.printf("socksent: %d\n", socksent);
		while (true) {
//			reads++;
			sockreceived = (int)recv(s, buf, 65536, 0);
			GLib.stdout.printf("sockreceived: %d %s\n", sockreceived, debug_bytes(buf, sockreceived));
			if (sockreceived == 0) break;
			int decrypted = rbio.write(buf, sockreceived);
			GLib.stdout.printf("decrypted: %d\n", decrypted/*, debug_bytes(buf, decrypted)*/);
			received = c.recv(buf, 65536);
			GLib.stdout.printf("received: %d\n", received);
			/*if (reads == 2) */break;
		}
		if (received > 0 || sockreceived == 0) {
			GLib.stdout.printf(debug_bytes(buf, received));
			break;
		}
	}
//		GLib.stdout.printf("sending request\n");
//		sent = c.send(request, request.length);
//		GLib.stdout.printf("sent: %d\n", sent);
//		encrypted = wbio.read(buf, 65536);
//		GLib.stdout.printf("encrypted: %d\n", encrypted);
//		socksent = (int)send(s, buf, encrypted, 0);
//		GLib.stdout.printf("socksent: %d\n", socksent);
//		while (true) {
//			int sockreceived = (int)recv(s, buf, 65536, 0);
//			GLib.stdout.printf("sockreceived: %d %s\n", sockreceived, debug_bytes(buf, sockreceived));
//			if (sockreceived == 0) break;
//			int decrypted = rbio.write(buf, sockreceived);
//			GLib.stdout.printf("decrypted: %d\n", decrypted);
//			int received = c.recv(buf, 65536);
//			GLib.stdout.printf("received: %d\n", received);
//			if (received > 0) {
//				GLib.stdout.printf((string)buf);
//				break;
//			}
//		}
//	} catch (SSLException e) {
//		GLib.stderr.printf("SSL error: %s\n", e.message);
//	}
	c.shutdown();
	GLib.stdout.printf("All done.\n");
	return 0;
}
