using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
using Renci.SshNet.Messages.Authentication;
using Renci.SshNet.Messages;
using Renci.SshNet.Common;
using System.Threading;
namespace Renci.SshNet
{
    /// 
    /// Provides functionality to perform private key authentication.
    /// 
    public class PrivateKeyAuthenticationMethod : AuthenticationMethod, IDisposable
    {
        private AuthenticationResult _authenticationResult = AuthenticationResult.Failure;
        private EventWaitHandle _authenticationCompleted = new ManualResetEvent(false);
        private bool _isSignatureRequired;
        /// 
        /// Gets authentication method name
        /// 
        public override string Name
        {
            get { return "publickey"; }
        }
        /// 
        /// Gets the key files used for authentication.
        /// 
        public ICollection KeyFiles { get; private set; }
        /// 
        /// Initializes a new instance of the  class.
        /// 
        /// The host.
        /// The port.
        /// The username.
        /// The key files.
        ///  is whitespace or null.
        public PrivateKeyAuthenticationMethod(string username, params PrivateKeyFile[] keyFiles)
            : base(username)
        {
            //  TODO:   Should throw on keyFiles == null here?
            this.KeyFiles = new Collection(keyFiles);
        }
        /// 
        /// Authenticates the specified session.
        /// 
        /// The session to authenticate.
        /// 
        public override AuthenticationResult Authenticate(Session session)
        {
            if (this.KeyFiles == null)
                return AuthenticationResult.Failure;
            session.UserAuthenticationSuccessReceived += Session_UserAuthenticationSuccessReceived;
            session.UserAuthenticationFailureReceived += Session_UserAuthenticationFailureReceived;
            session.MessageReceived += Session_MessageReceived;
            session.RegisterMessage("SSH_MSG_USERAUTH_PK_OK");
            foreach (var keyFile in this.KeyFiles)
            {
                this._authenticationCompleted.Reset();
                this._isSignatureRequired = false;
                var message = new RequestMessagePublicKey(ServiceName.Connection, this.Username, keyFile.HostKey.Name, keyFile.HostKey.Data);
                if (this.KeyFiles.Count < 2)
                {
                    //  If only one key file provided then send signature for very first request
                    var signatureData = new SignatureData(message, session.SessionId).GetBytes();
                    message.Signature = keyFile.HostKey.Sign(signatureData);
                }
                //  Send public key authentication request
                session.SendMessage(message);
                session.WaitHandle(this._authenticationCompleted);
                if (this._isSignatureRequired)
                {
                    this._authenticationCompleted.Reset();
                    var signatureMessage = new RequestMessagePublicKey(ServiceName.Connection, this.Username, keyFile.HostKey.Name, keyFile.HostKey.Data);
                    var signatureData = new SignatureData(message, session.SessionId).GetBytes();
                    signatureMessage.Signature = keyFile.HostKey.Sign(signatureData);
                    //  Send public key authentication request with signature
                    session.SendMessage(signatureMessage);
                }
                session.WaitHandle(this._authenticationCompleted);
                if (this._authenticationResult == AuthenticationResult.Success)
                {
                    break;
                }
            }
            
            session.UserAuthenticationSuccessReceived -= Session_UserAuthenticationSuccessReceived;
            session.UserAuthenticationFailureReceived -= Session_UserAuthenticationFailureReceived;
            session.MessageReceived -= Session_MessageReceived;
            session.UnRegisterMessage("SSH_MSG_USERAUTH_PK_OK");
            return this._authenticationResult;
        }
        private void Session_UserAuthenticationSuccessReceived(object sender, MessageEventArgs e)
        {
            this._authenticationResult = AuthenticationResult.Success;
            this._authenticationCompleted.Set();
        }
        private void Session_UserAuthenticationFailureReceived(object sender, MessageEventArgs e)
        {
            if (e.Message.PartialSuccess)
                this._authenticationResult = AuthenticationResult.PartialSuccess;
            else
                this._authenticationResult = AuthenticationResult.Failure;
            //  Copy allowed authentication methods
            this.AllowedAuthentications = e.Message.AllowedAuthentications.ToList();
            this._authenticationCompleted.Set();
        }
        private void Session_MessageReceived(object sender, MessageEventArgs e)
        {
            var publicKeyMessage = e.Message as PublicKeyMessage;
            if (publicKeyMessage != null)
            {
                this._isSignatureRequired = true;
                this._authenticationCompleted.Set();
            }
        }
        #region IDisposable Members
        private bool isDisposed = false;
        /// 
        /// 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 (!this.isDisposed)
            {
                // If disposing equals true, dispose all managed
                // and unmanaged resources.
                if (disposing)
                {
                    // Dispose managed resources.
                    if (this._authenticationCompleted != null)
                    {
                        this._authenticationCompleted.Dispose();
                        this._authenticationCompleted = null;
                    }
                }
                // Note disposing has been done.
                isDisposed = true;
            }
        }
        /// 
        /// Releases unmanaged resources and performs other cleanup operations before the
        ///  is reclaimed by garbage collection.
        /// 
        ~PrivateKeyAuthenticationMethod()
        {
            // Do not re-create Dispose clean-up code here.
            // Calling Dispose(false) is optimal in terms of
            // readability and maintainability.
            Dispose(false);
        }
        #endregion
        private class SignatureData : SshData
        {
            private RequestMessagePublicKey _message;
            private byte[] _sessionId;
            public SignatureData(RequestMessagePublicKey message, byte[] sessionId)
            {
                this._message = message;
                this._sessionId = sessionId;
            }
            protected override void LoadData()
            {
                throw new System.NotImplementedException();
            }
            protected override void SaveData()
            {
                this.WriteBinaryString(this._sessionId);
                this.Write((byte)50);
                this.Write(this._message.Username);
                this.WriteAscii("ssh-connection");
                this.WriteAscii("publickey");
                this.Write((byte)1);
                this.WriteAscii(this._message.PublicKeyAlgorithmName);
                this.WriteBinaryString(this._message.PublicKeyData);
            }
        }
    }
}