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

namespace FilterAPI.Networking
{
    public abstract class ClientMulti : IDisposable
    {
        private Socket ClientSocket;
        public IPEndPoint ClientEndPoint;

        private const Int32 CMaxReceiveBuffer = 5120;
        private Byte[] CReceiveBuffer;
        private Int32 CReceiveLength;

        private Socket ServerSocket;
        public IPEndPoint ServerEndPoint;

        private const Int32 SMaxReceiveBuffer = 25600;
        private Byte[] SReceiveBuffer;
        private Int32 SReceiveLength;

        public ClientMulti(Socket CS, IPEndPoint CIPEP, IPAddress SIP, Int32 SP, Boolean LingarSocket = true)
        {
            ClientSocket = CS;

            if (LingarSocket) { ClientSocket.LingerState = new LingerOption(true, 2); }

            ClientEndPoint = CIPEP;

            ServerEndPoint = new IPEndPoint(SIP, SP);

            ServerSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            if (LingarSocket) { ServerSocket.LingerState = new LingerOption(true, 2); }

            SocketAsyncEventArgs AsyncArgs = new SocketAsyncEventArgs();
            AsyncArgs.UserToken = ServerSocket;
            AsyncArgs.Completed += ServerConnected;
            AsyncArgs.RemoteEndPoint = ServerEndPoint;

            try
            {
                if (!ServerSocket.ConnectAsync(AsyncArgs)) { ServerConnected(this, AsyncArgs); }
            }
            catch
            {
                if (AsyncArgs != null) { AsyncArgs.Dispose(); }

                Dispose();
            }
        }

        private void ServerConnected(Object Sender, SocketAsyncEventArgs AsyncArgs)
        {
            if (disposed)
            {
                AsyncArgs.Dispose();

                return;
            }
            else if (AsyncArgs.SocketError != SocketError.Success)
            {
                AsyncArgs.Dispose();

                Dispose();

                return;
            }

            CReceiveBuffer = new Byte[CMaxReceiveBuffer];

            SReceiveBuffer = new Byte[SMaxReceiveBuffer];

            ReceiveClient();
            ReceiveServer();

            AsyncArgs.Dispose();
        }

        public void ReceiveClient()
        {
            if (disposed) { return; }

            try { ClientSocket.BeginReceive(CReceiveBuffer, 0, 1, SocketFlags.None, new AsyncCallback(ReceivedSmallSizeClient), null); }
            catch { Dispose(); }
        }

        private void ReceivedSmallSizeClient(IAsyncResult IAR)
        {
            if (disposed) { return; }

            Int32 ReceivedBytes;

            try { ReceivedBytes = ClientSocket.EndReceive(IAR); }
            catch
            {
                Dispose();

                return;
            }

            if (ReceivedBytes == 1)
            {
                CReceiveLength = CReceiveBuffer[0];

                if (CReceiveLength == 0)
                {
                    try { ClientSocket.BeginReceive(CReceiveBuffer, 0, 2, SocketFlags.None, new AsyncCallback(ReceivedBigSizeClient), null); }
                    catch { Dispose(); }
                }
                else
                {
                    try { ClientSocket.BeginReceive(CReceiveBuffer, 0, CReceiveLength, SocketFlags.None, new AsyncCallback(ReceivedPacketClient), null); }
                    catch { Dispose(); }
                }
            }
            else { Dispose(); }
        }

        private void ReceivedBigSizeClient(IAsyncResult IAR)
        {
            if (disposed) { return; }

            Int32 ReceivedBytes;

            try { ReceivedBytes = ClientSocket.EndReceive(IAR); }
            catch
            {
                Dispose();

                return;
            }

            if (ReceivedBytes == 2)
            {
                CReceiveLength = BitConverter.ToInt16(CReceiveBuffer, 0);

                try { ClientSocket.BeginReceive(CReceiveBuffer, 0, CReceiveLength, SocketFlags.None, new AsyncCallback(ReceivedPacketClient), null); }
                catch { Dispose(); }
            }
            else { Dispose(); }
        }

        private void ReceivedPacketClient(IAsyncResult IAR)
        {
            if (disposed) { return; }

            Int32 ReceivedBytes;

            try { ReceivedBytes = ClientSocket.EndReceive(IAR); }
            catch
            {
                Dispose();

                return;
            }

            if (ReceivedBytes == CReceiveLength)
            {
                Byte[] PacketData = new Byte[CReceiveLength];

                Buffer.BlockCopy(CReceiveBuffer, 0, PacketData, 0, CReceiveLength);

                ReceivedClient(PacketData);

                ReceiveClient();
            }
            else { Dispose(); }
        }

        public abstract void ReceivedClient(Byte[] Buffer);

        public void SendClient(Byte[] Buffer)
        {
            if (disposed) { return; }

            try { ClientSocket.BeginSend(Buffer, 0, Buffer.Length, SocketFlags.None, new AsyncCallback(SentClient), null); }
            catch { Dispose(); }
        }

        private void SentClient(IAsyncResult IAR)
        {
            if (disposed) { return; }

            Int32 SentBytes;

            try { SentBytes = ClientSocket.EndSend(IAR); }
            catch
            {
                Dispose();

                return;
            }

            if (SentBytes <= 0) { Dispose(); }
        }

        public void ReceiveServer()
        {
            if (disposed) { return; }

            try { ServerSocket.BeginReceive(SReceiveBuffer, 0, 1, SocketFlags.None, new AsyncCallback(ReceivedSmallSizeServer), null); }
            catch { Dispose(); }
        }

        private void ReceivedSmallSizeServer(IAsyncResult IAR)
        {
            if (disposed) { return; }

            Int32 ReceivedBytes;

            try { ReceivedBytes = ServerSocket.EndReceive(IAR); }
            catch
            {
                Dispose();

                return;
            }

            if (ReceivedBytes == 1)
            {
                SReceiveLength = SReceiveBuffer[0];

                if (SReceiveLength == 0)
                {
                    try { ServerSocket.BeginReceive(SReceiveBuffer, 0, 2, SocketFlags.None, new AsyncCallback(ReceivedBigSizeServer), null); }
                    catch { Dispose(); }
                }
                else
                {
                    try { ServerSocket.BeginReceive(SReceiveBuffer, 0, SReceiveLength, SocketFlags.None, new AsyncCallback(ReceivedPacketServer), null); }
                    catch { Dispose(); }
                }
            }
            else { Dispose(); }
        }

        private void ReceivedBigSizeServer(IAsyncResult IAR)
        {
            if (disposed) { return; }

            Int32 ReceivedBytes;

            try { ReceivedBytes = ServerSocket.EndReceive(IAR); }
            catch
            {
                Dispose();

                return;
            }

            if (ReceivedBytes == 2)
            {
                SReceiveLength = BitConverter.ToInt16(SReceiveBuffer, 0);

                try { ServerSocket.BeginReceive(SReceiveBuffer, 0, SReceiveLength, SocketFlags.None, new AsyncCallback(ReceivedPacketServer), null); }
                catch { Dispose(); }
            }
            else { Dispose(); }
        }

        private void ReceivedPacketServer(IAsyncResult IAR)
        {
            if (disposed) { return; }

            Int32 ReceivedBytes;

            try { ReceivedBytes = ServerSocket.EndReceive(IAR); }
            catch
            {
                Dispose();

                return;
            }

            if (ReceivedBytes == SReceiveLength)
            {
                Byte[] PacketData = new Byte[SReceiveLength];

                Buffer.BlockCopy(SReceiveBuffer, 0, PacketData, 0, SReceiveLength);

                ReceivedServer(PacketData);

                ReceiveServer();
            }
            else { Dispose(); }
        }

        public abstract void ReceivedServer(Byte[] Buffer);

        public void SendServer(Byte[] Buffer)
        {
            if (disposed) { return; }

            try { ServerSocket.BeginSend(Buffer, 0, Buffer.Length, SocketFlags.None, new AsyncCallback(SentServer), null); }
            catch { Dispose(); }
        }

        private void SentServer(IAsyncResult IAR)
        {
            if (disposed) { return; }

            Int32 SentBytes;

            try { SentBytes = ServerSocket.EndSend(IAR); }
            catch
            {
                Dispose();

                return;
            }

            if (SentBytes <= 0) { Dispose(); }
        }

        public abstract void Disconnected();

        private bool disposed = false;

        protected virtual void Dispose(bool disposing)
        {
            if (!disposed)
            {
                if (disposing)
                {
                    if (ServerSocket != null)
                    {
                        try
                        {
                            ServerSocket.Close();
                            ServerSocket.Dispose();
                        }
                        catch { }
                    }

                    if (ClientSocket != null)
                    {
                        try
                        {
                            ClientSocket.Close();
                            ClientSocket.Dispose();
                        }
                        catch { }
                    }
                }

                disposed = true;

                Disconnected();
            }
        }

        public void Dispose() { Dispose(true); }

        ~ClientMulti() { Dispose(false); }
    }
}
