using System;
using System.Linq;
using System.Text;
using System.Threading;
using Renci.SshNet.Common;
using Renci.SshNet.Messages.Authentication;
using Renci.SshNet.Messages;
namespace Renci.SshNet
{
    /// 
    /// Provides functionality to perform password authentication.
    /// 
    public partial class PasswordAuthenticationMethod : AuthenticationMethod, IDisposable
    {
        private AuthenticationResult _authenticationResult = AuthenticationResult.Failure;
        private Session _session;
        private EventWaitHandle _authenticationCompleted = new AutoResetEvent(false);
        private Exception _exception;
        private readonly RequestMessage _requestMessage;
        private readonly byte[] _password;
        /// 
        /// Gets authentication method name
        /// 
        public override string Name
        {
            get { return _requestMessage.MethodName; }
        }
        /// 
        /// Occurs when user's password has expired and needs to be changed.
        /// 
        public event EventHandler PasswordExpired;
        /// 
        /// Initializes a new instance of the  class.
        /// 
        /// The username.
        /// The password.
        ///  is whitespace or null.
        ///  is null.
        public PasswordAuthenticationMethod(string username, string password)
            : this(username, Encoding.UTF8.GetBytes(password))
        {
        }
        /// 
        /// Initializes a new instance of the  class.
        /// 
        /// The username.
        /// The password.
        ///  is whitespace or null.
        ///  is null.
        public PasswordAuthenticationMethod(string username, byte[] password)
            : base(username)
        {
            if (password == null)
                throw new ArgumentNullException("password");
            _password = password;
            _requestMessage = new RequestMessagePassword(ServiceName.Connection, Username, _password);
        }
        /// 
        /// Authenticates the specified session.
        /// 
        /// The session to authenticate.
        /// 
        /// Result of authentication  process.
        /// 
        ///  is null.
        public override AuthenticationResult Authenticate(Session session)
        {
            if (session == null)
                throw new ArgumentNullException("session");
            _session = session;
            session.UserAuthenticationSuccessReceived += Session_UserAuthenticationSuccessReceived;
            session.UserAuthenticationFailureReceived += Session_UserAuthenticationFailureReceived;
            session.MessageReceived += Session_MessageReceived;
            session.RegisterMessage("SSH_MSG_USERAUTH_PASSWD_CHANGEREQ");
            session.SendMessage(_requestMessage);
            session.WaitOnHandle(_authenticationCompleted);
            
            session.UserAuthenticationSuccessReceived -= Session_UserAuthenticationSuccessReceived;
            session.UserAuthenticationFailureReceived -= Session_UserAuthenticationFailureReceived;
            session.MessageReceived -= Session_MessageReceived;
            if (_exception != null)
            {
                throw _exception;
            }
            return _authenticationResult;
        }
        private void Session_UserAuthenticationSuccessReceived(object sender, MessageEventArgs e)
        {
            _authenticationResult = AuthenticationResult.Success;
            _authenticationCompleted.Set();
        }
        private void Session_UserAuthenticationFailureReceived(object sender, MessageEventArgs e)
        {
            if (e.Message.PartialSuccess)
                _authenticationResult = AuthenticationResult.PartialSuccess;
            else
                _authenticationResult = AuthenticationResult.Failure;
            //  Copy allowed authentication methods
            AllowedAuthentications = e.Message.AllowedAuthentications.ToList();
            _authenticationCompleted.Set();
        }
        private void Session_MessageReceived(object sender, MessageEventArgs e)
        {
            if (e.Message is PasswordChangeRequiredMessage)
            {
                _session.UnRegisterMessage("SSH_MSG_USERAUTH_PASSWD_CHANGEREQ");
                ExecuteThread(() =>
                {
                    try
                    {
                        var eventArgs = new AuthenticationPasswordChangeEventArgs(Username);
                        //  Raise an event to allow user to supply a new password
                        if (PasswordExpired != null)
                        {
                            PasswordExpired(this, eventArgs);
                        }
                        //  Send new authentication request with new password
                        _session.SendMessage(new RequestMessagePassword(ServiceName.Connection, Username, _password, eventArgs.NewPassword));
                    }
                    catch (Exception exp)
                    {
                        _exception = exp;
                        _authenticationCompleted.Set();
                    }
                });
            }
        }
        partial void ExecuteThread(Action action);
        #region IDisposable Members
        private bool _isDisposed;
        /// 
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// 
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        /// 
        /// Releases unmanaged and - optionally - managed resources
        /// 
        /// true to release both managed and unmanaged resources; false to release only unmanaged resources.
        protected virtual void Dispose(bool disposing)
        {
            // Check to see if Dispose has already been called.
            if (!_isDisposed)
            {
                // If disposing equals true, dispose all managed
                // and unmanaged resources.
                if (disposing)
                {
                    // Dispose managed resources.
                    if (_authenticationCompleted != null)
                    {
                        _authenticationCompleted.Dispose();
                        _authenticationCompleted = null;
                    }
                }
                // Note disposing has been done.
                _isDisposed = true;
            }
        }
        /// 
        /// Releases unmanaged resources and performs other cleanup operations before the
        ///  is reclaimed by garbage collection.
        /// 
        ~PasswordAuthenticationMethod()
        {
            // Do not re-create Dispose clean-up code here.
            // Calling Dispose(false) is optimal in terms of
            // readability and maintainability.
            Dispose(false);
        }
        #endregion
    }
}