using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
using System.IO;

namespace StarTrinity
{
	public static class SslTunnel
	{
        /// <param name="browserTcpClient">not used to send/receive data</param>
		public static void Tunnel(string firstHttpConnectLine, TcpClient browserTcpClient, Stream browserStream, StreamReader browserStreamReader, Log log)
		{
			// beginning of HTTPS request:
			/* 
request with proxy:

CONNECT www.yelp.com:443 HTTP/1.1
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:12.0) Gecko/20100101 Firefox/12.0
Proxy-Connection: keep-alive
Host: www.yelp.com
[blank line]
[ssl data]
			 */			
			
			//log.Write("ssltunnel", null, "got first line = '{0}'", firstHttpConnectLine);

			var browserStreamWriter = new StreamWriter(browserStream);

			var serverHostAndPort = firstHttpConnectLine.Split(' ')[1];
			var tunnelId = String.Format("{0} -> {1}", browserTcpClient.Client.RemoteEndPoint, serverHostAndPort);

			// read remaining lines
			for (; ; )
			{
				var line = browserStreamReader.ReadLine();
				log.Write("ssltunnel", tunnelId, "got line = '{0}'", line);
				if (String.IsNullOrEmpty(line)) break;
			}

			browserStreamWriter.WriteLine("HTTP/1.0 200 Connection established");
			browserStreamWriter.WriteLine(String.Format("Timestamp: {0}", DateTime.Now.ToString()));
			browserStreamWriter.WriteLine("Proxy-agent: StarTrinity");
			browserStreamWriter.WriteLine();
			browserStreamWriter.Flush();


			string server;
			int port = 443;
			if (serverHostAndPort.Contains(":"))
			{
				server = serverHostAndPort.Split(':')[0];
				port = Int32.Parse(serverHostAndPort.Split(':')[1]);
			}
			else
				server = serverHostAndPort;

			log.Write("ssltunnel", tunnelId, "proxying https request to server '{0}' port '{1}'", server, port);

			SendRequestToHttpsWebSiteAndProxyResponse(tunnelId, log, server, port, browserStreamWriter, browserStreamReader, browserTcpClient.Client);
		}

		private static void SendRequestToHttpsWebSiteAndProxyResponse(string tunnelId, Log log, string server, int port, StreamWriter browserStreamWriter,
            StreamReader browserStreamReader, Socket browserSocket)
		{
			var websiteClient = new TcpClient(server, port);
			log.Write("ssltunnel", tunnelId, "proxying https request to server '{0}' port '{1}'", server, port);

			var websiteStream = websiteClient.GetStream();
			BeginProxyToWebSite(tunnelId, log, browserStreamReader.BaseStream, websiteStream);
			BeginProxyFromWebSite(tunnelId, log, websiteStream, browserStreamReader.BaseStream, browserSocket);

		}
		static void BeginProxyFromWebSite(string tunnelId, Log log, Stream websiteStream, Stream browserStream, Socket browserSocket)
		{
			try
			{
				//log.Write("ssltunnel", tunnelId, "reading response from website...");
				byte[] buffer = new byte[10000];
				websiteStream.BeginRead(buffer, 0, buffer.Length, (ar) =>
				{
					try
					{
						int numberOfBytes = websiteStream.EndRead(ar);
						if (numberOfBytes > 0)
						{
							//log.Write("ssltunnel", tunnelId, "sending response to browser: {0}", BitConverter.ToString(buffer, 0, numberOfBytes));
							browserStream.BeginWrite(buffer, 0, numberOfBytes, (ar2) =>
							{
								try
								{
									browserStream.EndWrite(ar2);
									BeginProxyFromWebSite(tunnelId, log, websiteStream, browserStream, browserSocket);
								}
								catch (Exception exc)
								{
									log.WriteException("ssltunnel", exc, null);
								}
							}, null);
						}
					}
					catch (Exception exc)
					{
						if (exc.InnerException is SocketException)
						{
							var ne = (SocketException)exc.InnerException;
							if (ne.ErrorCode == 10054)
							{
								// normal case: web site closes socket. todo: close connection with browser
								try
								{
									browserSocket.Close();
								}
								catch
								{
								}
								return;
							}
						}

						log.WriteException("ssltunnel", exc, null);
					}
				}, null);
			}
			catch (Exception exc)
			{
				log.WriteException("ssltunnel", exc, null);
			}
		}
		static void BeginProxyToWebSite(string tunnelId, Log log, Stream source, Stream destination)
		{
			try
			{
				//log.Write("ssltunnel", tunnelId, "reading request from browser...");
				byte[] buffer = new byte[10000];
				source.BeginRead(buffer, 0, buffer.Length, (ar) =>
				{
					try
					{
						int numberOfBytes = source.EndRead(ar);
						if (numberOfBytes > 0)
						{
							//log.Write("ssltunnel", tunnelId, "sending to web site: {0}", BitConverter.ToString(buffer, 0, numberOfBytes));
							destination.BeginWrite(buffer, 0, numberOfBytes, (ar2) =>
							{
								try
								{
									destination.EndWrite(ar2);
									BeginProxyToWebSite(tunnelId, log, source, destination);
								}
								catch (Exception exc)
								{
									log.WriteException("ssltunnel", exc, null);
								}
							}, null);
						}
					}
					catch (Exception exc)
					{
						if (exc.InnerException is ObjectDisposedException)
							return;
						if (exc.InnerException is SocketException)
						{
							var ne = (SocketException)exc.InnerException;
							if (ne.ErrorCode == 10054) return; // normal case: browser closes socket. to o: close connection with web site
						}

						log.WriteException("ssltunnel", exc, null);
					}
				}, null);
			}
			catch (Exception exc)
			{
				log.WriteException("ssltunnel", exc, null);
			}
		}
	}
}
