using System;
using System.IO;
using System.Text;
using System.Threading;
using Org.Mentalis.Security.Ssl;

namespace Orciid.Core
{
	/// <summary>
	/// Simple IMAP authentication
	/// </summary>
	/// <remarks>
	/// This class allows user authentication with an IMAP server.  It does not provide
	/// any other IMAP related functionality
	/// </remarks>
	public class SimpleImapAuth
	{
		private int command = 0;
		private int _receiveTimeOut=60000;
		private int _sendTimeOut=60000;
		private int _receiveBufferSize=4090;
		private int _sendBufferSize=4090;
		private int _waitForResponseInterval=200;

		private SecureTcpClient clientSocket = null;		
		private StreamReader reader;
		private StreamWriter writer;
		
		private void WaitForResponse(ref StreamReader rdReader)
		{
			DateTime dtStart=DateTime.Now;
			TimeSpan tsSpan;
			while(!rdReader.BaseStream.CanRead)
			{
				tsSpan=DateTime.Now.Subtract(dtStart);
				if(tsSpan.Milliseconds>_receiveTimeOut)
					break;
				Thread.Sleep(_waitForResponseInterval);
			}
		}

		private bool IsOkResponse(string strResponse)
		{
			return strResponse.StartsWith("* OK") || strResponse.StartsWith(CommandPrefix + "OK");
		}

		private string CommandPrefix
		{
			get
			{
				return "A" + command + " ";
			}
		}

		private bool SendCommand(string strCommand)
		{
			command++;
			try
			{
				if(writer.BaseStream.CanWrite)
				{
					writer.WriteLine(CommandPrefix + strCommand);
					writer.Flush();
					WaitForResponse(ref reader);
					return IsOkResponse(reader.ReadLine());
				}
				else
					return false;
			}
			catch
			{
				return false;
			}
		}

		/// <summary>
		/// Connect to server
		/// </summary>
		/// <remarks>
		/// This method connects to an IMAP server and returns a boolean indicating success
		/// </remarks>
		/// <param name="host">Hostname of the server to connect to</param>
		/// <param name="port">Port to connect to</param>
		/// <param name="secure"><c>true</c> if server uses SSL to encrypt traffic, <c>false</c>
		/// for regular connections</param>
		/// <returns><c>true</c> if connection succeeded, <c>false</c> otherwise</returns>
		public bool Connect(string host, int port, bool secure)
		{
			SecureProtocol protocol = secure ? SecureProtocol.Tls1 | SecureProtocol.Ssl3 : SecureProtocol.None;
			SecurityOptions options = new SecurityOptions(protocol);
			options.Certificate = null;
			options.Entity = ConnectionEnd.Client;
			options.CommonName = host;
			options.VerificationType = CredentialVerification.Auto;
			options.Flags = SecurityFlags.Default;
			options.AllowedAlgorithms = SslAlgorithms.SECURE_CIPHERS;

			clientSocket = new SecureTcpClient(options);
			clientSocket.ReceiveTimeout=_receiveTimeOut;
			clientSocket.SendTimeout=_sendTimeOut;
			clientSocket.ReceiveBufferSize=_receiveBufferSize;
			clientSocket.SendBufferSize=_sendBufferSize;

			try
			{
				clientSocket.Connect(host, port);				
			}
			catch
			{				
				Disconnect();
				return false;
			}

			reader=new StreamReader(clientSocket.GetStream(),Encoding.Default,true);
			writer=new StreamWriter(clientSocket.GetStream());
			writer.AutoFlush=true;
		
			WaitForResponse(ref reader);

			if(IsOkResponse(reader.ReadLine()))
			{
				return true;
			}
			else
			{
				Disconnect();
				return false;
			}
		}

		/// <summary>
		/// Authenticates a user
		/// </summary>
		/// <remarks>
		/// This method tries to log into the IMAP server with the given user name and
		/// password.  The connection to the server must have been established already
		/// by calling <see cref="Connect"/>.
		/// </remarks>
		/// <param name="user">The user name to log in with. May not contain any 
		/// whitespace characters.</param>
		/// <param name="password">The password to log in with</param>
		/// <returns><c>true</c> if authentication succeeded, <c>false</c> otherwise</returns>
		public bool Authenticate(string user, string password)
		{
			if (user.IndexOfAny(" \n\r\t".ToCharArray()) >= 0 ||
				password.IndexOfAny("\n\r".ToCharArray()) >= 0)
				return false;
			return SendCommand("LOGIN " + user + " " + password);
		}

		/// <summary>
		/// Disconnects from server
		/// </summary>
		/// <remarks>
		/// This method logs out of the server and closes the connection. It can be called
		/// even if the connection has not been established.
		/// </remarks>
		public void Disconnect()
		{
			try
			{
				clientSocket.ReceiveTimeout=500;
				clientSocket.SendTimeout=500;
				SendCommand("LOGOUT");
				clientSocket.ReceiveTimeout=_receiveTimeOut;
				clientSocket.SendTimeout=_sendTimeOut;
				reader.Close();
				writer.Close();
				clientSocket.GetStream().Close();
				clientSocket.Close();
			}
			catch
			{
			}
			finally
			{
				reader=null;
				writer=null;
				clientSocket=null;
			}
		}
	}
}