using System;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using Renci.SshClient.Common;
using Renci.SshClient.Messages.Connection;
namespace Renci.SshClient.Channels
{
    /// 
    /// Implements "direct-tcpip" SSH channel.
    /// 
    internal class ChannelDirectTcpip : Channel
    {
        public EventWaitHandle _channelEof = new AutoResetEvent(false);
        private EventWaitHandle _channelOpen = new AutoResetEvent(false);
        private EventWaitHandle _channelData = new AutoResetEvent(false);
        private Socket _socket;
        /// 
        /// Gets the type of the channel.
        /// 
        /// 
        /// The type of the channel.
        /// 
        public override ChannelTypes ChannelType
        {
            get { return ChannelTypes.DirectTcpip; }
        }
        /// 
        /// Initializes a new instance of the  class.
        /// 
        public ChannelDirectTcpip()
            : base()
        {
        }
        /// 
        /// Binds channel to specified remote host.
        /// 
        /// The remote host.
        /// The port.
        /// The socket.
        public void Bind(string remoteHost, uint port, Socket socket)
        {
            this._socket = socket;
            IPEndPoint ep = socket.RemoteEndPoint as IPEndPoint;
            
            if (!this.IsConnected)
            {
                throw new SshException("Session is not connected.");
            }
            //  Open channel
            this.SendMessage(new ChannelOpenMessage(this.LocalChannelNumber, this.LocalWindowSize, this.PacketSize,
                                                        new DirectTcpipChannelInfo(remoteHost, port, ep.Address.ToString(), (uint)ep.Port)));
            //  Wait for channel to open
            this.WaitHandle(this._channelOpen);
            //  Start reading data from the port and send to channel
            EventWaitHandle readerTaskError = new AutoResetEvent(false);
            var readerTask = Task.Factory.StartNew(() =>
            {
                try
                {
                    var buffer = new byte[this.PacketSize - 9];
                    while (this._socket.Connected || this.IsConnected)
                    {
                        try
                        {
                            var read = this._socket.Receive(buffer);
                            if (read > 0)
                            {
                                this.SendMessage(new ChannelDataMessage(this.RemoteChannelNumber, buffer.Take(read).ToArray()));
                            }
                            else
                            {
                                break;
                            }
                        }
                        catch (SocketException exp)
                        {
                            if (exp.SocketErrorCode == SocketError.WouldBlock ||
                                exp.SocketErrorCode == SocketError.IOPending ||
                                exp.SocketErrorCode == SocketError.NoBufferSpaceAvailable)
                            {
                                // socket buffer is probably empty, wait and try again
                                Thread.Sleep(30);
                            }
                            else if (exp.SocketErrorCode == SocketError.ConnectionAborted)
                            {
                                break;
                            }
                            else
                                throw;  // throw any other error
                        }
                    }
                }
                catch (Exception)
                {
                    readerTaskError.Set();
                    throw;
                }
            });
            //  Channel was open and we MUST receive EOF notification, 
            //  data transfer can take longer then connection specified timeout
            System.Threading.WaitHandle.WaitAny(new WaitHandle[] { this._channelEof, readerTaskError });
            this._socket.Dispose();
            this._socket = null;
            //  Wait for task to finish and will throw any errors if any
            readerTask.Wait();
        }
        /// 
        /// Called when channel data is received.
        /// 
        /// The data.
        protected override void OnData(byte[] data)
        {
            base.OnData(data);
            this._socket.Send(data, 0, data.Length, SocketFlags.None);
        }
        /// 
        /// Called when channel is opened by the server.
        /// 
        /// The remote channel number.
        /// Initial size of the window.
        /// Maximum size of the packet.
        protected override void OnOpenConfirmation(uint remoteChannelNumber, uint initialWindowSize, uint maximumPacketSize)
        {
            base.OnOpenConfirmation(remoteChannelNumber, initialWindowSize, maximumPacketSize);
            this._channelOpen.Set();
        }
        /// 
        /// Called when channel has no more data to receive.
        /// 
        protected override void OnEof()
        {
            base.OnEof();
            this._channelEof.Set();
        }
        protected override void Dispose(bool disposing)
        {
            if (this._socket != null)
            {
                this._socket.Dispose();
                this._socket = null;
            }
            if (this._channelEof != null)
            {
                this._channelEof.Dispose();
                this._channelEof = null;
            }
            if (this._channelOpen != null)
            {
                this._channelOpen.Dispose();
                this._channelOpen = null;
            }
            if (this._channelData != null)
            {
                this._channelData.Dispose();
                this._channelData = null;
            }
            base.Dispose(disposing);
        }
    }
}