using System;
using System.Net.Sockets;
using System.Threading.Tasks;

namespace ZeroLevel.Services.Network.Proxies
{
    public class ProxyBinding
        : IDisposable
    {
        private readonly Socket _left;
        private readonly Socket _right;

        public ProxyBinding(Socket left, Socket right)
        {
            _left = left;
            _right = right;
        }

        public async Task Bind()
        {
            using (var serverStream = new NetworkStream(_left))
            {
                using (var clientStream = new NetworkStream(_right))
                {
                    while (IsConnected(_left) && IsConnected(_right))
                    {
                        if (await Request(clientStream, serverStream) == 0
                            &&
                            await Response(clientStream, serverStream) == 0)
                        {
                            await Task.Delay(50);
                        }
                    }
                }
            }
        }

        private static bool IsConnected(Socket socket)
        {
            try
            {
                return !(socket.Poll(1, SelectMode.SelectRead) && socket.Available == 0);
            }
            catch (SocketException) { return false; }
        }

        private static async Task<int> Request(NetworkStream left, NetworkStream right)
        {
            int total = 0;
            if (left.DataAvailable && left.CanRead && right.CanWrite)
            {
                int count;
                byte[] buffer = new byte[32 * 1024];
                while (left.DataAvailable && (count = await left.ReadAsync(buffer, 0, buffer.Length)) > 0)
                {
                    await right.WriteAsync(buffer, 0, count);
                    total += count;
                }
            }
            return total;
        }

        private async static Task<int> Response(NetworkStream left, NetworkStream right)
        {
            int total = 0;
            if (right.DataAvailable && right.CanRead && left.CanWrite)
            {
                int count;
                byte[] buffer = new byte[32 * 1024];
                while (right.DataAvailable && (count = await right.ReadAsync(buffer, 0, buffer.Length)) > 0)
                {
                    await left.WriteAsync(buffer, 0, count);
                    total += count;
                }
            }
            return total;
        }

        public void Dispose()
        {
            if (_left != null)
            {
                try
                {
                    if (_left.Connected)
                    {
                        _left.Shutdown(SocketShutdown.Both);
                    }
                    _left.Dispose();
                }
                catch { }
            }
            if (_right != null)
            {
                try
                {
                    if (_right.Connected)
                    {
                        _right.Shutdown(SocketShutdown.Both);
                    }
                    _right.Dispose();
                }
                catch { }
            }
        }
    }
}