context: using System.Collections;
  using System.Collections.Generic;
  using JetBrains.Annotations;
  
  namespace Mirror
  {
      public class SyncIDictionary<TKey, TValue> : IDictionary<TKey, TValue>, SyncObject, IReadOnlyDictionary<TKey, TValue>
      {
          public delegate void SyncDictionaryChanged(Operation op, TKey key, TValue item);
  
          protected readonly IDictionary<TKey, TValue> objects;
  
          public int Count => objects.Count;
          public bool IsReadOnly { get; private set; }
          public event SyncDictionaryChanged Callback;
  
          public enum Operation : byte
          {
              OP_ADD,
              OP_CLEAR,
              OP_REMOVE,
              OP_SET
          }
  
          struct Change
          {
              internal Operation operation;
              internal TKey key;
              internal TValue item;
          }
  
          readonly List<Change> changes = new List<Change>();
          // how many changes we need to ignore
          // this is needed because when we initialize the list,
          // we might later receive changes that have already been applied
          // so we need to skip them
          int changesAhead;
  
          public void Reset()
          {
              IsReadOnly = false;
              changes.Clear();
              changesAhead = 0;
              objects.Clear();
          }
  
          public bool IsDirty => changes.Count > 0;
  
          public ICollection<TKey> Keys => objects.Keys;
  
          public ICollection<TValue> Values => objects.Values;
  
          IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys => objects.Keys;
  
          IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values => objects.Values;
  
          // throw away all the changes
          // this should be called after a successful sync
          public void Flush() => changes.Clear();
  
          public SyncIDictionary(IDictionary<TKey, TValue> objects)
          {
              this.objects = objects;
          }
  
          void AddOperation(Operation op, TKey key, TValue item)
          {
              if (IsReadOnly)
              {
                  throw new System.InvalidOperationException("SyncDictionaries can only be modified by the server");
              }
  
              Change change = new Change
              {
                  operation = op,
                  key = key,
                  item = item
              };
  
              changes.Add(change);
  
              Callback?.Invoke(op, key, item);
          }
  
          public void OnSerializeAll(NetworkWriter writer)
          {
              // if init, write the full list content
              writer.WriteUInt((uint)objects.Count);
  
              foreach (KeyValuePair<TKey, TValue> syncItem in objects)
              {
                  writer.Write(syncItem.Key);
                  writer.Write(syncItem.Value);
              }
  
              // all changes have been applied already
              // thus the client will need to skip all the pending changes
              // or they would be applied again.
              // So we write how many changes are pending
              writer.WriteUInt((uint)changes.Count);
          }
  
          public void OnSerializeDelta(NetworkWriter writer)
          {
              // write all the queued up changes
              writer.WriteUInt((uint)changes.Count);
  
              for (int i = 0; i < changes.Count; i++)
              {
                  Change change = changes[i];
                  writer.WriteByte((byte)change.operation);
  
                  switch (change.operation)
                  {
                      case Operation.OP_ADD:
                      case Operation.OP_REMOVE:
                      case Operation.OP_SET:
                          writer.Write(change.key);
                          writer.Write(change.item);
                          break;
                      case Operation.OP_CLEAR:
                          break;
                  }
              }
          }
  
          public void OnDeserializeAll(NetworkReader reader)
          {
              // This list can now only be modified by synchronization
              IsReadOnly = true;
  
              // if init,  write the full list content
              int count = (int)reader.ReadUInt();
  
              objects.Clear();
              changes.Clear();
  
              for (int i = 0; i < count; i++)
              {
                  TKey key = reader.Read<TKey>();
                  TValue obj = reader.Read<TValue>();
                  objects.Add(key, obj);
              }
  
              // We will need to skip all these changes
              // the next time the list is synchronized
              // because they have already been applied
              changesAhead = (int)reader.ReadUInt();
          }
  
          public void OnDeserializeDelta(NetworkReader reader)
          {
              // This list can now only be modified by synchronization
              IsReadOnly = true;
  
              int changesCount = (int)reader.ReadUInt();
  
              for (int i = 0; i < changesCount; i++)
              {
                  Operation operation = (Operation)reader.ReadByte();
  
                  // apply the operation only if it is a new change
                  // that we have not applied yet
                  bool apply = changesAhead == 0;
                  TKey key = default;
                  TValue item = default;
  
                  switch (operation)
                  {
                      case Operation.OP_ADD:
                      case Operation.OP_SET:
                          key = reader.Read<TKey>();
                          item = reader.Read<TValue>();
                          if (apply)
                          {
                              objects[key] = item;
                          }
                          break;
  
                      case Operation.OP_CLEAR:
                          if (apply)
                          {
                              objects.Clear();
                          }
                          break;
  
                      case Operation.OP_REMOVE:
                          key = reader.Read<TKey>();
                          item = reader.Read<TValue>();
                          if (apply)
                          {
                              objects.Remove(key);
                          }
                          break;
                  }
  
                  if (apply)
                  {
                      Callback?.Invoke(operation, key, item);
                  }
                  // we just skipped this change
                  else
                  {
                      changesAhead--;
                  }
              }
          }
  
          public void Clear()
          {
              objects.Clear();
              AddOperation(Operation.OP_CLEAR, default, default);
          }
  
          public bool ContainsKey(TKey key) => objects.ContainsKey(key);
  
          public bool Remove(TKey key)
          {
              if (objects.TryGetValue(key, out TValue item) && objects.Remove(key))
              {
                  AddOperation(Operation.OP_REMOVE, key, item);
                  return true;
              }
              return false;
          }
  
          public TValue this[TKey i]
          {
              get => objects[i];
              set
              {
                  if (ContainsKey(i))
                  {
                      objects[i] = value;
                      AddOperation(Operation.OP_SET, i, value);
                  }
                  else
                  {
                      objects[i] = value;
                      AddOperation(Operation.OP_ADD, i, value);
                  }
              }
          }
  
          public bool TryGetValue(TKey key, out TValue value) => objects.TryGetValue(key, out value);
  
          public void Add(TKey key, TValue value)
          {
              objects.Add(key, value);
              AddOperation(Operation.OP_ADD, key, value);
          }
  
          public void Add(KeyValuePair<TKey, TValue> item) => Add(item.Key, item.Value);
  
          public bool Contains(KeyValuePair<TKey, TValue> item)
          {
              return TryGetValue(item.Key, out TValue val) && EqualityComparer<TValue>.Default.Equals(val, item.Value);
          }
  
          public void CopyTo([NotNull] KeyValuePair<TKey, TValue>[] array, int arrayIndex)
          {
              if (arrayIndex < 0 || arrayIndex > array.Length)
              {
                  throw new System.ArgumentOutOfRangeException(nameof(arrayIndex), "Array Index Out of Range");
              }
              if (array.Length - arrayIndex < Count)
              {
                  throw new System.ArgumentException("The number of items in the SyncDictionary is greater than the available space from arrayIndex to the end of the destination array");
              }
  
              int i = arrayIndex;
  
gt:             foreach (KeyValuePair<TKey, TValue> item in objects)