﻿using FishNet.Connection;
using FishNet.Managing;
using FishNet.Managing.Logging;
using FishNet.Managing.Server;
using FishNet.Object;
using FishNet.Observing;
using FishNet.Utility.Extension;
using FishNet.Utility.Performance;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using UnityEngine;

namespace FishNet.Component.Observing
{
    /// <summary>
    /// When this observer condition is placed on an object, a client must be within the same match to view the object.
    /// </summary>
    [CreateAssetMenu(menuName = "FishNet/Observers/Match Condition", fileName = "New Match Condition")]
    public class MatchCondition : ObserverCondition
    {
        #region Types.
        /// <summary>
        /// MatchCondition collections used.
        /// </summary>
        public class ConditionCollections
        {
            public Dictionary<int, HashSet<NetworkConnection>> MatchConnections = new Dictionary<int, HashSet<NetworkConnection>>();
            public Dictionary<NetworkConnection, HashSet<int>> ConnectionMatches = new Dictionary<NetworkConnection, HashSet<int>>();
            public Dictionary<int, HashSet<NetworkObject>> MatchObjects = new Dictionary<int, HashSet<NetworkObject>>();
            public Dictionary<NetworkObject, HashSet<int>> ObjectMatches = new Dictionary<NetworkObject, HashSet<int>>();
        }
        #endregion

        #region Private.
        [Obsolete("Use GetMatchConnections(NetworkManager).")] //Remove on 2023/06/01
        public static Dictionary<int, HashSet<NetworkConnection>> MatchConnections => GetMatchConnections();
        [Obsolete("Use GetConnectionMatches(NetworkManager).")] //Remove on 2024/01/01.
        public static Dictionary<NetworkConnection, HashSet<int>> ConnectionMatch => GetConnectionMatches();
        [Obsolete("Use GetMatchObjects(NetworkManager).")] //Remove on 2024/01/01.
        public static Dictionary<int, HashSet<NetworkObject>> MatchObject => GetMatchObjects();
        [Obsolete("Use GetObjectMatches(NetworkManager).")] //Remove on 2024/01/01.
        public static Dictionary<NetworkObject, HashSet<int>> ObjectMatch => GetObjectMatches();
        /// <summary>
        /// Collections for each NetworkManager instance.
        /// </summary>
        private static Dictionary<NetworkManager, ConditionCollections> _collections = new Dictionary<NetworkManager, ConditionCollections>();
        #endregion

        #region Collections.
        /// <summary>
        /// Stores collections for a manager.
        /// </summary>
        /// <param name="manager"></param>
        internal static void StoreCollections(NetworkManager manager)
        {
            ConditionCollections cc;
            if (!_collections.TryGetValue(manager, out cc))
                return;

            foreach (HashSet<int> item in cc.ObjectMatches.Values)
                HashSets.StoreCache(item);
            foreach (HashSet<NetworkConnection> item in cc.MatchConnections.Values)
                HashSets.StoreCache(item);
            foreach (HashSet<NetworkObject> item in cc.MatchObjects.Values)
                HashSets.StoreCache(item);
            foreach (HashSet<int> item in cc.ConnectionMatches.Values)
                HashSets.StoreCache(item);

            _collections.Remove(manager);
        }
        /// <summary>
        /// Gets condition collections for a NetowrkManager.
        /// </summary>
        private static ConditionCollections GetCollections(NetworkManager manager = null)
        {
            if (manager == null)
                manager = InstanceFinder.NetworkManager;

            ConditionCollections cc;
            if (!_collections.TryGetValue(manager, out cc))
            {
                cc = new ConditionCollections();
                _collections[manager] = cc;
            }

            return cc;
        }

        /// <summary>
        /// Returns matches and connections in each match.
        /// </summary>
        /// <param name="manager">NetworkManager to use.</param>
        /// <returns></returns>
        public static Dictionary<int, HashSet<NetworkConnection>> GetMatchConnections(NetworkManager manager = null)
        {
            ConditionCollections cc = GetCollections(manager);
            return cc.MatchConnections;
        }
        /// <summary>
        /// Returns connections and the matches they are in.
        /// </summary>
        /// <param name="manager">NetworkManager to use.</param>
        /// <returns></returns>
        public static Dictionary<NetworkConnection, HashSet<int>> GetConnectionMatches(NetworkManager manager = null)
        {
            ConditionCollections cc = GetCollections(manager);
            return cc.ConnectionMatches;
        }
        /// <summary>
        /// Returns matches and objects within each match.
        /// </summary>
        /// <param name="manager">NetworkManager to use.</param>
        /// <returns></returns>
        public static Dictionary<int, HashSet<NetworkObject>> GetMatchObjects(NetworkManager manager = null)
        {
            ConditionCollections cc = GetCollections(manager);
            return cc.MatchObjects;
        }
        /// <summary>
        /// Returns objects and the matches they are in.
        /// </summary>
        /// <param name="manager">NetworkManager to use.</param>
        /// <returns></returns>
        public static Dictionary<NetworkObject, HashSet<int>> GetObjectMatches(NetworkManager manager = null)
        {
            ConditionCollections cc = GetCollections(manager);
            return cc.ObjectMatches;
        }
        #endregion

        public void ConditionConstructor() { }

        #region Add to match NetworkConnection.
        /// <summary>
        /// Adds a connection to a match.
        /// </summary>
        private static bool AddToMatch(int match, NetworkConnection conn, NetworkManager manager, bool replaceMatch, bool rebuild)
        {
            Dictionary<int, HashSet<NetworkConnection>> matchConnections = GetMatchConnections(manager);

            if (replaceMatch)
                RemoveFromMatchesWithoutRebuild(conn, manager);

            /* Get current connections in match. This is where the conn
             * will be added to. If does not exist then make new
             * collection. */
            HashSet<NetworkConnection> matchConnValues;
            if (!matchConnections.TryGetValueIL2CPP(match, out matchConnValues))
            {
                matchConnValues = HashSets.RetrieveNetworkConnectionCache();
                matchConnections.Add(match, matchConnValues);
            }

            bool r = matchConnValues.Add(conn);
            AddToConnectionMatches(conn, match, manager);
            if (r && rebuild)
                GetServerObjects(manager).RebuildObservers();

            return r;
        }


        /// <summary>
        /// Adds a connection to a match.
        /// </summary>
        /// <param name="match">Match to add conn to.</param>
        /// <param name="conn">Connection to add to match.</param>
        /// <param name="manager">NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used.</param>
        /// <param name="replaceMatch">True to replace other matches with the new match.</param>
        public static void AddToMatch(int match, NetworkConnection conn, NetworkManager manager = null, bool replaceMatch = false)
        {
            AddToMatch(match, conn, manager, replaceMatch, true);
        }

        /// <summary>
        /// Updates a connection within ConnectionMatches to contain match.
        /// </summary>
        private static void AddToConnectionMatches(NetworkConnection conn, int match, NetworkManager manager)
        {
            Dictionary<NetworkConnection, HashSet<int>> connectionMatches = GetConnectionMatches(manager);

            HashSet<int> matches;
            if (!connectionMatches.TryGetValueIL2CPP(conn, out matches))
            {
                matches = HashSets.RetrieveIntCache();
                connectionMatches[conn] = matches;
            }

            matches.Add(match);
        }

        /// <summary>
        /// Adds connections to a match.
        /// </summary>
        /// <param name="match">Match to add conns to.</param>
        /// <param name="conns">Connections to add to match.</param>
        /// <param name="manager">NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used.</param>
        /// <param name="replaceMatch">True to replace other matches with the new match.</param>
        public static void AddToMatch(int match, NetworkConnection[] conns, NetworkManager manager = null, bool replaceMatch = false)
        {
            AddToMatch(match, conns.ToList(), manager, replaceMatch);
        }
        /// <summary>
        /// Adds connections to a match.
        /// </summary>
        /// <param name="match">Match to add conns to.</param>
        /// <param name="conns">Connections to add to match.</param>
        /// <param name="manager">NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used.</param>
        /// <param name="replaceMatch">True to replace other matches with the new match.</param>
        public static void AddToMatch(int match, List<NetworkConnection> conns, NetworkManager manager = null, bool replaceMatch = false)
        {
            bool added = false;
            foreach (NetworkConnection c in conns)
                added |= AddToMatch(match, c, manager, replaceMatch, false);

            if (added)
                GetServerObjects(manager).RebuildObservers();
        }
        #endregion

        #region Add to match NetworkObject.
        /// <summary>
        /// Adds an object to a match.
        /// </summary>
        private static bool AddToMatch(int match, NetworkObject nob, NetworkManager manager, bool replaceMatch, bool rebuild)
        {
            Dictionary<int, HashSet<NetworkObject>> matchObjects = GetMatchObjects(manager);
            Dictionary<NetworkObject, HashSet<int>> objectMatches = GetObjectMatches(manager);

            if (replaceMatch)
                RemoveFromMatchWithoutRebuild(nob, manager);

            HashSet<NetworkObject> matchObjectsValues;
            if (!matchObjects.TryGetValueIL2CPP(match, out matchObjectsValues))
            {
                matchObjectsValues = HashSets.RetrieveNetworkObjectCache();
                matchObjects.Add(match, matchObjectsValues);
            }
            bool added = matchObjectsValues.Add(nob);

            /* Also add to reverse dictionary. */
            HashSet<int> objectMatchesValues;
            if (!objectMatches.TryGetValueIL2CPP(nob, out objectMatchesValues))
            {
                objectMatchesValues = HashSets.RetrieveIntCache();
                objectMatches.Add(nob, objectMatchesValues);
            }
            objectMatchesValues.Add(match);

            if (added && rebuild)
                GetServerObjects(manager).RebuildObservers();

            return added;
        }
        /// <summary>
        /// Adds an object to a match.
        /// </summary>
        /// <param name="match">Match to add conn to.</param>
        /// <param name="nob">Connection to add to match.</param>
        /// <param name="manager">NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used.</param>
        /// <param name="replaceMatch">True to replace other matches with the new match.</param>
        public static void AddToMatch(int match, NetworkObject nob, NetworkManager manager = null, bool replaceMatch = false)
        {
            AddToMatch(match, nob, manager, replaceMatch, true);
        }
        /// <summary>
        /// Adds objects to a match.
        /// </summary>
        /// <param name="match">Match to add conns to.</param>
        /// <param name="nobs">Connections to add to match.</param>
        /// <param name="manager">NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used.</param>
        /// <param name="replaceMatch">True to replace other matches with the new match.</param>
        public static void AddToMatch(int match, NetworkObject[] nobs, NetworkManager manager = null, bool replaceMatch = false)
        {
            AddToMatch(match, nobs.ToList(), manager, replaceMatch);
        }
        /// <summary>
        /// Adds objects to a match.
        /// </summary>
        /// <param name="match">Match to add conns to.</param>
        /// <param name="nobs">Connections to add to match.</param>
        /// <param name="manager">NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used.</param>
        /// <param name="replaceMatch">True to replace other matches with the new match.</param>
        public static void AddToMatch(int match, List<NetworkObject> nobs, NetworkManager manager = null, bool replaceMatch = false)
        {
            //Remove from current matches.
            if (replaceMatch)
            {
                foreach (NetworkObject n in nobs)
                    RemoveFromMatchWithoutRebuild(n, manager);
            }

            bool added = false;
            //Add to matches.
            foreach (NetworkObject n in nobs)
                added |= AddToMatch(match, n, manager, replaceMatch, false);

            if (added)
                GetServerObjects(manager).RebuildObservers();
        }
        #endregion

        #region TryRemoveKey.
        /// <summary>
        /// Removes a key if values are empty, and caches values.
        /// </summary>
        private static void TryRemoveKey(Dictionary<int, HashSet<NetworkObject>> dict, int key, HashSet<NetworkObject> value)
        {
            bool isEmpty = true;
            if (value != null)
            {
                isEmpty = (value.Count == 0);
                if (isEmpty)
                    HashSets.StoreCache(value);
            }

            if (isEmpty)
                dict.Remove(key);
        }
        /// <summary>
        /// Removes a key if values are empty, and caches values.
        /// </summary>
        private static void TryRemoveKey(Dictionary<int, HashSet<NetworkObject>> dict, int key)
        {
            HashSet<NetworkObject> value;
            dict.TryGetValue(key, out value);
            TryRemoveKey(dict, key, value);
        }

        /// <summary>
        /// Removes a key if values are empty, and caches values.
        /// </summary>
        private static void TryRemoveKey(Dictionary<NetworkObject, HashSet<int>> dict, NetworkObject key, HashSet<int> value)
        {
            bool isEmpty = true;
            if (value != null)
            {
                isEmpty = (value.Count == 0);
                if (isEmpty)
                    HashSets.StoreCache(value);
            }

            if (isEmpty)
                dict.Remove(key);
        }
        /// <summary>
        /// Removes a key if values are empty, and caches values.
        /// </summary>
        private static void TryRemoveKey(Dictionary<NetworkObject, HashSet<int>> dict, NetworkObject key)
        {
            HashSet<int> value;
            dict.TryGetValueIL2CPP(key, out value);
            TryRemoveKey(dict, key, value);
        }

        /// <summary>
        /// Removes a key if values are empty, and caches values.
        /// </summary>
        private static void TryRemoveKey(Dictionary<int, HashSet<NetworkConnection>> dict, int key, HashSet<NetworkConnection> value)
        {
            bool isEmpty = true;
            if (value != null)
            {
                isEmpty = (value.Count == 0);
                if (isEmpty)
                    HashSets.StoreCache(value);
            }

            if (isEmpty)
                dict.Remove(key);
        }
        /// <summary>
        /// Removes a key if values are empty, and caches values.
        /// </summary>
        private static void TryRemoveKey(Dictionary<int, HashSet<NetworkConnection>> dict, int key)
        {
            HashSet<NetworkConnection> value;
            dict.TryGetValueIL2CPP(key, out value);
            TryRemoveKey(dict, key, value);
        }

        /// <summary>
        /// Removes a key if values are empty, and caches values.
        /// </summary>
        private static void TryRemoveKey(Dictionary<NetworkConnection, HashSet<int>> dict, NetworkConnection key, HashSet<int> value)
        {
            bool isEmpty = true;
            if (value != null)
            {
                isEmpty = (value.Count == 0);
                if (isEmpty)
                    HashSets.StoreCache(value);
            }

            if (isEmpty)
                dict.Remove(key);
        }
        /// <summary>
        /// Removes a key and caches collections where needed.
        /// </summary>
        private static void TryRemoveKey(Dictionary<NetworkConnection, HashSet<int>> dict, NetworkConnection key)
        {
            HashSet<int> value;
            dict.TryGetValueIL2CPP(key, out value);
            TryRemoveKey(dict, key, value);
        }
        #endregion

        #region Remove from match NetworkConnection.
        /// <summary>
        /// Removes a connection from all matches without rebuilding observers.
        /// </summary>
        /// <param name="conn">Connection to remove from matches.</param>
        /// <param name="manager">NetworkManager connection belongs to. This is not currently used.</param>
        internal static bool RemoveFromMatchesWithoutRebuild(NetworkConnection conn, NetworkManager manager)
        {
            Dictionary<NetworkConnection, HashSet<int>> connectionMatches = GetConnectionMatches(manager);
            Dictionary<int, HashSet<NetworkConnection>> matchConnections = GetMatchConnections(manager);

            bool removed = false;
            //If found to be in a match.
            if (connectionMatches.TryGetValueIL2CPP(conn, out HashSet<int> connectionMatchesValues))
            {
                removed = (connectionMatchesValues.Count > 0);
                foreach (int m in connectionMatchesValues)
                {
                    HashSet<NetworkConnection> matchConnsValues;
                    //If match is found.
                    if (matchConnections.TryGetValue(m, out matchConnsValues))
                    {
                        matchConnsValues.Remove(conn);
                        TryRemoveKey(matchConnections, m, matchConnsValues);
                    }
                }

                //Clear matches connection is in.
                connectionMatchesValues.Clear();
                //Remove from connectionMatches.
                TryRemoveKey(connectionMatches, conn, connectionMatchesValues);
            }

            return removed;
        }

        /// <summary>
        /// Removes a connection from all matches.
        /// </summary>
        /// <param name="conn">NetworkConnection to remove.</param>
        /// <param name="manager">NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used.</param>
        public static void RemoveFromMatch(NetworkConnection conn, NetworkManager manager)
        {
            bool removed = RemoveFromMatchesWithoutRebuild(conn, manager);
            if (removed)
                GetServerObjects(manager).RebuildObservers();
        }
        /// <summary>
        /// Removes a connection from a match.
        /// </summary>
        private static bool RemoveFromMatch(int match, NetworkConnection conn, NetworkManager manager, bool rebuild)
        {
            Dictionary<NetworkConnection, HashSet<int>> connectionMatches = GetConnectionMatches(manager);
            Dictionary<int, HashSet<NetworkConnection>> matchConnections = GetMatchConnections(manager);

            bool removed = false;
            HashSet<NetworkConnection> matchConnsValues;
            if (matchConnections.TryGetValueIL2CPP(match, out matchConnsValues))
            {
                removed |= matchConnsValues.Remove(conn);
                HashSet<int> connectionMatchesValues;
                if (connectionMatches.TryGetValueIL2CPP(conn, out connectionMatchesValues))
                {
                    connectionMatchesValues.Remove(match);
                    TryRemoveKey(connectionMatches, conn, connectionMatchesValues);
                }
                if (removed && rebuild)
                {
                    TryRemoveKey(matchConnections, match, matchConnsValues);
                    GetServerObjects(manager).RebuildObservers();
                }
            }

            return removed;
        }
        /// <summary>
        /// Removes a connection from a match.
        /// </summary>
        /// <param name="match">Match to remove conn from.</param>
        /// <param name="conn">Connection to remove from match.</param>
        /// <param name="manager">NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used.</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static bool RemoveFromMatch(int match, NetworkConnection conn, NetworkManager manager = null)
        {
            return RemoveFromMatch(match, conn, manager, true);
        }
        /// <summary>
        /// Removes connections from a match.
        /// </summary>
        /// <param name="match">Match to remove conns from.</param>
        /// <param name="conns">Connections to remove from match.</param>
        /// <param name="manager">NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used.</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void RemoveFromMatch(int match, NetworkConnection[] conns, NetworkManager manager)
        {
            RemoveFromMatch(match, conns.ToList(), manager);
        }
        /// <summary>
        /// Removes connections from a match.
        /// </summary>
        /// <param name="match">Match to remove conns from.</param>
        /// <param name="conns">Connections to remove from match.</param>
        /// <param name="manager">NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used.</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void RemoveFromMatch(int match, List<NetworkConnection> conns, NetworkManager manager)
        {
            bool removed = false;
            foreach (NetworkConnection c in conns)
                removed |= RemoveFromMatch(match, c, manager, false);

            if (removed)
                GetServerObjects(manager).RebuildObservers();
        }
        #endregion

        #region Remove from match NetworkObject.
        /// <summary>
        /// Removes a network object from any match without rebuilding observers.
        /// </summary>
        /// <param name="nob">NetworkObject to remove.</param>
        /// <param name="manager">Manager which the network object belongs to. This value is not yet used.</param>
        internal static bool RemoveFromMatchWithoutRebuild(NetworkObject nob, NetworkManager manager)
        {
            Dictionary<NetworkObject, HashSet<int>> objectMatches = GetObjectMatches(manager);
            Dictionary<int, HashSet<NetworkObject>> matchObjects = GetMatchObjects(manager);

            HashSet<int> objectMatchesValues;
            bool removed = false;
            //If found to be in a match.
            if (objectMatches.TryGetValueIL2CPP(nob, out objectMatchesValues))
            {
                removed = (objectMatchesValues.Count > 0);
                foreach (int m in objectMatchesValues)
                {
                    //If match is found.
                    if (matchObjects.TryGetValue(m, out HashSet<NetworkObject> matchObjectsValues))
                    {
                        matchObjectsValues.Remove(nob);
                        TryRemoveKey(matchObjects, m, matchObjectsValues);
                    }
                }

                //Since object is being removed from all matches this can be cleared.
                objectMatchesValues.Clear();
                TryRemoveKey(objectMatches, nob, objectMatchesValues);
            }

            return removed;
        }
        /// <summary>
        /// Removes nob from all matches.
        /// </summary>
        /// <param name="nob">NetworkObject to remove.</param>
        /// <param name="manager">NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used.</param>
        public static bool RemoveFromMatch(NetworkObject nob, NetworkManager manager = null)
        {
            bool removed = RemoveFromMatchWithoutRebuild(nob, manager);
            if (removed)
                GetServerObjects(manager).RebuildObservers(nob);

            return removed;
        }
        /// <summary>
        /// Removes a network object from all matches.
        /// </summary>
        /// <param name="nobs">NetworkObjects to remove.</param>
        /// <param name="manager">NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used.</param>
        public static void RemoveFromMatch(NetworkObject[] nobs, NetworkManager manager = null)
        {
            RemoveFromMatch(nobs.ToList(), manager);
        }
        /// <summary>
        /// Removes network objects from all matches.
        /// </summary>
        /// <param name="nobs">NetworkObjects to remove.</param>
        /// <param name="manager">NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used.</param>
        public static void RemoveFromMatch(List<NetworkObject> nobs, NetworkManager manager = null)
        {
            bool removed = false;
            foreach (NetworkObject n in nobs)
                removed |= RemoveFromMatchWithoutRebuild(n, manager);

            if (removed)
                GetServerObjects(manager).RebuildObservers(nobs);
        }
        /// <summary>
        /// Removes a network object from a match.
        /// </summary>
        /// <param name="match">Match to remove conn from.</param>
        /// <param name="nob">NetworkObject to remove from match.</param>
        /// <param name="manager">NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used.</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void RemoveFromMatch(int match, NetworkObject nob, NetworkManager manager = null)
        {
            Dictionary<int, HashSet<NetworkObject>> matchObjects = GetMatchObjects(manager);
            Dictionary<NetworkObject, HashSet<int>> objectMatches = GetObjectMatches(manager);

            HashSet<NetworkObject> matchObjectsValues;
            if (matchObjects.TryGetValueIL2CPP(match, out matchObjectsValues))
            {
                bool removed = matchObjectsValues.Remove(nob);

                if (removed)
                {
                    /* Check if nob is still in matches. If not then remove
                     * nob from ObjectMatches. */
                    HashSet<int> objectMatchesValues;
                    if (objectMatches.TryGetValueIL2CPP(nob, out objectMatchesValues))
                    {
                        objectMatchesValues.Remove(match);
                        TryRemoveKey(objectMatches, nob, objectMatchesValues);
                    }

                    TryRemoveKey(matchObjects, match, matchObjectsValues);
                    GetServerObjects(manager).RebuildObservers(nob);
                }
            }
        }
        /// <summary>
        /// Removes network objects from a match.
        /// </summary>
        /// <param name="match">Match to remove conns from.</param>
        /// <param name="nobs">NetworkObjects to remove from match.</param>
        /// <param name="manager">NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used.</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void RemoveFromMatch(int match, NetworkObject[] nobs, NetworkManager manager = null)
        {
            Dictionary<int, HashSet<NetworkObject>> matchObjects = GetMatchObjects(manager);
            Dictionary<NetworkObject, HashSet<int>> objectMatches = GetObjectMatches(manager);

            if (matchObjects.TryGetValueIL2CPP(match, out HashSet<NetworkObject> matchObjectsValues))
            {
                bool removed = false;
                for (int i = 0; i < nobs.Length; i++)
                {
                    NetworkObject n = nobs[i];
                    removed |= matchObjectsValues.Remove(n);
                    objectMatches.Remove(n);
                }

                if (removed)
                {
                    TryRemoveKey(matchObjects, match, matchObjectsValues);
                    GetServerObjects(manager).RebuildObservers(nobs);
                }
            }
        }
        /// <summary>
        /// Removes network objects from a match.
        /// </summary>
        /// <param name="match">Match to remove conns from.</param>
        /// <param name="nobs">NetworkObjects to remove from match.</param>
        /// <param name="manager">NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used.</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void RemoveFromMatch(int match, List<NetworkObject> nobs, NetworkManager manager = null)
        {
            Dictionary<int, HashSet<NetworkObject>> matchObjects = GetMatchObjects(manager);
            Dictionary<NetworkObject, HashSet<int>> objectMatches = GetObjectMatches(manager);

            if (matchObjects.TryGetValueIL2CPP(match, out HashSet<NetworkObject> matchObjectsValues))
            {
                bool removed = false;
                for (int i = 0; i < nobs.Count; i++)
                {
                    NetworkObject n = nobs[i];
                    removed |= matchObjectsValues.Remove(n);
                    objectMatches.Remove(n);
                }

                if (removed)
                {
                    TryRemoveKey(matchObjects, match, matchObjectsValues);
                    GetServerObjects(manager).RebuildObservers(nobs);
                }
            }
        }
        #endregion

        /// <summary>
        /// Returns if the object which this condition resides should be visible to connection.
        /// </summary>
        /// <param name="connection">Connection which the condition is being checked for.</param>
        /// <param name="currentlyAdded">True if the connection currently has visibility of this object.</param>
        /// <param name="notProcessed">True if the condition was not processed. This can be used to skip processing for performance. While output as true this condition result assumes the previous ConditionMet value.</param>
        public override bool ConditionMet(NetworkConnection connection, bool currentlyAdded, out bool notProcessed)
        {
            //If here then checks are being processed.
            notProcessed = false;
            NetworkConnection owner = base.NetworkObject.Owner;
            /* If object is owned then check if owner
            * and connection share a match. */
            if (owner.IsValid)
            {
                Dictionary<NetworkConnection, HashSet<int>> connectionMatches = GetConnectionMatches(base.NetworkObject.NetworkManager);
                //Output owner matches.
                HashSet<int> ownerMatches;
                //bool ownerMatchesFound = connectionMatches.TryGetValueIL2CPP(owner, out ownerMatches);
                ////Connection isn't in a match.
                //if (!connectionMatches.TryGetValueIL2CPP(connection, out HashSet<int> connMatches))
                //{
                //    //If owner is also not in a match then they can see each other.
                //    return !ownerMatchesFound;
                //}
                /* This objects owner is not in a match so treat it like
                 * a networkobject without an owner. Objects not in matches
                 * are visible to everyone. */
                if (!connectionMatches.TryGetValueIL2CPP(owner, out ownerMatches))
                {
                    return true;
                }
                /* Owner is in a match. See if connection is in any of
                 * the same matches. */
                else
                {
                    //If conn is not in any matches then they cannot see this object, as it is.
                    if (!connectionMatches.TryGetValue(connection, out HashSet<int> connMatches))
                    {
                        return false;
                    }
                    //See if conn is in any of the same matches.
                    else
                    {
                        foreach (int m in connMatches)
                        {
                            if (ownerMatches.Contains(m))
                                return true;
                        }
                    }

                    //Fall through, not found.
                    return false;
                }
            }
            /* If no owner see if the object is in a match and if so
             * then compare that. */
            else
            {
                Dictionary<NetworkObject, HashSet<int>> objectMatches = GetObjectMatches(base.NetworkObject.NetworkManager);
                Dictionary<NetworkConnection, HashSet<int>> connectionMatches = GetConnectionMatches(base.NetworkObject.NetworkManager);

                //Object isn't in a match. Is visible with no owner.
                HashSet<int> objectMatchesValues;
                if (!objectMatches.TryGetValueIL2CPP(base.NetworkObject, out objectMatchesValues))
                    return true;
                /* See if connection is in any of same matches as the object.
                 * If connection isn't in a match then it fails as at this point
                 * object would be, but not conn. */
                if (!connectionMatches.TryGetValueIL2CPP(connection, out HashSet<int> connectionMatchesValues))
                    return false;

                //Compare for same matches.
                foreach (int cM in connectionMatchesValues)
                {
                    if (objectMatchesValues.Contains(cM))
                        return true;
                }

                //Fall through, not in any of the matches.
                return false;
            }
        }


        /// <summary>
        /// Returns which ServerObjects to rebuild observers on.
        /// </summary>
        /// <param name="nm"></param>
        /// <returns></returns>
        private static ServerObjects GetServerObjects(NetworkManager manager)
        {
            return (manager == null) ? InstanceFinder.ServerManager.Objects : manager.ServerManager.Objects;
        }

        /// <summary>
        /// How a condition is handled.
        /// </summary>
        /// <returns></returns>
        public override ObserverConditionType GetConditionType() => ObserverConditionType.Normal;

        /// <summary>
        /// Clones referenced ObserverCondition. This must be populated with your conditions settings.
        /// </summary>
        /// <returns></returns>
        public override ObserverCondition Clone()
        {
            MatchCondition copy = ScriptableObject.CreateInstance<MatchCondition>();
            copy.ConditionConstructor();
            return copy;
        }
    }
}
