#if UNITY_EDITOR || DEVELOPMENT_BUILD
#define DEVELOPMENT
#endif
using FishNet.CodeGenerating;
using FishNet.Connection;
using FishNet.Documenting;
using FishNet.Managing;
using FishNet.Managing.Logging;
using FishNet.Managing.Predicting;
using FishNet.Managing.Server;
using FishNet.Managing.Timing;
using FishNet.Object.Prediction;
using FishNet.Object.Prediction.Delegating;
using FishNet.Serializing;
using FishNet.Serializing.Helping;
using FishNet.Transporting;
using FishNet.Utility;
using GameKit.Dependencies.Utilities;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using GameKit.Dependencies.Utilities.Types;
using UnityEngine;

[assembly: InternalsVisibleTo(UtilityConstants.CODEGEN_ASSEMBLY_NAME)]

namespace FishNet.Object
{
    #region Types.
    internal static class ReplicateTickFinder
    {
        public enum DataPlacementResult
        {
            /// <summary>
            /// Something went wrong; this should never be returned.
            /// </summary>
            Error,
            /// <summary>
            /// Tick was found on an index.
            /// </summary>
            Exact,
            /// <summary>
            /// Tick was not found because it is lower than any of the replicates.
            /// This is also used when there are no datas.
            /// </summary>
            InsertBeginning,
            /// <summary>
            /// Tick was not found but can be inserted in the middle of the collection.
            /// </summary>
            InsertMiddle,
            /// <summary>
            /// Tick was not found because it is larger than any of the replicates.
            /// </summary>
            InsertEnd,
        }

        /// <summary>
        /// Gets the index in replicates where the tick matches.
        /// </summary>
        public static int GetReplicateHistoryIndex<T>(uint tick, RingBuffer<ReplicateDataContainer<T>> replicatesHistory, out DataPlacementResult findResult) where T : IReplicateData, new()
        {
            int replicatesCount = replicatesHistory.Count;
            if (replicatesCount == 0)
            {
                findResult = DataPlacementResult.InsertBeginning;
                return 0;
            }

            uint firstTick = replicatesHistory[0].Data.GetTick();

            //Try to find by skipping ahead the difference between tick and start.
            int diff = (int)(tick - firstTick);
            /* If the difference is larger than replicatesCount
             * then that means the replicates collection is missing
             * entries. EG if replicates values were 4, 7, 10 and tick were
             * 10 the difference would be 6. While replicates does contain the value
             * there is no way it could be found by pulling index 'diff' since that
             * would be out of bounds. This should never happen under normal conditions, return
             * missing if it does. */
            //Do not need to check less than 0 since we know if here tick is larger than first entry.
            if (diff >= replicatesCount)
            {
                //Try to return value using brute force.
                int index = FindIndexBruteForce(out findResult);
                return index;
            }
            else if (diff < 0)
            {
                findResult = DataPlacementResult.InsertBeginning;
                return 0;
            }
            else
            {
                /* If replicatesHistory contained the ticks
                 * of 1 2 3 4 5, and the tick is 3, then the difference
                 * would be 2 (because 3 - 1 = 2). As we can see index
                 * 2 of replicatesHistory does indeed return the proper tick. */
                //Expected diff to be result but was not.
                if (replicatesHistory[diff].Data.GetTick() != tick)
                {
                    //Try to return value using brute force.
                    int index = FindIndexBruteForce(out findResult);
                    return index;
                }
                //Exact was found, this is the most ideal situation.
                else
                {
                    findResult = DataPlacementResult.Exact;
                    return diff;
                }
            }

            //Tries to find the index by brute forcing the collection.
            int FindIndexBruteForce(out DataPlacementResult result)
            {
                /* Some quick exits to save perf. */
                //If tick is lower than first then it must be inserted at the beginning.
                if (tick < firstTick)
                {
                    result = DataPlacementResult.InsertBeginning;
                    return 0;
                }
                //If tick is larger the last then it must be inserted at the end.
                else if (tick > replicatesHistory[replicatesCount - 1].Data.GetTick())
                {
                    result = DataPlacementResult.InsertEnd;
                    return replicatesCount;
                }
                else
                {
                    //Brute check.
                    for (int i = 0; i < replicatesCount; i++)
                    {
                        uint lTick = replicatesHistory[i].Data.GetTick();
                        //Exact match found.
                        if (lTick == tick)
                        {
                            result = DataPlacementResult.Exact;
                            return i;
                        }
                        /* The checked data is greater than
                         * what was being searched. This means
                         * to insert right before it. */
                        else if (lTick > tick)
                        {
                            result = DataPlacementResult.InsertMiddle;
                            return i;
                        }
                    }

                    //Should be impossible to get here.
                    result = DataPlacementResult.Error;
                    return -1;
                }
            }
        }
    }

    //See todo below.
    /* Update codegen to remove arrBuffer from replicate method calls.
     * Update codegen to remove channel from replicate method calls where applicable.
     * Convert BasicQueue<T>/RingBuffer<T> to BasicQueue/RingBuffer<ReplicateData<T>>.
     * */
    #endregion

    public abstract partial class NetworkBehaviour : MonoBehaviour
    {
        #region Public.
        /// <summary>
        /// True if this NetworkBehaviour is reconciling.
        /// If this NetworkBehaviour does not implemnent prediction methods this value will always be false.
        /// Value will be false if there is no data to reconcile to, even if the PredictionManager IsReconciling.
        /// Data may be missing if it were intentionally not sent, or due to packet loss.
        /// </summary>
        public bool IsBehaviourReconciling { get; internal set; }
        #endregion

        #region Private.
        /// <summary>
        /// Registered Replicate methods.
        /// </summary>
        private Dictionary<uint, ReplicateRpcDelegate> _replicateRpcDelegates;
        /// <summary>
        /// Registered Reconcile methods.
        /// </summary>
        private Dictionary<uint, ReconcileRpcDelegate> _reconcileRpcDelegates;
        /// <summary>
        /// Number of replicate resends which may occur.
        /// </summary>
        private int _remainingReplicateResends;
        /// <summary>
        /// Number of reconcile resends which may occur.
        /// </summary>
        private int _remainingReconcileResends;
        /// <summary>
        /// Last replicate tick read from remote. This can be the server reading a client or the other way around.
        /// </summary>
        private uint _lastReplicateReadRemoteTick = TimeManager.UNSET_TICK;
        /// <summary>
        /// Tick when replicates should begun to run. This is set and used when inputs are just received and need to queue to create a buffer.
        /// </summary>
        private uint _replicateStartTick = TimeManager.UNSET_TICK;
        /// <summary>
        /// Last tick to replicate which was not replayed.
        /// </summary>
        private uint _lastOrderedReplicatedTick = TimeManager.UNSET_TICK;
        /// <summary>
        /// Last tick read for a replicate.
        /// </summary>
        private uint _lastReadReplicateTick = TimeManager.UNSET_TICK;
        /// <summary>
        /// Last tick read for a reconcile. This is only set on the client.
        /// </summary>
        private uint _lastReadReconcileRemoteTick = TimeManager.UNSET_TICK;
        /// <summary>
        /// Last tick this object reconciled on.
        /// </summary>
        private uint _lastReconcileTick = TimeManager.UNSET_TICK;
        /// <summary>
        /// Last values when checking for transform changes since previous tick.
        /// </summary>
        private Vector3 _lastTransformPosition;
        /// <summary>
        /// Last values when checking for transform changes since previous tick.
        /// </summary>
        private Quaternion _lastTransformRotation;
        /// <summary>
        /// Last values when checking for transform changes since previous tick.
        /// </summary>
        private Vector3 _lastTransformScale;
        /// <summary>
        /// True if this Networkbehaviour implements prediction methods.
        /// </summary>
        [APIExclude]
        private bool _usesPrediction;
        #endregion

        /// <summary>
        /// Initializes the NetworkBehaviour for prediction.
        /// </summary>
        internal void Preinitialize_Prediction(bool asServer) { }

        /// <summary>
        /// Deinitializes the NetworkBehaviour for prediction.
        /// </summary>
        internal void Deinitialize_Prediction(bool asServer) { }

        /// <summary>
        /// Called when the object is destroyed.
        /// </summary>
        internal void OnDestroy_Prediction()
        {
            CollectionCaches<uint, ReplicateRpcDelegate>.StoreAndDefault(ref _replicateRpcDelegates);
            CollectionCaches<uint, ReconcileRpcDelegate>.StoreAndDefault(ref _reconcileRpcDelegates);
        }

        /// <summary>
        /// Registers a RPC method.
        /// Internal use.
        /// </summary>
        /// <param name="hash"></param>
        /// <param name="del"></param>
        [MakePublic]
        internal void RegisterReplicateRpc(uint hash, ReplicateRpcDelegate del)
        {
            _usesPrediction = true;

            if (_replicateRpcDelegates == null)
                _replicateRpcDelegates = CollectionCaches<uint, ReplicateRpcDelegate>.RetrieveDictionary();
            _replicateRpcDelegates[hash] = del;
        }

        /// <summary>
        /// Registers a RPC method.
        /// Internal use.
        /// </summary>
        /// <param name="hash"></param>
        /// <param name="del"></param>
        [MakePublic]
        internal void RegisterReconcileRpc(uint hash, ReconcileRpcDelegate del)
        {
            if (_reconcileRpcDelegates == null)
                _reconcileRpcDelegates = CollectionCaches<uint, ReconcileRpcDelegate>.RetrieveDictionary();
            _reconcileRpcDelegates[hash] = del;
        }

        /// <summary>
        /// Called when a replicate is received.
        /// </summary>
        internal void OnReplicateRpc(uint? methodHash, PooledReader reader, NetworkConnection sendingClient, Channel channel)
        {
            if (methodHash == null)
                methodHash = ReadRpcHash(reader);

            reader.NetworkManager = _networkObjectCache.NetworkManager;

            if (_replicateRpcDelegates.TryGetValueIL2CPP(methodHash.Value, out ReplicateRpcDelegate del))
                del.Invoke(reader, sendingClient, channel);
            else
                _networkObjectCache.NetworkManager.LogWarning($"Replicate not found for hash {methodHash.Value} on {gameObject.name}, behaviour {GetType().Name}. Remainder of packet may become corrupt.");
        }

        /// <summary>
        /// Called when a reconcile is received.
        /// </summary>
        internal void OnReconcileRpc(uint? methodHash, PooledReader reader, Channel channel)
        {
            if (methodHash == null)
                methodHash = ReadRpcHash(reader);

            reader.NetworkManager = _networkObjectCache.NetworkManager;

            if (_reconcileRpcDelegates.TryGetValueIL2CPP(methodHash.Value, out ReconcileRpcDelegate del))
                del.Invoke(reader, channel);
            else
                _networkObjectCache.NetworkManager.LogWarning($"Reconcile not found for hash {methodHash.Value}. Remainder of packet may become corrupt.");
        }

        /// <summary>
        /// Resets cached ticks used by prediction, such as last read and replicate tick.
        /// This is generally used when the ticks will be different then what was previously used; eg: when ownership changes.
        /// </summary>
        private void ResetState_Prediction(bool asServer)
        {
            if (!asServer)
            {
                _lastReadReconcileRemoteTick = TimeManager.UNSET_TICK;
                _lastReconcileTick = TimeManager.UNSET_TICK;
            }

            _lastOrderedReplicatedTick = TimeManager.UNSET_TICK;
            _lastReplicateReadRemoteTick = TimeManager.UNSET_TICK;
            _lastReadReplicateTick = TimeManager.UNSET_TICK;

            ClearReplicateCache();
        }

        /// <summary>
        /// Clears cached replicates for server and client. This can be useful to call on server and client after teleporting.
        /// </summary>
        public virtual void ClearReplicateCache() { }

        /// <summary>
        /// Clears cached replicates and histories.
        /// </summary>
        [MakePublic]
        internal void ClearReplicateCache_Internal<T, T2>(BasicQueue<ReplicateDataContainer<T>> replicatesQueue, RingBuffer<ReplicateDataContainer<T>> replicatesHistory, RingBuffer<LocalReconcile<T2>> reconcilesHistory, ref T lastReadReplicate, ref T2 lastReadReconcile) where T : IReplicateData, new() where T2 : IReconcileData, new()
        {
            while (replicatesQueue.Count > 0)
            {
                ReplicateDataContainer<T> dataContainer = replicatesQueue.Dequeue();
                dataContainer.Dispose();
            }

            if (lastReadReplicate != null)
                lastReadReplicate.Dispose();
            lastReadReplicate = default;
            
            if (lastReadReconcile != null)
                lastReadReconcile.Dispose();
            lastReadReconcile = default;

            for (int i = 0; i < replicatesHistory.Count; i++)
            {
                ReplicateDataContainer<T> dataContainer = replicatesHistory[i];
                dataContainer.Dispose();
            }
            replicatesHistory.Clear();

            ClearReconcileHistory(reconcilesHistory);
        }

        /// <summary>
        /// Sends a RPC to target.
        /// Internal use.
        /// </summary>
        [MakePublic]
        private void Server_SendReconcileRpc<T>(uint hash, ref T lastReconcileData, T reconcileData, Channel channel) where T : IReconcileData
        {
            if (!IsSpawned)
                return;

            //If channel is reliable set remaining resends to 1.
            if (channel == Channel.Reliable)
                _remainingReconcileResends = 1;

            if (_remainingReconcileResends == 0)
                return;
            _remainingReconcileResends--;

            //No owner and no state forwarding, nothing to do.
            bool stateForwarding = _networkObjectCache.EnableStateForwarding;
            if (!Owner.IsValid && !stateForwarding)
                return;

            /* Set the channel for Rpcs to reliable to that the length
             * is written. The data does not actually send reliable, unless
             * the channel is of course that to start. */
            /* This is a temporary solution to resolve an issue which was
             * causing parsing problems due to states sending unreliable and reliable
             * headers being written, or sending reliably and unreliable headers being written.
             * Using an extra byte to write length is more preferred than always forcing reliable
             * until properly resolved. */
            const Channel rpcChannel = Channel.Reliable;

            PooledWriter methodWriter = WriterPool.Retrieve();
            /* Tick does not need to be written because it will always
             * be the localTick of the server. For the clients, this will
             * be the LastRemoteTick of the packet.
             *
             * The exception is for the owner, which we send the last replicate
             * tick so the owner knows which to roll back to. */

//#if !FISHNET_STABLE_SYNCTYPES
#if DO_NOT_USE
            methodWriter.WriteDeltaReconcile(lastReconcileData, reconcileData, GetDeltaSerializeOption());
#else
            methodWriter.WriteReconcile<T>(reconcileData);
#endif
            lastReconcileData = reconcileData;

            PooledWriter writer;
#if DEVELOPMENT
            if (!NetworkManager.DebugManager.DisableReconcileRpcLinks && _rpcLinks.TryGetValueIL2CPP(hash, out RpcLinkType link))
#else
			if (_rpcLinks.TryGetValueIL2CPP(hash, out RpcLinkType link))
#endif
                writer = CreateLinkedRpc(link, methodWriter, rpcChannel);
            else
                writer = CreateRpc(hash, methodWriter, PacketId.Reconcile, rpcChannel);

            //If state forwarding is not enabled then only send to owner.
            if (!stateForwarding)
            {
                Owner.WriteState(writer);
            }
            //State forwarding, send to all.
            else
            {
                foreach (NetworkConnection nc in Observers)
                    nc.WriteState(writer);
            }

            methodWriter.Store();
            writer.Store();
        }

        /// <summary> 
        /// Returns if there is a chance the transform may change after the tick.
        /// </summary>
        /// <returns></returns>
        private bool TransformChanged()
        {
            if (TimeManager.PhysicsMode == PhysicsMode.Disabled)
                return false;

            /* Use distance when checking if changed because rigidbodies can twitch
             * or move an extremely small amount. These small moves are not worth
             * resending over because they often fix themselves each frame. */
            float changeDistance = 0.000004f;

            bool anyChanged = false;
            anyChanged |= (transform.position - _lastTransformPosition).sqrMagnitude > changeDistance;
            if (!anyChanged)
                anyChanged |= (transform.rotation.eulerAngles - _lastTransformRotation.eulerAngles).sqrMagnitude > changeDistance;
            if (!anyChanged)
                anyChanged |= (transform.localScale - _lastTransformScale).sqrMagnitude > changeDistance;

            //If transform changed update last values.
            if (anyChanged)
            {
                _lastTransformPosition = transform.position;
                _lastTransformRotation = transform.rotation;
                _lastTransformScale = transform.localScale;
            }

            return anyChanged;
        }

        /// <summary>
        /// Performs a replicate for current tick.
        /// </summary>
        [MakePublic]
        internal void Replicate_Current<T>(ReplicateUserLogicDelegate<T> del, uint methodHash, BasicQueue<ReplicateDataContainer<T>> replicatesQueue, RingBuffer<ReplicateDataContainer<T>> replicatesHistory, ReplicateDataContainer<T> dataContainer) where T : IReplicateData, new()
        {
            /* Do not run if currently reconciling.
             * This change allows devs to call inherited replicates
             * from replays to only run the method logic without
             * prompting for network action. */
            if (_networkObjectCache.PredictionManager.IsReconciling)
                return;

            if (_networkObjectCache.IsController)
                Replicate_Authoritative(del, methodHash, replicatesHistory, dataContainer);
            else
                Replicate_NonAuthoritative(del, replicatesQueue, replicatesHistory);
        }

        /// <summary>
        /// Returns if a replicates data changed and updates resends as well data tick.
        /// </summary>
        /// <param name="enqueueData">True to enqueue data for replaying.</param>
        /// <returns>True if data has changed..</returns>
        private void Replicate_Authoritative<T>(ReplicateUserLogicDelegate<T> del, uint methodHash, RingBuffer<ReplicateDataContainer<T>> replicatesHistory, ReplicateDataContainer<T> dataContainer) where T : IReplicateData, new()
        {
            bool ownerlessAndServer = (!Owner.IsValid && IsServerStarted);
            if (!IsOwner && !ownerlessAndServer)
                return;

            Func<T, bool> isDefaultDel = PublicPropertyComparer<T>.IsDefault;
            if (isDefaultDel == null)
            {
                NetworkManager.LogError($"{nameof(PublicPropertyComparer<T>)} not found for type {typeof(T).FullName}");
                return;
            }

            PredictionManager pm = NetworkManager.PredictionManager;
            uint dataTick = TimeManager.LocalTick;

            /* The following code is to remove replicates from replicatesHistory
             * which exceed the buffer allowance. Replicates are kept for up to
             * x seconds to clients can re-run them during a reconcile. The reconcile
             * method removes old histories but given the server does not reconcile,
             * it will never perform that operation.
             * The server would not actually need to keep replicates history except
             * when it is also client(clientHost). This is because the clientHost must
             * send redundancies to other clients still, therefor that redundancyCount
             * must be the allowance when clientHost. */
            if (IsHostStarted)
            {
                int replicatesHistoryCount = replicatesHistory.Count;
                int maxCount = pm.RedundancyCount;
                //Number to remove which is over max count.
                int removeCount = (replicatesHistoryCount - maxCount);
                //If there are any to remove.
                if (removeCount > 0)
                {
                    //Dispose first.
                    for (int i = 0; i < removeCount; i++)
                        replicatesHistory[i].Dispose();

                    //Then remove range.
                    replicatesHistory.RemoveRange(true, removeCount);
                }
            }

            dataContainer.SetDataTick(dataTick);
            AddReplicatesHistory(replicatesHistory, dataContainer);

            //Check to reset resends.
            bool isDefault = isDefaultDel.Invoke(dataContainer.Data);
            bool resetResends = (!isDefault || TransformChanged());

            byte redundancyCount = PredictionManager.RedundancyCount;

            //Standard delta serialize option.
            //+1 to redundancy so lastFirstRead is pushed out to the last actual input when server reads.
            if (resetResends)
            {
                _remainingReplicateResends = redundancyCount;
                _remainingReconcileResends = redundancyCount;
            }

            bool sendData = (_remainingReplicateResends > 0);
            if (sendData)
            {
                /* If not server then send to server.
                 * If server then send to clients. */
                bool toServer = !IsServerStarted;
                Replicate_SendAuthoritative(toServer, methodHash, redundancyCount, replicatesHistory, dataTick, dataContainer.Channel, GetDeltaSerializeOption());
                _remainingReplicateResends--;
            }

            SetReplicateTick(dataTick, createdReplicate: true);

#if !FISHNET_STABLE_REPLICATESTATES
            //Owner always replicates with new data.
            del.Invoke(dataContainer.Data, (ReplicateState.Ticked | ReplicateState.Created), dataContainer.Channel);
#else
            del.Invoke(dataContainer.Data, ReplicateState.CurrentCreated, dataContainer.Channel);
#endif
        }

        /// <summary>
        /// Gets the next replicate in perform when server or non-owning client.
        /// </summary>
        /// </summary>
        private void Replicate_NonAuthoritative<T>(ReplicateUserLogicDelegate<T> del, BasicQueue<ReplicateDataContainer<T>> replicatesQueue, RingBuffer<ReplicateDataContainer<T>> replicatesHistory) where T : IReplicateData, new()
        {
            bool serverStarted = _networkObjectCache.IsServerStarted;
            bool ownerlessAndServer = (!Owner.IsValid && serverStarted);
            if (IsOwner || ownerlessAndServer)
                return;
            /* Still need to run inputs if server, even if forwarding
             * is not enabled.*/
            if (!_networkObjectCache.EnableStateForwarding && !serverStarted)
                return;

            TimeManager tm = _networkObjectCache.TimeManager;
            PredictionManager pm = _networkObjectCache.PredictionManager;
            uint localTick = tm.LocalTick;
            bool isServer = _networkObjectCache.IsServerStarted;
            bool isAppendedOrder = pm.IsAppendedStateOrder;

            //Server is initialized or appended state order.
            if (isServer || isAppendedOrder)
            {
                int count = replicatesQueue.Count;
                /* If count is 0 then data must be set default
                 * and as predicted. */
                if (count == 0)
                {
                    ReplicateDefaultData();
                }
                //Not predicted, is user created.
                else
                {
                    //Check to unset start tick, which essentially voids it resulting in inputs being run immediately.
                    /* As said above, if start tick is unset then replicates
                     * can run. When still set that means the start condition has
                     * not been met yet. */
                    if (localTick >= _replicateStartTick)
                    {
                        _replicateStartTick = TimeManager.UNSET_TICK;
                        ReplicateDataContainer<T> queueEntry;
                        bool queueEntryValid = false;
                        while (replicatesQueue.TryDequeue(out queueEntry))
                        {
                            if (queueEntry.Data.GetTick() > _lastReconcileTick)
                            {
                                queueEntryValid = true;
                                break;
                            }
                        }

                        if (queueEntryValid)
                        {
                            _remainingReconcileResends = pm.RedundancyCount;

#if !FISHNET_STABLE_REPLICATESTATES
                            ReplicateData(queueEntry, (ReplicateState.Ticked | ReplicateState.Created));
#else
                            ReplicateData(queueEntry, ReplicateState.CurrentCreated);
#endif

                            //Update count since old entries were dropped and one replicate run.
                            count = replicatesQueue.Count;

                            bool consumeExcess = (!pm.DropExcessiveReplicates || IsClientOnlyStarted);
                            int leaveInBuffer = _networkObjectCache.PredictionManager.StateInterpolation;

                            //Only consume if the queue count is over leaveInBuffer.
                            if (consumeExcess && count > leaveInBuffer)
                            {
                                const byte maximumAllowedConsumes = 1;
                                int maximumPossibleConsumes = (count - leaveInBuffer);
                                int consumeAmount = Mathf.Min(maximumAllowedConsumes, maximumPossibleConsumes);

                                for (int i = 0; i < consumeAmount; i++)
#if !FISHNET_STABLE_REPLICATESTATES
                                    ReplicateData(replicatesQueue.Dequeue(), (ReplicateState.Ticked | ReplicateState.Created));
#else
                                    ReplicateData(replicatesQueue.Dequeue(), ReplicateState.CurrentCreated);
#endif
                            }
                        }
                    }
                    //Not enough ticks passed yet to run actually data.
                    else
                    {
                        ReplicateDefaultData();
                    }
                }
            }
            //Is client only and not using future state order.
            else
            {
                ReplicateDefaultData();
            }

            //Performs a replicate using default data.
            void ReplicateDefaultData()
            {
                uint tick = (GetDefaultedLastReplicateTick() + 1);
                ReplicateDataContainer<T> dataContainer = ReplicateDataContainer<T>.GetDefault(tick);
#if !FISHNET_STABLE_REPLICATESTATES
                ReplicateData(dataContainer, ReplicateState.Ticked);
#else
                ReplicateData(dataContainer, ReplicateState.CurrentFuture);
#endif
            }

            void ReplicateData(ReplicateDataContainer<T> data, ReplicateState state)
            {
                uint tick = data.Data.GetTick();
#if !FISHNET_STABLE_REPLICATESTATES
                SetReplicateTick(tick, state.ContainsCreated());
#else
                SetReplicateTick(tick, (state == ReplicateState.CurrentCreated));
#endif
                /* If server or appended state order then insert/add to history when run
                 * within this method.
                 * Whether data is inserted/added into the past (replicatesHistory) depends on
                 * if client only && and state order.
                 *
                 * Server only adds onto the history after running the inputs. This is so
                 * the server can send past inputs with redundancy.
                 *
                 * Client inserts into the history under two scenarios:
                 *  - If state order is using inserted. This is done when the data is read so it
                 *  can be iterated during the next reconcile, since the data is not added to
                 *  a queue otherwise. This is what causes the requirement to reconcile to run
                 *  datas.
                 *  - If the state order if using append, and the state just ran. This is so that
                 *  the reconcile does not replay data which hasn't yet run. But, the data should still
                 *  be inserted at point of run so reconciles can correct to the state at the right
                 *  point in history.*/

                //Server always adds.
                if (isServer)
                    AddReplicatesHistory(replicatesHistory, data);
                //If client insert value into history.
                else
                    InsertIntoReplicateHistory(data, replicatesHistory);

                del.Invoke(data.Data, state, data.Channel);
            }

            //Returns a replicate tick for when data is not created.
            uint GetDefaultedLastReplicateTick()
            {
                if (_lastOrderedReplicatedTick == TimeManager.UNSET_TICK)
                    _lastOrderedReplicatedTick = (tm.LastPacketTick.Value() + pm.StateInterpolation);

                return _lastOrderedReplicatedTick;
            }
        }

        /// <summary>
        /// Called internally when an input from localTick should be replayed.
        /// </summary>
        internal virtual void Replicate_Replay_Start(uint replayTick) { }

        /// <summary>
        /// Replays inputs from replicates.
        /// </summary>
        /// <remarks>The server calls this from codegen but it never completes as IsBehaviourReconciling will always be false on server.</remarks>
        [MakePublic]
        internal void Replicate_Replay<T>(uint replayTick, ReplicateUserLogicDelegate<T> del, RingBuffer<ReplicateDataContainer<T>> replicatesHistory) where T : IReplicateData, new()
        {
            //Reconcile data was not received so cannot replay.
            if (!IsBehaviourReconciling)
                return;

            if (_networkObjectCache.IsController)
                Replicate_Replay_Authoritative(replayTick, del, replicatesHistory);
            else
                Replicate_Replay_NonAuthoritative(replayTick, del, replicatesHistory);
        }

        /// <summary>
        /// Replays an input for authoritative entity.
        /// </summary>
        private void Replicate_Replay_Authoritative<T>(uint replayTick, ReplicateUserLogicDelegate<T> del, RingBuffer<ReplicateDataContainer<T>> replicatesHistory) where T : IReplicateData, new()
        {
            ReplicateTickFinder.DataPlacementResult findResult;
            int replicateIndex = ReplicateTickFinder.GetReplicateHistoryIndex(replayTick, replicatesHistory, out findResult);

            ReplicateDataContainer<T> dataContainer;
            ReplicateState state;
            //If found then the replicate has been received by the server.
            if (findResult == ReplicateTickFinder.DataPlacementResult.Exact)
            {
                dataContainer = replicatesHistory[replicateIndex];
#if !FISHNET_STABLE_REPLICATESTATES
                state = (ReplicateState.Replayed | ReplicateState.Ticked | ReplicateState.Created);
#else
                state = ReplicateState.ReplayedCreated;
#endif

                //SetReplicateTick(data.GetTick(), true);
                del.Invoke(dataContainer.Data, state, dataContainer.Channel);
            }
        }

        /// <summary>
        /// Replays an input for non authoritative entity.
        /// </summary>
        [MakePublic]
        private void Replicate_Replay_NonAuthoritative<T>(uint replayTick, ReplicateUserLogicDelegate<T> del, RingBuffer<ReplicateDataContainer<T>> replicatesHistory) where T : IReplicateData, new()
        {
                         
            ReplicateDataContainer<T> dataContainer;
            ReplicateState state;
            bool isAppendedOrder = _networkObjectCache.PredictionManager.IsAppendedStateOrder;
            //If the first replay.
            if (isAppendedOrder || replayTick == (_networkObjectCache.PredictionManager.ServerStateTick + 1))
            {
                ReplicateTickFinder.DataPlacementResult findResult;
                int replicateIndex = ReplicateTickFinder.GetReplicateHistoryIndex(replayTick, replicatesHistory, out findResult);
                //If not found then something went wrong.
                if (findResult == ReplicateTickFinder.DataPlacementResult.Exact)
                {
                    dataContainer = replicatesHistory[replicateIndex];

#if !FISHNET_STABLE_REPLICATESTATES
                    state = ReplicateState.Replayed;

                    bool isCreated = dataContainer.IsCreated;
                    //Set if created.
                    if (isCreated)
                        state |= ReplicateState.Created;
                    /* Ticked will be true if value had ticked outside of reconcile,
                     * or if data is created. It's possible for data to be created
                     * and not yet ticked if state order is inserted rather than append. */
                    if (replayTick <= _lastOrderedReplicatedTick || isCreated)
                        state |= ReplicateState.Ticked;
#else
                    //state = ReplicateState.ReplayedCreated;
                    state = (dataContainer.IsCreated) ? ReplicateState.ReplayedCreated : ReplicateState.ReplayedFuture;
#endif
                }
                else
                {
                    SetDataToDefault();
                }
            }
            //Not the first replay tick.
            else
            {
                SetDataToDefault();
            }

            void SetDataToDefault()
            {
                dataContainer = ReplicateDataContainer<T>.GetDefault(replayTick);
#if !FISHNET_STABLE_REPLICATESTATES
                state = ReplicateState.Replayed;
#else
                state = ReplicateState.ReplayedFuture;
#endif
            }

            del.Invoke(dataContainer.Data, state, dataContainer.Channel);
        }

        /// <summary>
        /// This is overriden by codegen to call EmptyReplicatesQueueIntoHistory().
        /// This should only be called when client only.
        /// </summary>
        [MakePublic]
        internal virtual void EmptyReplicatesQueueIntoHistory_Start() { }

        /// <summary>
        /// Replicates which are enqueued will be removed from the queue and put into replicatesHistory.
        /// This should only be called when client only.
        /// </summary>
        [MakePublic]
        internal void EmptyReplicatesQueueIntoHistory<T>(BasicQueue<ReplicateDataContainer<T>> replicatesQueue, RingBuffer<ReplicateDataContainer<T>> replicatesHistory) where T : IReplicateData, new()
        {
            while (replicatesQueue.TryDequeue(out ReplicateDataContainer<T> data))
                InsertIntoReplicateHistory(data, replicatesHistory);
        }

        /// <summary>
        /// Returns the DeltaSerializeOption to use for the tick.
        /// </summary>
        /// <param name="resendsEnded"></param>
        /// <returns></returns>
        private DeltaSerializerOption GetDeltaSerializeOption()
        {
            uint localTick = _networkObjectCache.TimeManager.LocalTick;
            ushort tickRate = _networkObjectCache.TimeManager.TickRate;
            /* New observers so send a full serialize next replicate.
             * This could go out to only the newly added observers, but it
             * would generate a lot more complexity to save presumably
             * a small amount of occasional bandwidth. */
            if (_networkObjectCache.ObserverAddedTick == localTick)
                return DeltaSerializerOption.FullSerialize;
            //Send full every half a second.
            //else if (localTick % tickRate == 0 || localTick % (tickRate / 2) == 0)
            //     return DeltaSerializerOption.FullSerialize;
            //Send full every second.
            else if (localTick % tickRate == 0)
                return DeltaSerializerOption.FullSerialize;
            //Otherwise return rootSerialize, the default for sending the child most data.
            else
                return DeltaSerializerOption.RootSerialize;
        }

        /// <summary>
        /// Sends a Replicate to server or clients.
        /// </summary>
        private void Replicate_SendAuthoritative<T>(bool toServer, uint hash, int pastInputs, RingBuffer<ReplicateDataContainer<T>> replicatesHistory, uint queuedTick, Channel channel, DeltaSerializerOption deltaOption) where T : IReplicateData, new()
        {
            /* Do not use IsSpawnedWithWarning because the server
             * will still call this a tick or two as clientHost when
             * an owner disconnects. This comes from calling Replicate(default)
             * for the server-side processing in NetworkBehaviours. */
            if (!IsSpawned)
                return;

            int historyCount = replicatesHistory.Count;
            //Nothing to send; should never be possible.
            if (historyCount <= 0)
                return;

            //Number of past inputs to send.
            if (historyCount < pastInputs)
                pastInputs = historyCount;
            /* Where to start writing from. When passed
             * into the writer values from this offset
             * and forward will be written.
             * Always write up to past inputs. */
            int offset = (historyCount - pastInputs);

            //Write history to methodWriter.
            PooledWriter methodWriter = WriterPool.Retrieve(WriterPool.LENGTH_BRACKET);
            /* If going to clients from the server then
             * write the queueTick. */
            if (!toServer)
                methodWriter.WriteTickUnpacked(queuedTick);
//#if !FISHNET_STABLE_SYNCTYPES
#if DO_NOT_USE
            methodWriter.WriteDeltaReplicate(replicatesHistory, offset, deltaOption);
#else
            methodWriter.WriteReplicate<T>(replicatesHistory, offset);
#endif
            _transportManagerCache.CheckSetReliableChannel(methodWriter.Length + MAXIMUM_RPC_HEADER_SIZE, ref channel);
            PooledWriter writer = CreateRpc(hash, methodWriter, PacketId.Replicate, channel);

            /* toServer will never be true if clientHost.
             * When clientHost and here replicates will
             * always just send to clients, while
             * excluding clientHost. */
            if (toServer)
            {
                NetworkManager.TransportManager.SendToServer((byte)channel, writer.GetArraySegment(), splitLargeMessages: true);
            }
            else
            {
                /* If going to clients from server, then only send
                 * if state forwarding is enabled. */
                if (_networkObjectCache.EnableStateForwarding)
                {
                    //Exclude owner and if clientHost, also localClient.
                    _networkConnectionCache.Clear();
                    _networkConnectionCache.Add(Owner);
                    if (IsClientStarted)
                        _networkConnectionCache.Add(ClientManager.Connection);

                    NetworkManager.TransportManager.SendToClients((byte)channel, writer.GetArraySegment(), Observers, _networkConnectionCache, splitLargeMessages: true);
                }
            }

            /* If sending as reliable there is no reason
             * to perform resends, so clear remaining resends. */
            if (channel == Channel.Reliable)
                _remainingReplicateResends = 0;

            methodWriter.StoreLength();
            writer.StoreLength();
        }

        /// <summary>
        /// Reads a replicate the client.
        /// </summary>
        [MakePublic]
        internal void Replicate_Reader<T>(uint hash, PooledReader reader, NetworkConnection sender, ref ReplicateDataContainer<T> lastReadReplicate, BasicQueue<ReplicateDataContainer<T>> replicatesQueue, RingBuffer<ReplicateDataContainer<T>> replicatesHistory, Channel channel) where T : IReplicateData, new()
        {
            /* This will never be received on owner, except in the condition
             * the server is the owner and also a client. In such condition
             * the method is exited after data is parsed. */
            PredictionManager pm = _networkObjectCache.PredictionManager;
            TimeManager tm = _networkObjectCache.TimeManager;
            bool fromServer = (reader.Source == Reader.DataSource.Server);

            uint tick;
            /* If coming from the server then read the tick. Server sends tick
             * if authority or if relaying from another client. The tick which
             * arrives will be the tick the replicate will run on the server. */
            if (fromServer)
                tick = reader.ReadTickUnpacked();
            /* When coming from a client it will always be owner.
             * Client sends out replicates soon as they are run.
             * It's safe to use the LastRemoteTick from the client
             * in addition to QueuedInputs. */
            else
                tick = (tm.LastPacketTick.LastRemoteTick);

//#if !FISHNET_STABLE_SYNCTYPES
#if DO_NOT_USE
            receivedReplicatesCount = reader.ReadDeltaReplicate(lastReadReplicate, ref arrBuffer, tick);
#else
            List<ReplicateDataContainer<T>> readReplicates = reader.ReadReplicate<T>(tick);
#endif
            //Update first read if able.
            if (readReplicates.Count > 0)
            {
                lastReadReplicate.Dispose();
                lastReadReplicate = readReplicates[^1];
            }

            //If received on clientHost simply ignore after parsing data.
            if (fromServer && IsHostStarted)
                return;

            /* Replicate rpc readers relay to this method and
             * do not have an owner check in the generated code.
             * Only server needs to check for owners. Clients
             * should accept the servers data regardless.
             *
             * If coming from a client and that client is not owner then exit. */
            if (!fromServer && !OwnerMatches(sender))
                return;
            //Early exit if old data.
            if (TimeManager.LastPacketTick.LastRemoteTick < _lastReplicateReadRemoteTick)
                return;
            _lastReplicateReadRemoteTick = TimeManager.LastPacketTick.LastRemoteTick;

            //If from a client that is not clientHost do some safety checks.
            if (!fromServer && !Owner.IsLocalClient)
            {
                if (readReplicates.Count > pm.RedundancyCount)
                {
                    sender.Kick(reader, KickReason.ExploitAttempt, LoggingType.Common, $"Connection {sender.ToString()} sent too many past replicates. Connection will be kicked immediately.");
                    return;
                }
            }

            Replicate_EnqueueReceivedReplicate(readReplicates, replicatesQueue, replicatesHistory);
            Replicate_SendNonAuthoritative(hash, replicatesQueue, channel);

            CollectionCaches<ReplicateDataContainer<T>>.Store(readReplicates);
        }

        /// <summary>
        /// Sends data from a reader which only contains the replicate packet.
        /// </summary>
        [MakePublic]
        internal void Replicate_SendNonAuthoritative<T>(uint hash, BasicQueue<ReplicateDataContainer<T>> replicatesQueue, Channel channel) where T : IReplicateData, new()
        {
            if (!IsServerStarted)
                return;
            if (!_networkObjectCache.EnableStateForwarding)
                return;

            int queueCount = replicatesQueue.Count;
            //None to send.
            if (queueCount == 0)
                return;

            //If the only observer is the owner then there is no need to write.
            int observersCount = Observers.Count;
            //Quick exit for no observers other than owner.
            if (observersCount == 0 || (Owner.IsValid && observersCount == 1))
                return;

            PooledWriter methodWriter = WriterPool.Retrieve(WriterPool.LENGTH_BRACKET);

            uint localTick = _networkObjectCache.TimeManager.LocalTick;
            /* Write when the last entry will run.
             *
             * Typically, the last entry will run on localTick + (queueCount - 1).
             * 1 is subtracted from queueCount because in most cases the first entry
             * is going to run same tick.
             * An exception is when the replicateStartTick is set, then there is going
             * to be a delayed based on start tick difference. */
            uint runTickOflastEntry = localTick + ((uint)queueCount - 1);
            //If start tick is set then add on the delay.
            if (_replicateStartTick != TimeManager.UNSET_TICK)
                runTickOflastEntry += (_replicateStartTick - TimeManager.LocalTick);
            //Write the run tick now.
            methodWriter.WriteTickUnpacked(runTickOflastEntry);
            //Write the replicates.
            int redundancyCount = (int)Mathf.Min(_networkObjectCache.PredictionManager.RedundancyCount, queueCount);
//#if !FISHNET_STABLE_SYNCTYPES
#if DO_NOT_USE
            methodWriter.WriteDeltaReplicate(replicatesQueue, redundancyCount, GetDeltaSerializeOption());
#else
            methodWriter.WriteReplicate<T>(replicatesQueue, redundancyCount);
#endif
            PooledWriter writer = CreateRpc(hash, methodWriter, PacketId.Replicate, channel);

            //Exclude owner and if clientHost, also localClient.
            _networkConnectionCache.Clear();
            if (Owner.IsValid)
                _networkConnectionCache.Add(Owner);
            if (IsClientStarted && !Owner.IsLocalClient)
                _networkConnectionCache.Add(ClientManager.Connection);

            NetworkManager.TransportManager.SendToClients((byte)channel, writer.GetArraySegment(), Observers, _networkConnectionCache, false);

            methodWriter.StoreLength();
            writer.StoreLength();
        }

        /// <summary>
        /// Handles a received replicate packet.
        /// </summary>
        private void Replicate_EnqueueReceivedReplicate<T>(List<ReplicateDataContainer<T>> readDatas, BasicQueue<ReplicateDataContainer<T>> replicatesQueue, RingBuffer<ReplicateDataContainer<T>> replicatesHistory) where T : IReplicateData, new()
        {
            int startQueueCount = replicatesQueue.Count;
            /* Owner never gets this for their own object so
             * this can be processed under the assumption data is only
             * handled on unowned objects. */
            PredictionManager pm = PredictionManager;

            bool isServer = _networkObjectCache.IsServerStarted;
            bool isAppendedOrder = pm.IsAppendedStateOrder;

            //Maximum number of replicates allowed to be queued at once.
            int maximmumReplicates = (IsServerStarted) ? pm.GetMaximumServerReplicates() : pm.MaximumPastReplicates;

            for (int i = 0; i < readDatas.Count; i++)
            {
                ReplicateDataContainer<T> dataContainer = readDatas[i];
                dataContainer.IsCreated = true;
                uint tick = dataContainer.Data.GetTick();

                //Skip if old data.
                if (tick <= _lastReadReplicateTick)
                {
                    dataContainer.Dispose();
                    continue;
                }

                _lastReadReplicateTick = tick;

                //Cannot queue anymore, discard oldest.
                if (replicatesQueue.Count > maximmumReplicates)
                {
                    ReplicateDataContainer<T> disposableDataContainer = replicatesQueue.Dequeue();
                    disposableDataContainer.Dispose();
                }

                /* Check if replicate is already in history.
                 * This can occur when the replicate method has a predicted
                 * state for the tick, but a user created replicate comes
                 * through afterward.
                 *
                 * Only perform this check if not the server, since server
                 * does not reconcile it will never use replicatesHistory.
                 *
                 * When clients are also using ReplicateStateOrder.Future the replicates
                 * do not need to be put into the past, as they're always added onto
                 * the end of the queue.
                 *
                 * The server also does not predict replicates in the same way
                 * a client does. When an owner sends a replicate to the server
                 * the server only uses the owner tick to check if it's an old replicate.
                 * But when running the replicate, the server applies it's local tick and
                 * sends that to spectators. */
                //Add automatically if server or future order.
                if (isServer || isAppendedOrder)
                    replicatesQueue.Enqueue(dataContainer);
                //Run checks to replace data if not server.
                else
                    InsertIntoReplicateHistory(dataContainer, replicatesHistory);
            }

            /* If entries are being added after nothing then
             * start the queued inputs delay. Only the server needs
             * to do this since clients implement the queue delay
             * by holding reconcile x ticks rather than not running received
             * x ticks. */
            if ((isServer || isAppendedOrder) && startQueueCount == 0 && replicatesQueue.Count > 0)
                _replicateStartTick = (_networkObjectCache.TimeManager.LocalTick + pm.StateInterpolation);
        }

        /// <summary>
        /// Inserts data into the replicatesHistory collection.
        /// This should only be called when client only.
        /// </summary>
        private void InsertIntoReplicateHistory<T>(ReplicateDataContainer<T> dataContainer, RingBuffer<ReplicateDataContainer<T>> replicatesHistory) where T : IReplicateData, new()
        {
            /* See if replicate tick is in history. Keep in mind
             * this is the localTick from the server, not the localTick of
             * the client which is having their replicate relayed. */
            ReplicateTickFinder.DataPlacementResult findResult;
            int index = ReplicateTickFinder.GetReplicateHistoryIndex(dataContainer.Data.GetTick(), replicatesHistory, out findResult);

            /* Exact entry found. This is the most likely
             * scenario. Client would have already run the tick
             * in the future, and it's now being replaced with
             * the proper data. */
            if (findResult == ReplicateTickFinder.DataPlacementResult.Exact)
            {
                ReplicateDataContainer<T> prevEntry = replicatesHistory[index];
                prevEntry.Dispose();
                replicatesHistory[index] = dataContainer;
            }
            else if (findResult == ReplicateTickFinder.DataPlacementResult.InsertMiddle)
            {
                InsertReplicatesHistory(replicatesHistory, dataContainer, index);
            }
            else if (findResult == ReplicateTickFinder.DataPlacementResult.InsertEnd)
            {
                AddReplicatesHistory(replicatesHistory, dataContainer);
            }

            /* Insert beginning should not happen unless the data is REALLY old.
             * This would mean the network was in an unplayable state. Discard the
             * data. */
            if (findResult == ReplicateTickFinder.DataPlacementResult.InsertBeginning)
                InsertReplicatesHistory(replicatesHistory, dataContainer, 0);
        }

        /// <summary>
        /// Adds to replicate history disposing of old entries if needed.
        /// </summary>
        private void AddReplicatesHistory<T>(RingBuffer<ReplicateDataContainer<T>> replicatesHistory, ReplicateDataContainer<T> value) where T : IReplicateData, new()
        {
            ReplicateDataContainer<T> prev = replicatesHistory.Add(value);
            if (prev.Data != null)
                prev.Dispose();
        }

        /// <summary>
        /// Inserts to replicate history disposing of old entries if needed.
        /// </summary>
        private void InsertReplicatesHistory<T>(RingBuffer<ReplicateDataContainer<T>> replicatesHistory, ReplicateDataContainer<T> value, int index) where T : IReplicateData, new()
        {
            ReplicateDataContainer<T> prev = replicatesHistory.Insert(index, value);
            if (prev.Data != null)
                prev.Dispose();
        }

        /// <summary>
        /// Override this method to create your reconcile data, and call your reconcile method.
        /// </summary>
        public virtual void CreateReconcile() { }

        /// <summary>
        /// Sends a reconcile to clients.
        /// </summary>
        [MakePublic]
        internal void Reconcile_Server<T>(uint methodHash, ref T lastReconcileData, T data, Channel channel) where T : IReconcileData
        {
            //Tick does not need to be set for reconciles since they come in as state updates, which have the tick included globally.
            if (IsServerInitialized)
                Server_SendReconcileRpc(methodHash, ref lastReconcileData, data, channel);
        }

        /// <summary>
        /// This is called when the NetworkBehaviour should perform a reconcile.
        /// Codegen overrides this calling Reconcile_Client with the needed data.
        /// </summary>
        [MakePublic]
        internal virtual void Reconcile_Client_Start() { }

        /// <summary>
        /// Adds a reconcile to local reconcile history.
        /// </summary>
        [MakePublic]
        internal void Reconcile_Client_AddToLocalHistory<T>(RingBuffer<LocalReconcile<T>> reconcilesHistory, T data) where T : IReconcileData
        {
            //Server does not need to store these locally.
            if (_networkObjectCache.IsServerStarted)
                return;
            if (!_networkObjectCache.PredictionManager.CreateLocalStates)
                return;

            /* This is called by the local client when creating
             * a local reconcile state. These states should always
             * be in order, so we will add data to the end
             * of the collection. */

            /* These datas are used to fill missing reconciles
             * be it the packet dropped, server doesnt need to send,
             * or if the player is throttling reconciles. */

            uint tick = _networkObjectCache.PredictionManager.GetCreateReconcileTick(_networkObjectCache.IsOwner);
            //Tick couldn't be retrieved.
            if (tick == TimeManager.UNSET_TICK)
                return;

            data.SetTick(tick);

            //Build LocalReconcile.
            LocalReconcile<T> lr = new();
            lr.Initialize(tick, data);

            reconcilesHistory.Add(lr);
        }

        /// <summary>
        /// Called by codegen with data provided by user, such as from overriding CreateReconcile.
        /// </summary>
        [MakePublic]
        internal void Reconcile_Current<T>(uint hash, ref T lastReconcileData, RingBuffer<LocalReconcile<T>> reconcilesHistory, T data, Channel channel) where T : IReconcileData, new()
        {
            if (_networkObjectCache.PredictionManager.IsReconciling)
                return;

            if (_networkObjectCache.IsServerInitialized)
                Reconcile_Server(hash, ref lastReconcileData, data, channel);
            else
                Reconcile_Client_AddToLocalHistory(reconcilesHistory, data);
        }

        /// <summary>
        /// Runs a reconcile. Prefers server data if available, otherwise uses local history data.
        /// </summary>
        [MakePublic]
        internal void Reconcile_Client<T, T2>(ReconcileUserLogicDelegate<T> reconcileDel, RingBuffer<ReplicateDataContainer<T2>> replicatesHistory, RingBuffer<LocalReconcile<T>> reconcilesHistory, T data) where T : IReconcileData where T2 : IReplicateData, new()
        {
            bool isBehaviourReconciling = IsBehaviourReconciling;

            const long unsetHistoryIndex = -1;
            long historyIndex = unsetHistoryIndex;

            /* There should always be entries, except when the object
             * first spawns.
             *
             * Find the history index associated with the reconcile tick. */
            if (reconcilesHistory.Count > 0)
            {
                //If reconcile data received then use that tick, otherwise get estimated tick for this reconcile.
                uint reconcileTick = (isBehaviourReconciling) ? data.GetTick() : _networkObjectCache.PredictionManager.GetReconcileStateTick(_networkObjectCache.IsOwner);

                uint firstHistoryTick = reconcilesHistory[0].Tick;
                historyIndex = ((long)reconcileTick - (long)firstHistoryTick);

                /* If difference is negative then
                 * the first history is beyond the tick being reconciled.
                 * EG: if history index 0 is 100 and reconcile tick is 90 then
                 * (90 - 100) = -10.
                 * This should only happen when first connecting and data hasn't been made yet. */
                if (!IsHistoryIndexValid((int)historyIndex))
                {
                    historyIndex = unsetHistoryIndex;
                    ClearReconcileHistory(reconcilesHistory);
                }
                //Valid history index.
                else
                {
                    //Get the tick at the index.
                    uint lrTick = reconcilesHistory[(int)historyIndex].Tick;
                    /* Since we store reconcile data every tick moving ahead a set number of ticks
                     * should usually match up to the reconcile tick. There are exceptions where the tick
                     * used to locally create the reconcile was for non owner, so using the server tick,
                     * and there is a slight misalignment in the server tick. This is not unusual as the
                     * client corrects it's tick timing regularly, but such an alignment could make this not line up. */
                    /* If the history tick does not match the reconcile tick try to find
                     * the correct history tick. This should rarely happen but since these reconciles
                     * are created locally and client timing can vary slightly it's still possible. */
                    if (lrTick != reconcileTick)
                    {
                        /* Get the difference between what tick is stored vs reconcile tick.
                         * Adjust the index based on this difference. */
                        long tickDifference = ((long)reconcileTick - (long)lrTick);

                        /* Add difference onto history index and again validate that it
                         * is in range of the collection. */
                        historyIndex += tickDifference;
                        //Invalid.
                        if (!IsHistoryIndexValid((int)historyIndex))
                        {
                            /* This shouldn't ever happen. Something went very wrong if here.
                             * When this does happen clear out the entire history collection
                             * and start over. */
                            ClearReconcileHistory(reconcilesHistory);
                            //Unset index.
                            historyIndex = unsetHistoryIndex;
                        }
                    }

                    //If index is set and behaviour is not reconciling then apply data.
                    if (!isBehaviourReconciling && historyIndex != unsetHistoryIndex)
                    {
                        LocalReconcile<T> localReconcile = reconcilesHistory[(int)historyIndex];
                        //Before disposing get the writer and call reconcile reader so it's parsed.
                        PooledWriter reconcileWritten = localReconcile.Writer;
                        /* Although this is actually from the local client the datasource is being set to server since server
                         * is what typically sends reconciles. */
                        PooledReader reader = ReaderPool.Retrieve(reconcileWritten.GetArraySegment(), _networkObjectCache.NetworkManager, Reader.DataSource.Server);
                        data = Reconcile_Reader_Local<T>(localReconcile.Tick, reader);
                        ReaderPool.Store(reader);
                    }
                }
            }

            //Returns if a history index can be within history collection.
            bool IsHistoryIndexValid(int index) => (index >= 0 && (index < reconcilesHistory.Count));

            //Dispose of old reconcile histories.
            if (historyIndex != unsetHistoryIndex)
            {
                int index = (int)historyIndex;
                //If here everything is good, remove up to used index.
                for (int i = 0; i < index; i++)
                    reconcilesHistory[i].Dispose();

                reconcilesHistory.RemoveRange(true, (int)historyIndex);
            }

            //If does not have data still then exit method.
            if (!IsBehaviourReconciling)
                return;

            //Set on the networkObject that a reconcile can now occur.
            _networkObjectCache.IsObjectReconciling = true;

            uint dataTick = data.GetTick();
            _lastReconcileTick = dataTick;

            if (replicatesHistory.Count > 0)
            {
                /* Remove replicates up to reconcile. Since the reconcile
                 * is the state after a replicate for it's tick we no longer
                 * need any replicates prior. */
                //Find the closest entry which can be removed.
                int removeCount = 0;
                //A few quick tests.
                if (replicatesHistory.Count > 0)
                {
                    /* If the last entry in history is less or equal
                     * to datatick then all histories need to be removed
                     * as reconcile is beyond them. */
                    if (replicatesHistory[^1].Data.GetTick() <= dataTick)
                    {
                        removeCount = replicatesHistory.Count;
                    }
                    //Somewhere in between. Find what to remove up to.
                    else
                    {
                        for (int i = 0; i < replicatesHistory.Count; i++)
                        {
                            uint entryTick = replicatesHistory[i].Data.GetTick();
                            /* Soon as an entry beyond dataTick is
                             * found remove up to that entry. */
                            if (entryTick > dataTick)
                            {
                                removeCount = i;
                                break;
                            }
                        }
                    }
                }

                for (int i = 0; i < removeCount; i++)
                    replicatesHistory[i].Dispose();
                replicatesHistory.RemoveRange(true, removeCount);
            }

            //Call reconcile user logic.
            reconcileDel?.Invoke(data, Channel.Reliable);
        }

        internal void Reconcile_Client_End()
        {
            IsBehaviourReconciling = false;
        }

        /// <summary>
        /// Disposes and clears LocalReconciles.
        /// </summary>
        private void ClearReconcileHistory<T>(RingBuffer<LocalReconcile<T>> reconcilesHistory) where T : IReconcileData
        {
            foreach (LocalReconcile<T> localReconcile in reconcilesHistory)
                localReconcile.Dispose();

            reconcilesHistory.Clear();
        }

        /// <summary>
        /// Reads a reconcile from the server.
        /// </summary>
        public void Reconcile_Reader<T>(PooledReader reader, ref T lastReconcileData) where T : IReconcileData
        {
            uint tick = (IsOwner) ? PredictionManager.ClientStateTick : PredictionManager.ServerStateTick;
//#if !FISHNET_STABLE_SYNCTYPES
#if DO_NOT_USE
            T newData = reader.ReadDeltaReconcile(lastReconciledata);
#else
            T newData = reader.ReadReconcile<T>();
#endif
            //Do not process if an old state.
            if (tick < _lastReadReconcileRemoteTick)
                return;

            lastReconcileData = newData;
            lastReconcileData.SetTick(tick);

            IsBehaviourReconciling = true;
            _networkObjectCache.IsObjectReconciling = true;
            _lastReadReconcileRemoteTick = tick;
        }

        /// <summary>
        /// Reads a local reconcile from the client.
        /// </summary>
        public T Reconcile_Reader_Local<T>(uint tick, PooledReader reader) where T : IReconcileData
        {
            reader.NetworkManager = _networkObjectCache.NetworkManager;
            T newData = reader.ReadReconcile<T>();
            newData.SetTick(tick);

            IsBehaviourReconciling = true;

            return newData;
        }

        /// <summary>
        /// Sets the last tick this NetworkBehaviour replicated with.
        /// </summary>
        /// <param name="setUnordered">True to set unordered value, false to set ordered.</param>
        private void SetReplicateTick(uint value, bool createdReplicate)
        {
            _lastOrderedReplicatedTick = value;
            _networkObjectCache.SetReplicateTick(value, createdReplicate);
        }
    }
}