using System;
using HG;
using RoR2.ConVar;
using UnityEngine;
using UnityEngine.Networking;

namespace RoR2.Networking;

public static class RttManager
{
	private struct ConnectionRttInfo
	{
		public readonly NetworkConnection connection;

		public float newestRttInSeconds;

		public uint newestRttInMilliseconds;

		public float frameSmoothedRtt;

		public float frameVelocity;

		public float fixedSmoothedRtt;

		public float fixedVelocity;

		public ConnectionRttInfo(NetworkConnection connection)
		{
			this.connection = connection;
			newestRttInMilliseconds = 0u;
			newestRttInSeconds = 0f;
			frameSmoothedRtt = 0f;
			frameVelocity = 0f;
			fixedSmoothedRtt = 0f;
			fixedVelocity = 0f;
		}
	}

	private class PingMessage : MessageBase
	{
		public uint timeStampMs;

		public override void Serialize(NetworkWriter writer)
		{
			writer.WritePackedUInt32(timeStampMs);
		}

		public override void Deserialize(NetworkReader reader)
		{
			timeStampMs = reader.ReadPackedUInt32();
		}
	}

	private static ConnectionRttInfo[] entries = Array.Empty<ConnectionRttInfo>();

	private static readonly FloatConVar cvRttSmoothDuration = new FloatConVar("net_rtt_smooth_duration", ConVarFlags.None, "0.1", "The smoothing duration for round-trip time values.");

	public static float GetConnectionRTT(NetworkConnection connection)
	{
		if (FindConnectionIndex(connection, out var entryIndex))
		{
			return entries[entryIndex].newestRttInSeconds;
		}
		return 0f;
	}

	public static uint GetConnectionRTTInMilliseconds(NetworkConnection connection)
	{
		if (FindConnectionIndex(connection, out var entryIndex))
		{
			return entries[entryIndex].newestRttInMilliseconds;
		}
		return 0u;
	}

	public static float GetConnectionFrameSmoothedRtt(NetworkConnection connection)
	{
		if (FindConnectionIndex(connection, out var entryIndex))
		{
			return entries[entryIndex].frameSmoothedRtt;
		}
		return 0f;
	}

	public static float GetConnectionFixedSmoothedRtt(NetworkConnection connection)
	{
		if (FindConnectionIndex(connection, out var entryIndex))
		{
			return entries[entryIndex].fixedSmoothedRtt;
		}
		return 0f;
	}

	private static void OnConnectionDiscovered(NetworkConnection connection)
	{
		ConnectionRttInfo value = new ConnectionRttInfo(connection);
		ArrayUtils.ArrayAppend(ref entries, in value);
	}

	private static void OnConnectionLost(NetworkConnection connection)
	{
		if (FindConnectionIndex(connection, out var entryIndex))
		{
			ArrayUtils.ArrayRemoveAtAndResize(ref entries, entryIndex);
		}
	}

	private static bool FindConnectionIndex(NetworkConnection connection, out int entryIndex)
	{
		ConnectionRttInfo[] array = entries;
		for (int i = 0; i < array.Length; i++)
		{
			if (array[i].connection == connection)
			{
				entryIndex = i;
				return true;
			}
		}
		entryIndex = -1;
		return false;
	}

	private static void UpdateFilteredRtt(float deltaTime, float targetValue, ref float currentValue, ref float velocity)
	{
		if (currentValue == 0f)
		{
			currentValue = targetValue;
			velocity = 0f;
		}
		else
		{
			currentValue = Mathf.SmoothDamp(currentValue, targetValue, ref velocity, cvRttSmoothDuration.value, 100f, deltaTime);
		}
	}

	[RuntimeInitializeOnLoadMethod]
	private static void Init()
	{
		NetworkManagerSystem.onClientConnectGlobal += OnConnectionDiscovered;
		NetworkManagerSystem.onClientDisconnectGlobal += OnConnectionLost;
		NetworkManagerSystem.onServerConnectGlobal += OnConnectionDiscovered;
		NetworkManagerSystem.onServerDisconnectGlobal += OnConnectionLost;
		RoR2Application.onUpdate += Update;
		RoR2Application.onFixedUpdate += FixedUpdate;
	}

	private static void Update()
	{
		float deltaTime = Time.deltaTime;
		ConnectionRttInfo[] array = entries;
		for (int i = 0; i < array.Length; i++)
		{
			ref ConnectionRttInfo reference = ref array[i];
			UpdateFilteredRtt(deltaTime, reference.newestRttInSeconds, ref reference.frameSmoothedRtt, ref reference.frameVelocity);
		}
	}

	private static void FixedUpdate()
	{
		float fixedDeltaTime = Time.fixedDeltaTime;
		ConnectionRttInfo[] array = entries;
		for (int i = 0; i < array.Length; i++)
		{
			ref ConnectionRttInfo reference = ref array[i];
			UpdateFilteredRtt(fixedDeltaTime, reference.newestRttInSeconds, ref reference.fixedSmoothedRtt, ref reference.fixedVelocity);
		}
	}

	[NetworkMessageHandler(msgType = 65, client = true, server = true)]
	private static void HandlePing(NetworkMessage netMsg)
	{
		NetworkReader reader = netMsg.reader;
		netMsg.conn.SendByChannel(66, reader.ReadMessage<PingMessage>(), netMsg.channelId);
	}

	[NetworkMessageHandler(msgType = 66, client = true, server = true)]
	private static void HandlePingResponse(NetworkMessage netMsg)
	{
		uint timeStampMs = netMsg.reader.ReadMessage<PingMessage>().timeStampMs;
		uint num = (uint)(int)RoR2Application.stopwatch.ElapsedMilliseconds - timeStampMs;
		if (FindConnectionIndex(netMsg.conn, out var entryIndex))
		{
			ref ConnectionRttInfo reference = ref entries[entryIndex];
			reference.newestRttInMilliseconds = num;
			reference.newestRttInSeconds = (float)num * 0.001f;
		}
	}

	public static void Ping(NetworkConnection conn, int channelId)
	{
		conn.SendByChannel(65, new PingMessage
		{
			timeStampMs = (uint)RoR2Application.stopwatch.ElapsedMilliseconds
		}, channelId);
	}
}
