using System;
using System.Threading;
using Renci.SshNet.Channels;
using Renci.SshNet.Messages.Connection;
using Renci.SshNet.Common;
using System.Diagnostics;
using System.Globalization;
namespace Renci.SshNet
{
    /// 
    /// Provides functionality for remote port forwarding
    /// 
    public partial class ForwardedPortRemote : ForwardedPort, IDisposable
    {
        private bool _requestStatus;
        private EventWaitHandle _globalRequestResponse = new AutoResetEvent(false);
        /// 
        /// Starts remote port forwarding.
        /// 
        public override void Start()
        {
            base.Start();
            //  If port already started don't start it again
            if (this.IsStarted)
                return;
            this.Session.RegisterMessage("SSH_MSG_REQUEST_FAILURE");
            this.Session.RegisterMessage("SSH_MSG_REQUEST_SUCCESS");
            this.Session.RegisterMessage("SSH_MSG_CHANNEL_OPEN");
            this.Session.RequestSuccessReceived += Session_RequestSuccess;
            this.Session.RequestFailureReceived += Session_RequestFailure;
            this.Session.ChannelOpenReceived += Session_ChannelOpening;
            //  Send global request to start direct tcpip
            this.Session.SendMessage(new GlobalRequestMessage(GlobalRequestName.TcpIpForward, true, this.BoundHost, this.BoundPort));
            this.Session.WaitHandle(this._globalRequestResponse);
            if (!this._requestStatus)
            {
                //  If request  failed don't handle channel opening for this request
                this.Session.ChannelOpenReceived -= Session_ChannelOpening;
                throw new SshException(string.Format(CultureInfo.CurrentCulture, "Port forwarding for '{0}' port '{1}' failed to start.", this.Host, this.Port));
            }
            else
            {
                this.IsStarted = true;
            }
        }
        /// 
        /// Stops remote port forwarding.
        /// 
        public override void Stop()
        {
            base.Stop();
            //  If port not started you cant stop it
            if (!this.IsStarted)
                return;
            //  Send global request to cancel direct tcpip
            this.Session.SendMessage(new GlobalRequestMessage(GlobalRequestName.CancelTcpIpForward, true, this.BoundHost, this.BoundPort));
            this.Session.WaitHandle(this._globalRequestResponse);
            this.Session.RequestSuccessReceived -= Session_RequestSuccess;
            this.Session.RequestFailureReceived -= Session_RequestFailure;
            this.Session.ChannelOpenReceived -= Session_ChannelOpening;
            this.IsStarted = false;
        }
        private void Session_ChannelOpening(object sender, MessageEventArgs e)
        {
            //  Ensure that this is corresponding request
            var info = e.Message.Info as ForwardedTcpipChannelInfo;
            if (info != null)
            {
                if (info.ConnectedAddress == this.BoundHost && info.ConnectedPort == this.BoundPort)
                {
                    this.ExecuteThread(() =>
                    {
                        try
                        {
                            this.RaiseRequestReceived(info.OriginatorAddress, info.OriginatorPort);
                            var channel = this.Session.CreateChannel(e.Message.LocalChannelNumber, e.Message.InitialWindowSize, e.Message.MaximumPacketSize);
                            channel.Bind(this.Host, this.Port);
                        }
                        catch (Exception exp)
                        {
                            this.RaiseExceptionEvent(exp);
                        }
                    });
                }
            }
        }
        private void Session_RequestFailure(object sender, System.EventArgs e)
        {
            this._requestStatus = false;
            this._globalRequestResponse.Set();
        }
        private void Session_RequestSuccess(object sender, MessageEventArgs e)
        {
            this._requestStatus = true;
            if (this.BoundPort == 0)
            {
                this.BoundPort = (e.Message.BoundPort == null) ? 0 : e.Message.BoundPort.Value;
            }
            this._globalRequestResponse.Set();
        }
        partial void ExecuteThread(Action action);
        #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._globalRequestResponse != null)
                    {
                        this._globalRequestResponse.Dispose();
                        this._globalRequestResponse = null;
                    }
                }
                // Note disposing has been done.
                _isDisposed = true;
            }
        }
        /// 
        /// Releases unmanaged resources and performs other cleanup operations before the
        ///  is reclaimed by garbage collection.
        /// 
        ~ForwardedPortRemote()
        {
            // Do not re-create Dispose clean-up code here.
            // Calling Dispose(false) is optimal in terms of
            // readability and maintainability.
            Dispose(false);
        }
        #endregion
    }
}