using System;
using System.Net;
using System.Threading;
using System.Net.Sockets;
using System.Collections.Concurrent;

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

        public Boolean Ping;
        public Boolean SendName;
        public String ServiceName;

        private const Int32 MaxReceiveBuffer = 1024;
        private Byte[] ReceiveBuffer;
        private Int32 ReceiveLength;

        public Int32 IsSending;

        public Client(IPAddress ConnectIP, Int32 ConnectPort, Boolean P = true)
        {
            ClientEndPoint = new IPEndPoint(ConnectIP, ConnectPort);

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

            Ping = P;

            SocketAsyncEventArgs AsyncArgs = new SocketAsyncEventArgs();
            AsyncArgs.UserToken = ClientSocket;
            AsyncArgs.Completed += ConnectionOpened;
            AsyncArgs.RemoteEndPoint = ClientEndPoint;

            try
            {
                if (!ClientSocket.ConnectAsync(AsyncArgs)) { ConnectionOpened(this, AsyncArgs); }
            }
            catch
            {
                AsyncArgs.Dispose();

                Dispose();
            }
        }

        private void ConnectionOpened(Object Sender, SocketAsyncEventArgs AsyncArgs)
        {
            if (disposed)
            {
                if (AsyncArgs != null) { AsyncArgs.Dispose(); }

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

                Dispose();

                return;
            }

            ReceiveBuffer = new Byte[MaxReceiveBuffer];

            Receive();

            if (Ping) { StartPacketSender(1, 1, 5); }

            Connected();

            AsyncArgs.Dispose();
        }

        public abstract void Connected();

        private Boolean PSenderIsDone = true;

        public void StartPacketSender(Byte Header, Byte Type, Int32 Time)
        {
            Thread NewThread = new Thread(delegate ()
            {
                while (!disposed)
                {
                    if (PSenderIsDone) { RunPSender(Header, Type); }

                    Thread.Sleep(Time * 1000);
                }
            });

            NewThread.Start();
        }

        private void RunPSender(Byte Header, Byte Type)
        {
            if (disposed) { return; }

            PSenderIsDone = false;

            using (var P = new Packet(Header, Type))
            {
                Byte[] PacketBuffer;

                P.ToArray(out PacketBuffer);

                Send(PacketBuffer);
            }

            PSenderIsDone = true;
        }

        public Client(Socket AcceptedSocket, IPEndPoint AcceptedEndPoint)
        {
            ClientSocket = AcceptedSocket;

            ClientEndPoint = AcceptedEndPoint;

            ReceiveBuffer = new Byte[MaxReceiveBuffer];

            Receive();
        }

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

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

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

            Int32 ReceivedBytes;

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

                return;
            }

            if (ReceivedBytes == 1)
            {
                ReceiveLength = ReceiveBuffer[0];

                if (ReceiveLength == 0)
                {
                    try { ClientSocket.BeginReceive(ReceiveBuffer, 0, 2, SocketFlags.None, new AsyncCallback(ReceivedBigSize), null); }
                    catch { Dispose(); }
                }
                else
                {
                    try { ClientSocket.BeginReceive(ReceiveBuffer, 0, ReceiveLength, SocketFlags.None, new AsyncCallback(ReceivedPacket), null); }
                    catch { Dispose(); }
                }
            }
            else { Dispose(); }
        }

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

            Int32 ReceivedBytes;

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

                return;
            }

            if (ReceivedBytes == 2)
            {
                ReceiveLength = BitConverter.ToInt16(ReceiveBuffer, 0);
				ReceiveLength = ReceiveBuffer.Length;
                try {
					ClientSocket.BeginReceive(ReceiveBuffer, 0, ReceiveLength, SocketFlags.None, new AsyncCallback(ReceivedPacket), null); }
                catch { Dispose(); }
            }
            else { Dispose(); }
        }

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

            Int32 ReceivedBytes;

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

                return;
            }

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

                Buffer.BlockCopy(ReceiveBuffer, 0, PacketData, 0, ReceiveLength);

                Received(PacketData);

                Thread.Sleep(1);

                Receive();
            }
            else { Dispose(); }
        }

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

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

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

            Int32 SentBytes;

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

                return;
            }

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

        public abstract void Received(Byte[] Buffer);

        public abstract void Disconnected();

        private bool disposed = false;

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

                disposed = true;

                Disconnected();
            }
        }

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

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