context: using System;
  using System.Collections.Generic;
  using System.Linq;
  using UnityEngine;
  using Mirror;
  using Systems.Interaction;
  using UI;
  
  
  namespace Messages.Server
  {
  	public class TabUpdateMessage : ServerMessage<TabUpdateMessage.NetMessage>
  	{
  		public struct NetMessage : NetworkMessage
  		{
  			public uint Provider;
  			public NetTabType Type;
  			public TabAction Action;
  
  			public ElementValue[] ElementValues;
  
  			public bool Touched;
  
  			public TabMessageType ID;
  			public int UniqueID;
  			public int NumOfMessages;
  
  			public override string ToString()
  			{
  				return $"[TabUpdateMessage {nameof(Provider)}: {Provider}, {nameof(Type)}: {Type}, " +
  				       $"{nameof(Action)}: {Action}, " +
  				       $"{nameof(ElementValue)}: {string.Join("; ", ElementValues ?? NoValues)}]";
  			}
  		}
  
  		private static readonly ElementValue[] NoValues = new ElementValue[0];
  
  		private static Dictionary<int, Tuple<ElementValue[], int>> ElementValuesCache = new Dictionary<int, Tuple<ElementValue[], int>>();
  
  		private static int Counter = 0;
  
  		public override void Process(NetMessage msg)
  		{
  			Logger.LogTraceFormat("Processed {0}", Category.NetUI, this);
  			LoadNetworkObject(msg.Provider);
  
  			// If start or middle of message add to cache then stop
  			if (msg.ID == TabMessageType.MoreIncoming)
  			{
  				// If Unique Id doesnt exist make new entry
  				if (ElementValuesCache.Count == 0 || !ElementValuesCache.ContainsKey(msg.UniqueID))
  				{
  					ElementValuesCache.Add(msg.UniqueID, new Tuple<ElementValue[], int>(msg.ElementValues, 1));
  					return;
  				}
  
  				// Sanity check to make sure this isnt the last message
  				if (msg.NumOfMessages == ElementValuesCache[msg.UniqueID].Item2 + 1)
  				{
  					Logger.LogError("This message didnt arrive in time before the end message!", Category.NetUI);
  					ElementValuesCache.Remove(msg.UniqueID);
  					return;
  				}
  
  				// Unique Id already exists so add arrays to each other
  				ElementValuesCache[msg.UniqueID] = new Tuple<ElementValue[], int>(Concat(ElementValuesCache[msg.UniqueID].Item1, msg.ElementValues), ElementValuesCache[msg.UniqueID].Item2 + 1);
  				return;
  			}
  
  			// If end of message add and continue
  			if(msg.ID == TabMessageType.EndOfMessage)
  			{
  				// Add the arrays together
  				ElementValuesCache[msg.UniqueID] = new Tuple<ElementValue[], int>(Concat(ElementValuesCache[msg.UniqueID].Item1, msg.ElementValues), ElementValuesCache[msg.UniqueID].Item2 + 1);
  
  				// Check to make sure its the last message
  				if (msg.NumOfMessages != ElementValuesCache[msg.UniqueID].Item2)
  				{
  					Logger.LogError("Not all the messages arrived in time for the NetUI update.", Category.NetUI);
  					return;
  				}
  
  				msg.ElementValues = ElementValuesCache[msg.UniqueID].Item1;
  				ElementValuesCache.Remove(msg.UniqueID);
  			}
  
  
  			switch (msg.Action)
  			{
  				case TabAction.Open:
  					ControlTabs.ShowTab(msg.Type, NetworkObject, msg.ElementValues);
  					break;
  				case TabAction.Close:
  					ControlTabs.CloseTab(msg.Type, NetworkObject);
  					break;
  				case TabAction.Update:
  					ControlTabs.UpdateTab(msg.Type, NetworkObject, msg.ElementValues, msg.Touched);
  					break;
  			}
  		}
  
  		public static void SendToPeepers(GameObject provider, NetTabType type, TabAction tabAction, ElementValue[] values = null)
  		{
  			// Notify all peeping players of the change
  			var list = NetworkTabManager.Instance.GetPeepers(provider, type);
  			foreach (var connectedPlayer in list)
  			{
  				Send(connectedPlayer.GameObject, provider, type, tabAction, null, values);
  			}
  		}
  
  		public static TabUpdateMessage Send(GameObject recipient, GameObject provider, NetTabType type, TabAction tabAction,
  			GameObject changedBy = null,
  			ElementValue[] values = null)
  		{
  
  			switch (tabAction)
  			{
  				case TabAction.Open:
  					NetworkTabManager.Instance.Add(provider, type, recipient);
  					var instance = NetworkTabManager.Instance.Get(provider, type);
  					if (instance == null)
  					{
  						Logger.LogError($"Couldn't find NetTab to send for {provider.OrNull()?.ExpensiveName()} " +
  								$"Does the tab prefab match the type '{type}'? Make sure that 'Tab{type}' is listed inside the NetTabs SO.");
  						return default;
  					}
  					values = instance.ElementValues;
  					break;
  				case TabAction.Close:
  					NetworkTabManager.Instance.Remove(provider, type, recipient);
  					break;
  				case TabAction.Update:
  					// TODO: FIXME: duplication of NetTab.ValidatePeepers
  					// Not sending updates and closing tab for players that don't pass the validation anymore
  					var validate = Validations.CanApply(recipient.GetComponent<PlayerScript>(), provider, NetworkSide.Server);
  
  					if (recipient.GetComponent<PlayerScript>().OrNull()?.PlayerState == PlayerScript.PlayerStates.Ai)
  					{
  						validate = Validations.CanApply(new AiActivate(recipient, null,
  							provider, Intent.Help, AiActivate.ClickTypes.NormalClick), NetworkSide.Server);
  					}
  
  					if (!validate)
  					{
  						Send(recipient, provider, type, TabAction.Close);
  						return null;
  					}
  					break;
  			}
  
  			// SingleMessage, MoreIncoming, EndOfMessage
  			var id = TabMessageType.SingleMessage;
  
  			Counter++;
  			var uniqueID = Counter;
  
  			if (Counter > 10000)
  			{
  				Counter = 0;
  			}
  
  			var elementValuesLists = new Dictionary<List<ElementValue>, TabMessageType>();
  
  			if (values != null && tabAction != TabAction.Close)
  			{
  				// get max possible packet size from current transform
  				var maxPacketSize = Transport.activeTransport.GetMaxPacketSize(0);
  
  				// set currentSize start value to max TCP header size (60b)
  				var currentSize = 100;
  
  				// Stores the current cycle of ElementValues
  				var elementValues = new List<ElementValue>();
  
  				// How many values are being sent
  				var length = values.Length;
  
  				// Total packet size if all values sent together
  				var totalSize = 0;
  
  				// Work out totalSize
  				foreach (var value in values)
  				{
  					var size = value.GetSize();
  
  					// If a single value is bigger than max packet size cannot proceed
  					if (size + 60 >= maxPacketSize)
  					{
  						Logger.LogError($"This value is above the max mirror packet limit, and cannot be split. Is {size + 60} bytes", Category.NetUI);
  						return null;
  					}
  
  					totalSize += size;
  				}
  
  				// Rounds up to the max number of divisions of the max packet size will be needed for values
  				var divisions = (int)Math.Ceiling((float)totalSize / maxPacketSize);
  
  				// Counter for which division is currently being made
  				var currentDivision = 0;
  
  				// The loop for making the messages from the values
  				for (var i = 0; i < length; i++)
  				{
  					// Keep adding values until bigger than packet size
  					currentSize += values[i].GetSize();
  
  					if (currentSize > maxPacketSize)
  					{
  						currentDivision ++;
  						currentSize = 100;
  
  						// Id MoreIncoming, means it is a multimessage but not the end.
  						id = TabMessageType.MoreIncoming;
  
  						// If last division then this will be the end, set to end Id of EndOfMessage
  						if (currentDivision == divisions)
  						{
  							id = TabMessageType.EndOfMessage;
  						}
  
  						// Add value list to the message list
  						elementValuesLists.Add(elementValues, id);
  						elementValues = new List<ElementValue>();
  					}
  
  					elementValues.Add(values[i]);
  				}
  
  				// Single message
  				if (elementValuesLists.Count == 0)
  				{
  
gt: 					values = elementValues.ToArray();