using System;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using Renci.SshNet.Common;
using Renci.SshNet.Messages.Connection;
namespace Renci.SshNet.Channels
{
    /// 
    /// Implements "forwarded-tcpip" SSH channel.
    /// 
    internal partial class ChannelForwardedTcpip : Channel
    {
        private Socket _socket;
        /// 
        /// Gets the type of the channel.
        /// 
        /// 
        /// The type of the channel.
        /// 
        public override ChannelTypes ChannelType
        {
            get { return ChannelTypes.ForwardedTcpip; }
        }
        /// 
        /// Initializes a new instance of the  class.
        /// 
        public ChannelForwardedTcpip()
            : base()
        {
        }
        /// 
        /// Binds channel to specified connected host.
        /// 
        /// The connected host.
        /// The connected port.
        public void Bind(string connectedHost, uint connectedPort)
        {
            byte[] buffer = null;
            this.ServerWindowSize = this.LocalWindowSize;
            if (!this.IsConnected)
            {
                throw new SshException("Session is not connected.");
            }
            //  Try to connect to the socket 
            try
            {
                //  Get buffer in memory for data exchange
                buffer = new byte[this.PacketSize - 9];
                this.OpenSocket(connectedHost, connectedPort);
                //  Send channel open confirmation message
                this.SendMessage(new ChannelOpenConfirmationMessage(this.RemoteChannelNumber, this.LocalWindowSize, this.PacketSize, this.LocalChannelNumber));
            }
            catch (Exception exp)
            {
                //  Send channel open failure message
                this.SendMessage(new ChannelOpenFailureMessage(this.RemoteChannelNumber, exp.ToString(), 2));
                throw;
            }
            //  Start reading data from the port and send to channel
            while (this._socket.Connected || this.IsConnected)
            {
                try
                {
                    int read = 0;
                    this.InternalSocketReceive(buffer, ref read);
                    if (read > 0)
                    {
                        this.SendMessage(new ChannelDataMessage(this.RemoteChannelNumber, buffer.Take(read).ToArray()));
                    }
                    else
                    {
                        //  Zero bytes received when remote host shuts down the connection
                        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
                }
            }
            this.Close();
        }
        partial void OpenSocket(string connectedHost, uint connectedPort);
        public override void Close()
        {
            //  Send EOF message first when channel need to be closed
            this.SendMessage(new ChannelEofMessage(this.RemoteChannelNumber));
            base.Close();
        }
        /// 
        /// Called when channel data is received.
        /// 
        /// The data.
        protected override void OnData(byte[] data)
        {
            base.OnData(data);
            //  Read data from the channel and send it to the port
            this.InternalSocketSend(data);
        }
        partial void InternalSocketSend(byte[] data);
        
        partial void InternalSocketReceive(byte[] buffer, ref int read);
        /// 
        /// Releases unmanaged and - optionally - managed resources
        /// 
        /// true to release both managed and unmanaged resources; false to release only unmanaged resources.
        protected override void Dispose(bool disposing)
        {
            if (this._socket != null)
            {
                this._socket.Dispose();
                this._socket = null;
            }
            base.Dispose(disposing);
        }
    }
}