using System.Linq;
using System;
using System.Net.Sockets;
using System.Net;
using Renci.SshNet.Messages;
using Renci.SshNet.Common;
using System.Threading;
using Renci.SshNet.Messages.Transport;
namespace Renci.SshNet
{
    /// 
    /// Provides functionality to connect and interact with SSH server.
    /// 
    public partial class Session
    {
        partial void ExecuteThread(Action action)
        {
            ThreadPool.QueueUserWorkItem((o) => { action(); });
        }
        partial void InternalRegisterMessage(string messageName)
        {
            lock (this._messagesMetadata)
            {
                foreach (var m in from m in this._messagesMetadata where m.Name == messageName select m)
                {
                    m.Enabled = true; 
                    m.Activated = true;
                }
            }
        }
        partial void InternalUnRegisterMessage(string messageName)
        {
            lock (this._messagesMetadata)
            {
                foreach (var m in from m in this._messagesMetadata where m.Name == messageName select m)
                {
                    m.Enabled = false;
                    m.Activated = false;
                }
            }
        }
        partial void OpenSocket()
        {
            var ep = new IPEndPoint(Dns.GetHostAddresses(this.ConnectionInfo.Host)[0], this.ConnectionInfo.Port);
            this._socket = new Socket(ep.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
            var socketBufferSize = 2 * MAXIMUM_PACKET_SIZE;
            this._socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true);
            this._socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendBuffer, socketBufferSize);
            this._socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveBuffer, socketBufferSize);
            //  Connect socket with 5 seconds timeout
            var connectResult = this._socket.BeginConnect(ep, null, null);
            connectResult.AsyncWaitHandle.WaitOne(this.ConnectionInfo.Timeout);
            //  Build list of available messages while connecting
            this._messagesMetadata = (from type in this.GetType().Assembly.GetTypes()
                                      from messageAttribute in type.GetCustomAttributes(false).OfType()
                                      select new MessageMetadata
                                      {
                                          Name = messageAttribute.Name,
                                          Number = messageAttribute.Number,
                                          Enabled = false,
                                          Activated = false,
                                          Type = type,
                                      }).ToList();
            this._socket.EndConnect(connectResult);
        }
        partial void InternalRead(int length, ref byte[] buffer)
        {
            var offset = 0;
            int receivedTotal = 0;  // how many bytes is already received
            do
            {
                try
                {
                    var receivedBytes = this._socket.Receive(buffer, offset + receivedTotal, length - receivedTotal, SocketFlags.None);
                    if (receivedBytes > 0)
                    {
                        receivedTotal += receivedBytes;
                        continue;
                    }
                    else
                    {
                        throw new SshConnectionException("An established connection was aborted by the software in your host machine.", DisconnectReason.ConnectionLost);
                    }
                }
                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
                        throw;  // any serious error occurred
                }
            } while (receivedTotal < length);
        }
        partial void Write(byte[] data)
        {
            int sent = 0;  // how many bytes is already sent
            int length = data.Length;
            do
            {
                try
                {
                    sent += this._socket.Send(data, sent, length - sent, SocketFlags.None);
                }
                catch (SocketException ex)
                {
                    if (ex.SocketErrorCode == SocketError.WouldBlock ||
                        ex.SocketErrorCode == SocketError.IOPending ||
                        ex.SocketErrorCode == SocketError.NoBufferSpaceAvailable)
                    {
                        // socket buffer is probably full, wait and try again
                        Thread.Sleep(30);
                    }
                    else
                        throw;  // any serious error occurr
                }
            } while (sent < length);
        }
    }
}