using System;
using System.Net;
using System.Net.Sockets;

namespace Org.Mentalis.Network.ProxySocket
{
	public class ProxySocket : Socket
	{
		private object m_State;

		private IPEndPoint m_ProxyEndPoint = null;

		private ProxyTypes m_ProxyType = ProxyTypes.None;

		private string m_ProxyUser = null;

		private string m_ProxyPass = null;

		private AsyncCallback CallBack = null;

		private IAsyncProxyResult m_AsyncResult;

		private Exception m_ToThrow = null;

		private int m_RemotePort;

		public IPEndPoint ProxyEndPoint
		{
			get
			{
				return this.m_ProxyEndPoint;
			}
			set
			{
				this.m_ProxyEndPoint = value;
			}
		}

		public ProxyTypes ProxyType
		{
			get
			{
				return this.m_ProxyType;
			}
			set
			{
				this.m_ProxyType = value;
			}
		}

		private object State
		{
			get
			{
				return this.m_State;
			}
			set
			{
				this.m_State = value;
			}
		}

		public string ProxyUser
		{
			get
			{
				return this.m_ProxyUser;
			}
			set
			{
				if (value == null)
				{
					throw new ArgumentNullException();
				}
				this.m_ProxyUser = value;
			}
		}

		public string ProxyPass
		{
			get
			{
				return this.m_ProxyPass;
			}
			set
			{
				if (value == null)
				{
					throw new ArgumentNullException();
				}
				this.m_ProxyPass = value;
			}
		}

		private IAsyncProxyResult AsyncResult
		{
			get
			{
				return this.m_AsyncResult;
			}
			set
			{
				this.m_AsyncResult = value;
			}
		}

		private Exception ToThrow
		{
			get
			{
				return this.m_ToThrow;
			}
			set
			{
				this.m_ToThrow = value;
			}
		}

		private int RemotePort
		{
			get
			{
				return this.m_RemotePort;
			}
			set
			{
				this.m_RemotePort = value;
			}
		}

		public ProxySocket(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType) : this(addressFamily, socketType, protocolType, "")
		{
		}

		public ProxySocket(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType, string proxyUsername) : this(addressFamily, socketType, protocolType, proxyUsername, "")
		{
		}

		public ProxySocket(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType, string proxyUsername, string proxyPassword) : base(addressFamily, socketType, protocolType)
		{
			this.ProxyUser = proxyUsername;
			this.ProxyPass = proxyPassword;
			this.ToThrow = new InvalidOperationException();
		}

		public new void Connect(EndPoint remoteEP)
		{
			if (remoteEP == null)
			{
				throw new ArgumentNullException("<remoteEP> cannot be null.");
			}
			if (base.ProtocolType != ProtocolType.Tcp || this.ProxyType == ProxyTypes.None || this.ProxyEndPoint == null)
			{
				base.Connect(remoteEP);
			}
			else
			{
				base.Connect(this.ProxyEndPoint);
				if (this.ProxyType == ProxyTypes.Socks4)
				{
					new Socks4Handler(this, this.ProxyUser).Negotiate((IPEndPoint)remoteEP);
				}
				else if (this.ProxyType == ProxyTypes.Socks5)
				{
					new Socks5Handler(this, this.ProxyUser, this.ProxyPass).Negotiate((IPEndPoint)remoteEP);
				}
			}
		}

		public new void Connect(string host, int port)
		{
			if (host == null)
			{
				throw new ArgumentNullException("<host> cannot be null.");
			}
			if (port <= 0 || port > 65535)
			{
				throw new ArgumentException("Invalid port.");
			}
			if (base.ProtocolType != ProtocolType.Tcp || this.ProxyType == ProxyTypes.None || this.ProxyEndPoint == null)
			{
				base.Connect(new IPEndPoint(Dns.Resolve(host).AddressList[0], port));
			}
			else
			{
				base.Connect(this.ProxyEndPoint);
				if (this.ProxyType == ProxyTypes.Socks4)
				{
					new Socks4Handler(this, this.ProxyUser).Negotiate(host, port);
				}
				else if (this.ProxyType == ProxyTypes.Socks5)
				{
					new Socks5Handler(this, this.ProxyUser, this.ProxyPass).Negotiate(host, port);
				}
			}
		}

		public new IAsyncResult BeginConnect(EndPoint remoteEP, AsyncCallback callback, object state)
		{
			if (remoteEP == null || callback == null)
			{
				throw new ArgumentNullException();
			}
			IAsyncResult result;
			if (base.ProtocolType != ProtocolType.Tcp || this.ProxyType == ProxyTypes.None || this.ProxyEndPoint == null)
			{
				result = base.BeginConnect(remoteEP, callback, state);
			}
			else
			{
				this.CallBack = callback;
				if (this.ProxyType == ProxyTypes.Socks4)
				{
					this.AsyncResult = new Socks4Handler(this, this.ProxyUser).BeginNegotiate((IPEndPoint)remoteEP, new HandShakeComplete(this.OnHandShakeComplete), this.ProxyEndPoint);
					result = this.AsyncResult;
				}
				else if (this.ProxyType == ProxyTypes.Socks5)
				{
					this.AsyncResult = new Socks5Handler(this, this.ProxyUser, this.ProxyPass).BeginNegotiate((IPEndPoint)remoteEP, new HandShakeComplete(this.OnHandShakeComplete), this.ProxyEndPoint);
					result = this.AsyncResult;
				}
				else
				{
					result = null;
				}
			}
			return result;
		}

		public new IAsyncResult BeginConnect(string host, int port, AsyncCallback callback, object state)
		{
			if (host == null || callback == null)
			{
				throw new ArgumentNullException();
			}
			if (port <= 0 || port > 65535)
			{
				throw new ArgumentException();
			}
			this.CallBack = callback;
			IAsyncResult result;
			if (base.ProtocolType != ProtocolType.Tcp || this.ProxyType == ProxyTypes.None || this.ProxyEndPoint == null)
			{
				this.RemotePort = port;
				this.AsyncResult = this.BeginDns(host, new HandShakeComplete(this.OnHandShakeComplete));
				result = this.AsyncResult;
			}
			else if (this.ProxyType == ProxyTypes.Socks4)
			{
				this.AsyncResult = new Socks4Handler(this, this.ProxyUser).BeginNegotiate(host, port, new HandShakeComplete(this.OnHandShakeComplete), this.ProxyEndPoint);
				result = this.AsyncResult;
			}
			else if (this.ProxyType == ProxyTypes.Socks5)
			{
				this.AsyncResult = new Socks5Handler(this, this.ProxyUser, this.ProxyPass).BeginNegotiate(host, port, new HandShakeComplete(this.OnHandShakeComplete), this.ProxyEndPoint);
				result = this.AsyncResult;
			}
			else
			{
				result = null;
			}
			return result;
		}

		public new void EndConnect(IAsyncResult asyncResult)
		{
			if (asyncResult == null)
			{
				throw new ArgumentNullException();
			}
			if (!asyncResult.IsCompleted)
			{
				throw new ArgumentException();
			}
			if (this.ToThrow != null)
			{
				throw this.ToThrow;
			}
		}

		internal IAsyncProxyResult BeginDns(string host, HandShakeComplete callback)
		{
			IAsyncProxyResult result;
			try
			{
				Dns.BeginResolve(host, new AsyncCallback(this.OnResolved), this);
				result = new IAsyncProxyResult();
			}
			catch
			{
				throw new SocketException();
			}
			return result;
		}

		private void OnResolved(IAsyncResult asyncResult)
		{
			try
			{
				IPHostEntry iPHostEntry = Dns.EndResolve(asyncResult);
				base.BeginConnect(new IPEndPoint(iPHostEntry.AddressList[0], this.RemotePort), new AsyncCallback(this.OnConnect), this.State);
			}
			catch (Exception error)
			{
				this.OnHandShakeComplete(error);
			}
		}

		private void OnConnect(IAsyncResult asyncResult)
		{
			try
			{
				base.EndConnect(asyncResult);
				this.OnHandShakeComplete(null);
			}
			catch (Exception error)
			{
				this.OnHandShakeComplete(error);
			}
		}

		private void OnHandShakeComplete(Exception error)
		{
			if (error != null)
			{
				base.Close();
			}
			this.ToThrow = error;
			this.AsyncResult.Reset();
			if (this.CallBack != null)
			{
				this.CallBack(this.AsyncResult);
			}
		}
	}
}
