using System;
using System.Net;
using System.Net.Sockets;
using Villain.Networking.Packets;

namespace Villain.Networking
{
	public class Client : IDisposable
	{
		private Socket _socket;
		private byte[] _buffer = new byte[1024];

		public bool IsConnected = true;
		public IPEndPoint RemoteIPEndPoint;

		private IPacket _incompletePacket;

		public Action<IPacket> OnRecieve;
		public Action<Client> OnDisconnect;

		public Client(Socket socket)
		{
			_socket = socket;

			RemoteIPEndPoint = (IPEndPoint)socket.RemoteEndPoint;

			BeginRecieve();
		}

		~Client()
		{
			Dispose();
		}

		public void Dispose()
		{
			Disconnect();

			_socket.Dispose();
		}

		public void Disconnect()
		{
			if (IsConnected)
			{
				IsConnected = false;

				_socket.Close();

				OnDisconnect?.Invoke(this);
			}
		}

		private void BeginRecieve()
		{
			if (IsConnected)
			{
				try
				{
					_socket.BeginReceive(_buffer, 0, _buffer.Length, SocketFlags.None, new AsyncCallback(Recieve), null);
				}
				catch
				{
					Disconnect();
				}
			}
		}

		private void Recieve(IAsyncResult asyncResult)
		{
			if (!IsConnected) return;

			SocketError socketError;

			int recievedLength = _socket.EndReceive(asyncResult, out socketError);

			if (socketError != SocketError.Success || recievedLength <= 0)
			{
				Disconnect();

				return;
			}
			
			byte[] data = new byte[recievedLength];

			Array.Copy(_buffer, data, data.Length);

			Recieve(data);

			BeginRecieve();
		}

		public virtual void Recieve(byte[] data)
		{
			if (_incompletePacket != null)
			{
				data = _incompletePacket.AppendMissingData(data);

				if (_incompletePacket.MissingDataLength == 0)
				{
					Recieve(_incompletePacket);

					_incompletePacket = null;
				}
			}

			if (data.Length > 0)
			{
				IPacket packet = CreatePacket(data);

				if (packet.MissingDataLength > 0)
				{
					_incompletePacket = packet;
				}
				else
				{
					byte[] extraData = null;

					if (packet.ExtraData?.Length > 0)
					{
						extraData = packet.ExtraData;

						packet.ExtraData = null;
					}

					Recieve(packet);

					if (extraData != null)
					{
						Recieve(extraData);
					}
				}
			}
		}

		public virtual void Recieve(IPacket packet)
		{
			OnRecieve?.Invoke(packet);
		}

		public virtual void Send(IPacket packet)
		{
			Send(packet.GetData());
		}

		public virtual void Send(byte[] data)
		{
			_socket.Send(data);
		}

		public virtual IPacket CreatePacket(byte[] data)
		{
			return new Packet(data);
		}
	}
}