using QuantumUNET.Components;
using QuantumUNET.Messages;
using System;
using System.Text;
using UnityEngine;
using UnityEngine.Networking;

namespace QuantumUNET.Transport
{
	public class QNetworkReader
	{
		private readonly QNetBuffer m_buf;
		private static byte[] s_StringReaderBuffer;
		private static Encoding s_Encoding;

		public QNetworkReader(QNetworkWriter writer)
		{
			m_buf = new QNetBuffer(writer.AsArray());
			Initialize();
		}

		public QNetworkReader(byte[] buffer)
		{
			m_buf = new QNetBuffer(buffer);
			Initialize();
		}

		private static void Initialize()
		{
			if (s_Encoding == null)
			{
				s_StringReaderBuffer = new byte[1024];
				s_Encoding = new UTF8Encoding();
			}
		}

		public uint Position => m_buf.Position;

		public int Length => m_buf.Length;

		public void SeekZero() => m_buf.SeekZero();

		internal void Replace(byte[] buffer) => m_buf.Replace(buffer);

		public uint ReadPackedUInt32()
		{
			var b = ReadByte();
			uint result;
			if (b < 241)
			{
				result = b;
			}
			else
			{
				var b2 = ReadByte();
				if (b is >= 241 and <= 248)
				{
					result = 240U + (256U * (uint)(b - 241)) + b2;
				}
				else
				{
					var b3 = ReadByte();
					if (b == 249)
					{
						result = 2288U + (256U * b2) + b3;
					}
					else
					{
						var b4 = ReadByte();
						if (b == 250)
						{
							result = (uint)(b2 + (b3 << 8) + (b4 << 16));
						}
						else
						{
							var b5 = ReadByte();
							if (b < 251)
							{
								throw new IndexOutOfRangeException($"ReadPackedUInt32() failure: {b}");
							}

							result = (uint)(b2 + (b3 << 8) + (b4 << 16) + (b5 << 24));
						}
					}
				}
			}

			return result;
		}

		public ulong ReadPackedUInt64()
		{
			var b = ReadByte();
			ulong result;
			if (b < 241)
			{
				result = b;
			}
			else
			{
				var b2 = ReadByte();
				if (b is >= 241 and <= 248)
				{
					result = 240UL + (256UL * (b - 241UL)) + b2;
				}
				else
				{
					var b3 = ReadByte();
					if (b == 249)
					{
						result = 2288UL + (256UL * b2) + b3;
					}
					else
					{
						var b4 = ReadByte();
						if (b == 250)
						{
							result = b2 + ((ulong)b3 << 8) + ((ulong)b4 << 16);
						}
						else
						{
							var b5 = ReadByte();
							if (b == 251)
							{
								result = b2 + ((ulong)b3 << 8) + ((ulong)b4 << 16) + ((ulong)b5 << 24);
							}
							else
							{
								var b6 = ReadByte();
								if (b == 252)
								{
									result = b2 + ((ulong)b3 << 8) + ((ulong)b4 << 16) + ((ulong)b5 << 24) + ((ulong)b6 << 32);
								}
								else
								{
									var b7 = ReadByte();
									if (b == 253)
									{
										result = b2 + ((ulong)b3 << 8) + ((ulong)b4 << 16) + ((ulong)b5 << 24) + ((ulong)b6 << 32) + ((ulong)b7 << 40);
									}
									else
									{
										var b8 = ReadByte();
										if (b == 254)
										{
											result = b2 + ((ulong)b3 << 8) + ((ulong)b4 << 16) + ((ulong)b5 << 24) + ((ulong)b6 << 32) + ((ulong)b7 << 40) + ((ulong)b8 << 48);
										}
										else
										{
											var b9 = ReadByte();
											if (b != 255)
											{
												throw new IndexOutOfRangeException($"ReadPackedUInt64() failure: {b}");
											}

											result = b2 + ((ulong)b3 << 8) + ((ulong)b4 << 16) + ((ulong)b5 << 24) + ((ulong)b6 << 32) + ((ulong)b7 << 40) + ((ulong)b8 << 48) + ((ulong)b9 << 56);
										}
									}
								}
							}
						}
					}
				}
			}

			return result;
		}

		public NetworkInstanceId ReadNetworkId() => new(ReadPackedUInt32());

		public NetworkSceneId ReadSceneId() => new(ReadPackedUInt32());

		public byte ReadByte() => m_buf.ReadByte();

		public sbyte ReadSByte() => (sbyte)m_buf.ReadByte();

		public short ReadInt16()
		{
			ushort num = 0;
			num |= m_buf.ReadByte();
			num |= (ushort)(m_buf.ReadByte() << 8);
			return (short)num;
		}

		public ushort ReadUInt16() => (ushort)((ushort)(0U | m_buf.ReadByte()) | (uint)(ushort)((uint)m_buf.ReadByte() << 8));

		public int ReadInt32()
		{
			var num = 0U;
			num |= m_buf.ReadByte();
			num |= (uint)m_buf.ReadByte() << 8;
			num |= (uint)m_buf.ReadByte() << 16;
			return (int)(num | ((uint)m_buf.ReadByte() << 24));
		}

		public uint ReadUInt32()
		{
			var num = 0U;
			num |= m_buf.ReadByte();
			num |= (uint)m_buf.ReadByte() << 8;
			num |= (uint)m_buf.ReadByte() << 16;
			return num | ((uint)m_buf.ReadByte() << 24);
		}

		public long ReadInt64()
		{
			var num = 0UL;
			var num2 = (ulong)m_buf.ReadByte();
			num |= num2;
			num2 = (ulong)m_buf.ReadByte() << 8;
			num |= num2;
			num2 = (ulong)m_buf.ReadByte() << 16;
			num |= num2;
			num2 = (ulong)m_buf.ReadByte() << 24;
			num |= num2;
			num2 = (ulong)m_buf.ReadByte() << 32;
			num |= num2;
			num2 = (ulong)m_buf.ReadByte() << 40;
			num |= num2;
			num2 = (ulong)m_buf.ReadByte() << 48;
			num |= num2;
			num2 = (ulong)m_buf.ReadByte() << 56;
			return (long)(num | num2);
		}

		public ulong ReadUInt64()
		{
			var num = 0UL;
			var num2 = (ulong)m_buf.ReadByte();
			num |= num2;
			num2 = (ulong)m_buf.ReadByte() << 8;
			num |= num2;
			num2 = (ulong)m_buf.ReadByte() << 16;
			num |= num2;
			num2 = (ulong)m_buf.ReadByte() << 24;
			num |= num2;
			num2 = (ulong)m_buf.ReadByte() << 32;
			num |= num2;
			num2 = (ulong)m_buf.ReadByte() << 40;
			num |= num2;
			num2 = (ulong)m_buf.ReadByte() << 48;
			num |= num2;
			num2 = (ulong)m_buf.ReadByte() << 56;
			return num | num2;
		}

		public decimal ReadDecimal() =>
			new(new[]
			{
				ReadInt32(),
				ReadInt32(),
				ReadInt32(),
				ReadInt32()
			});

		public float ReadSingle()
		{
			var bytes = ReadBytes(4);
			return BitConverter.ToSingle(bytes, 0);
		}

		public double ReadDouble()
		{
			var bytes = ReadBytes(8);
			return BitConverter.ToSingle(bytes, 0);
		}

		public string ReadString()
		{
			var num = ReadUInt16();
			string result;
			if (num == 0)
			{
				result = "";
			}
			else
			{
				if (num >= 32768)
				{
					throw new IndexOutOfRangeException($"ReadString() too long: {num}");
				}

				while (num > s_StringReaderBuffer.Length)
				{
					s_StringReaderBuffer = new byte[s_StringReaderBuffer.Length * 2];
				}

				m_buf.ReadBytes(s_StringReaderBuffer, num);
				var chars = s_Encoding.GetChars(s_StringReaderBuffer, 0, num);
				result = new string(chars);
			}

			return result;
		}

		public char ReadChar() => (char)m_buf.ReadByte();

		public bool ReadBoolean()
		{
			var num = (int)m_buf.ReadByte();
			return num == 1;
		}

		public byte[] ReadBytes(int count)
		{
			if (count < 0)
			{
				throw new IndexOutOfRangeException($"NetworkReader ReadBytes {count}");
			}

			var array = new byte[count];
			m_buf.ReadBytes(array, (uint)count);
			return array;
		}

		public byte[] ReadBytesAndSize()
		{
			var num = ReadUInt16();
			return num == 0 ? new byte[0] : ReadBytes(num);
		}

		public Vector2 ReadVector2() => new(ReadSingle(), ReadSingle());

		public Vector3 ReadVector3() => new(ReadSingle(), ReadSingle(), ReadSingle());

		public Vector4 ReadVector4() => new(ReadSingle(), ReadSingle(), ReadSingle(), ReadSingle());

		public Color ReadColor() => new(ReadSingle(), ReadSingle(), ReadSingle(), ReadSingle());

		public Color32 ReadColor32() => new(ReadByte(), ReadByte(), ReadByte(), ReadByte());

		public Quaternion ReadQuaternion() => new(ReadSingle(), ReadSingle(), ReadSingle(), ReadSingle());

		public Rect ReadRect() => new(ReadSingle(), ReadSingle(), ReadSingle(), ReadSingle());

		public Plane ReadPlane() => new(ReadVector3(), ReadSingle());

		public Ray ReadRay() => new(ReadVector3(), ReadVector3());

		public Matrix4x4 ReadMatrix4x4() => new()
		{
			m00 = ReadSingle(),
			m01 = ReadSingle(),
			m02 = ReadSingle(),
			m03 = ReadSingle(),
			m10 = ReadSingle(),
			m11 = ReadSingle(),
			m12 = ReadSingle(),
			m13 = ReadSingle(),
			m20 = ReadSingle(),
			m21 = ReadSingle(),
			m22 = ReadSingle(),
			m23 = ReadSingle(),
			m30 = ReadSingle(),
			m31 = ReadSingle(),
			m32 = ReadSingle(),
			m33 = ReadSingle()
		};

		public NetworkHash128 ReadNetworkHash128()
		{
			NetworkHash128 result;
			result.i0 = ReadByte();
			result.i1 = ReadByte();
			result.i2 = ReadByte();
			result.i3 = ReadByte();
			result.i4 = ReadByte();
			result.i5 = ReadByte();
			result.i6 = ReadByte();
			result.i7 = ReadByte();
			result.i8 = ReadByte();
			result.i9 = ReadByte();
			result.i10 = ReadByte();
			result.i11 = ReadByte();
			result.i12 = ReadByte();
			result.i13 = ReadByte();
			result.i14 = ReadByte();
			result.i15 = ReadByte();
			return result;
		}

		public Transform ReadTransform()
		{
			var networkInstanceId = ReadNetworkId();
			Transform result;
			if (networkInstanceId.IsEmpty())
			{
				result = null;
			}
			else
			{
				var gameObject = QClientScene.FindLocalObject(networkInstanceId);
				if (gameObject == null)
				{
					Debug.Log($"ReadTransform netId:{networkInstanceId}");
					result = null;
				}
				else
				{
					result = gameObject.transform;
				}
			}

			return result;
		}

		public GameObject ReadGameObject()
		{
			var networkInstanceId = ReadNetworkId();
			GameObject result;
			if (networkInstanceId.IsEmpty())
			{
				result = null;
			}
			else
			{
				var gameObject = QNetworkServer.active
					? QNetworkServer.FindLocalObject(networkInstanceId)
					: QClientScene.FindLocalObject(networkInstanceId);
				if (gameObject == null)
				{
					Debug.Log($"ReadGameObject netId:{networkInstanceId}go: null");
				}

				result = gameObject;
			}

			return result;
		}

		public QNetworkIdentity ReadNetworkIdentity()
		{
			var networkInstanceId = ReadNetworkId();
			QNetworkIdentity result;
			if (networkInstanceId.IsEmpty())
			{
				result = null;
			}
			else
			{
				var gameObject = QNetworkServer.active
					? QNetworkServer.FindLocalObject(networkInstanceId)
					: QClientScene.FindLocalObject(networkInstanceId);
				if (gameObject == null)
				{
					Debug.Log($"ReadNetworkIdentity netId:{networkInstanceId}go: null");
					result = null;
				}
				else
				{
					result = gameObject.GetComponent<QNetworkIdentity>();
				}
			}

			return result;
		}

		public override string ToString() => m_buf.ToString();

		public TMsg ReadMessage<TMsg>() where TMsg : QMessageBase, new()
		{
			var result = Activator.CreateInstance<TMsg>();
			result.Deserialize(this);
			return result;
		}
	}
}