using FishNet.CodeGenerating.Extension;
using FishNet.CodeGenerating.Helping;
using FishNet.CodeGenerating.Helping.Extension;
using FishNet.Configuring;
using FishNet.Object;
using FishNet.Object.Synchronizing;
using FishNet.Object.Synchronizing.Internal;
using FishNet.Serializing;
using FishNet.Transporting;
using MonoFN.Cecil;
using MonoFN.Cecil.Cil;
using MonoFN.Cecil.Rocks;
using MonoFN.Collections.Generic;
using System.Collections.Generic;
using UnityEngine;
namespace FishNet.CodeGenerating.Processing
{
    internal class NetworkBehaviourSyncProcessor : CodegenBase
    {
        #region Reflection references.
        private TypeDefinition SyncBase_TypeDef;
        #endregion
        #region Private.
        /// 
        /// Last instruction to read a sync type.
        ///  
        private Instruction _lastReadInstruction;
        /// 
        /// Sync objects, such as get and set, created during this process. Used to skip modifying created methods.
        ///  
        private List _createdSyncTypeMethodDefinitions = new List();
        /// 
        /// ReadSyncVar methods which have had their base call already made.
        ///  
        private HashSet _baseCalledReadSyncVars = new HashSet();
        #endregion
        #region Const.
        private const string SYNCVAR_PREFIX = "syncVar___";
        private const string ACCESSOR_PREFIX = "sync___";
        private const string SETREGISTERED_METHOD_NAME = "SetRegistered";
        private const string INITIALIZEINSTANCE_METHOD_NAME = "InitializeInstance";
        private const string GETSERIALIZEDTYPE_METHOD_NAME = "GetSerializedType";
        private const string SENDRATE_NAME = "SendRate";
        private const string READPERMISSIONS_NAME = "ReadPermissions";
        #endregion
        public override bool ImportReferences()
        {
            System.Type syncBaseType = typeof(SyncBase);
            SyncBase_TypeDef = base.ImportReference(syncBaseType).Resolve();
            return true;
        }
        /// 
        /// Processes SyncVars and Objects.
        ///  
        /// 
        /// Gets number of SyncTypes by checking for SyncVar/Object attributes. This does not perform error checking.
        ///  
        /// 
        /// Replaces GetSets for methods which may use a SyncType.
        ///  
        internal bool ReplaceGetSets(TypeDefinition typeDef, List<(SyncType, ProcessedSync)> allProcessedSyncs)
        {
            bool modified = false;
            List modifiableMethods = GetModifiableMethods(typeDef);
            modified |= ReplaceGetSetDirties(modifiableMethods, allProcessedSyncs);
            return modified;
        }
        /// 
        /// Gets SyncType fieldDef is.
        ///  
        /// ())
                        base.LogError($"{fieldDef.Name} within {fieldDef.DeclaringType.Name} is a SyncType but is missing the [SyncVar] or [SyncObject] attribute.");
                    return SyncType.Unset;
                }
                /* If the attribute is not [SyncObject] then the attribute
                 * is [SyncVar]. Only checks that need to be made is to make sure
                 * the user is not using a SyncVar attribute when they should be using a SyncObject attribute. */
                if (syncAttribute != null && !syncObject)
                {
                    //Make sure syncvar attribute isnt on a sync object.
                    if (GetSyncObjectSyncType(syncAttribute) != SyncType.Unset)
                    {
                        base.LogError($"{fieldDef.Name} within {fieldDef.DeclaringType.Name} uses a [SyncVar] attribute but should be using [SyncObject].");
                        return SyncType.Unset;
                    }
                    else
                        return SyncType.Variable;
                }
                /* If here could be syncObject
                 * or attribute might be null. */
                if (fieldDef.FieldType.CachedResolve(base.Session).ImplementsInterfaceRecursive(base.Session))
                    return GetSyncObjectSyncType(syncAttribute);
                SyncType GetSyncObjectSyncType(CustomAttribute sa)
                {
                    //If attribute is null then throw error.
                    if (sa == null)
                    {
                        base.LogError($"{fieldDef.Name} within {fieldDef.DeclaringType.Name} is a SyncType but [SyncObject] attribute was not found.");
                        return SyncType.Unset;
                    }
                    if (fieldDef.FieldType.Name == base.GetClass().SyncList_Name)
                    {
                        return SyncType.List;
                    }
                    else if (fieldDef.FieldType.Name == base.GetClass().SyncDictionary_Name)
                    {
                        return SyncType.Dictionary;
                    }
                    else if (fieldDef.FieldType.Name == base.GetClass().SyncHashSet_Name)
                    {
                        return SyncType.HashSet;
                    }
                    //Custom types must also implement ICustomSync.
                    else if (fieldDef.FieldType.CachedResolve(base.Session).ImplementsInterfaceRecursive(base.Session))
                    {
                        return SyncType.Custom;
                    }
                    else
                    {
                        return SyncType.Unset;
                    }
                }
                //Fall through.
                if (syncAttribute != null)
                    base.LogError($"SyncObject attribute found on {fieldDef.Name} within {fieldDef.DeclaringType.Name} but type {fieldDef.FieldType.Name} does not inherit from SyncBase, or if a custom type does not implement ICustomSync.");
                return SyncType.Unset;
            }
        }
        /// 
        /// Tries to create a SyncList.
        ///  
        private bool TryCreateCustom(uint syncTypeCount, List<(SyncType, ProcessedSync)> allProcessedSyncs, TypeDefinition typeDef, FieldDefinition originalFieldDef, CustomAttribute syncAttribute)
        {
            //Get the serialized type.
            MethodDefinition getSerialziedTypeMd = originalFieldDef.FieldType.CachedResolve(base.Session).GetMethod(GETSERIALIZEDTYPE_METHOD_NAME);
            MethodReference getSerialziedTypeMr = base.ImportReference(getSerialziedTypeMd);
            Collection instructions = getSerialziedTypeMr.CachedResolve(base.Session).Body.Instructions;
            bool canSerialize = false;
            TypeReference serializedDataTypeRef = null;
            /* If the user is returning null then
             * they are indicating a custom serializer does not
             * have to be implemented. */
            if (instructions.Count == 2 && instructions[0].OpCode == OpCodes.Ldnull && instructions[1].OpCode == OpCodes.Ret)
            {
                canSerialize = true;
            }
            //If not returning null then make a serializer for return type.
            else
            {
                foreach (Instruction item in instructions)
                {
                    //This token references the type.
                    if (item.OpCode == OpCodes.Ldtoken)
                    {
                        TypeReference importedTr = null;
                        if (item.Operand is TypeDefinition td)
                            importedTr = base.ImportReference(td);
                        else if (item.Operand is TypeReference tr)
                            importedTr = base.ImportReference(tr);
                        if (importedTr != null)
                        {
                            serializedDataTypeRef = importedTr;
                            canSerialize = base.GetClass().HasSerializerAndDeserializer(serializedDataTypeRef, true);
                        }
                    }
                }
            }
            //Wasn't able to determine serialized type, or create it.
            if (!canSerialize)
            {
                base.LogError($"Custom SyncObject {originalFieldDef.Name} data type {serializedDataTypeRef.FullName} does not support serialization. Use a supported type or create a custom serializer.");
                return false;
            }
            bool result = InitializeCustom(syncTypeCount, typeDef, originalFieldDef, syncAttribute);
            if (result)
                allProcessedSyncs.Add((SyncType.Custom, null));
            return result;
        }
        /// 
        /// Tries to create a SyncList.
        ///  
        private bool TryCreateSyncList_SyncHashSet(uint syncTypeCount, List<(SyncType, ProcessedSync)> allProcessedSyncs, TypeDefinition typeDef, FieldDefinition originalFieldDef, CustomAttribute syncAttribute, SyncType syncType)
        {
            //Import fieldType to module.
            TypeReference fieldTypeTr = base.ImportReference(originalFieldDef.FieldType);
            //Make sure type can be serialized.
            GenericInstanceType tmpGenerinstanceType = fieldTypeTr as GenericInstanceType;
            //this returns the correct data type, eg SyncList would return int.
            TypeReference dataTypeRef = base.ImportReference(tmpGenerinstanceType.GenericArguments[0]);
            bool canSerialize = base.GetClass().HasSerializerAndDeserializer(dataTypeRef, true);
            if (!canSerialize)
            {
                base.LogError($"SyncObject {originalFieldDef.Name} data type {dataTypeRef.FullName} does not support serialization. Use a supported type or create a custom serializer.");
                return false;
            }
            bool result = InitializeSyncList_SyncHashSet(syncTypeCount, typeDef, originalFieldDef, syncAttribute);
            if (result)
                allProcessedSyncs.Add((syncType, null));
            return result;
        }
        /// 
        /// Tries to create a SyncDictionary.
        ///  
        private bool TryCreateSyncDictionary(uint syncTypeCount, List<(SyncType, ProcessedSync)> allProcessedSyncs, TypeDefinition typeDef, FieldDefinition originalFieldDef, CustomAttribute syncAttribute)
        {
            //Make sure type can be serialized.
            GenericInstanceType tmpGenerinstanceType = originalFieldDef.FieldType as GenericInstanceType;
            //this returns the correct data type, eg SyncList would return int.
            TypeReference keyTypeRef = tmpGenerinstanceType.GenericArguments[0];
            TypeReference valueTypeRef = tmpGenerinstanceType.GenericArguments[1];
            bool canSerialize;
            //Check key serializer.
            canSerialize = base.GetClass().HasSerializerAndDeserializer(keyTypeRef, true);
            if (!canSerialize)
            {
                base.LogError($"SyncObject {originalFieldDef.Name} key type {keyTypeRef.FullName} does not support serialization. Use a supported type or create a custom serializer.");
                return false;
            }
            //Check value serializer.
            canSerialize = base.GetClass().HasSerializerAndDeserializer(valueTypeRef, true);
            if (!canSerialize)
            {
                base.LogError($"SyncObject {originalFieldDef.Name} value type {valueTypeRef.FullName} does not support serialization. Use a supported type or create a custom serializer.");
                return false;
            }
            bool result = InitializeSyncDictionary(syncTypeCount, typeDef, originalFieldDef, syncAttribute);
            if (result)
                allProcessedSyncs.Add((SyncType.Dictionary, null));
            return result;
        }
        /// 
        /// Tries to create a SyncVar.
        ///  
        private bool TryCreateSyncVar(uint syncCount, List<(SyncType, ProcessedSync)> allProcessedSyncs, TypeDefinition typeDef, FieldDefinition fieldDef, CustomAttribute syncAttribute)
        {
            bool canSerialize = base.GetClass().HasSerializerAndDeserializer(fieldDef.FieldType, true);
            if (!canSerialize)
            {
                base.LogError($"SyncVar {fieldDef.FullName} field type {fieldDef.FieldType.FullName} does not support serialization. Use a supported type or create a custom serializer.");
                return false;
            }
            if (base.Module != typeDef.Module)
            {
                //Only display warning if field is exposed.
                if (!fieldDef.Attributes.HasFlag(FieldAttributes.Private))
                    base.Session.DifferentAssemblySyncVars.Add(fieldDef);
                return false;
            }
            FieldDefinition syncVarFd;
            MethodReference accessorSetValueMr;
            MethodReference accessorGetValueMr;
            bool created = CreateSyncVar(syncCount, typeDef, fieldDef, syncAttribute, out syncVarFd, out accessorSetValueMr, out accessorGetValueMr);
            if (created)
            {
                FieldReference originalFr = base.ImportReference(fieldDef);
                allProcessedSyncs.Add((SyncType.Variable, new ProcessedSync(originalFr, syncVarFd, accessorSetValueMr, accessorGetValueMr)));
            }
            return created;
        }
        /// 
        /// Returns if fieldDef has a SyncType attribute. No error checking is performed.
        ///  
        /// ().IsSyncVarAttribute(customAttribute.AttributeType.FullName))
                    return true;
                else if (base.GetClass().IsSyncObjectAttribute(customAttribute.AttributeType.FullName))
                    return true;
            }
            return false;
        }
        /// 
        /// Returns the syncvar attribute on a method, if one exist. Otherwise returns null.
        ///  
        /// ().IsSyncVarAttribute(customAttribute.AttributeType.FullName))
                    syncObject = false;
                else if (base.GetClass().IsSyncObjectAttribute(customAttribute.AttributeType.FullName))
                    syncObject = true;
                else
                    continue;
                //A syncvar attribute already exist.
                if (foundAttribute != null)
                {
                    base.LogError($"{fieldDef.Name} cannot have multiple SyncType attributes.");
                    error = true;
                }
                //Static.
                if (fieldDef.IsStatic)
                {
                    base.LogError($"{fieldDef.Name} SyncType cannot be static.");
                    error = true;
                }
                //Generic.
                if (fieldDef.FieldType.IsGenericParameter)
                {
                    base.LogError($"{fieldDef.Name} SyncType cannot be be generic.");
                    error = true;
                }
                //SyncObject readonly check.
                if (syncObject && !fieldDef.Attributes.HasFlag(FieldAttributes.InitOnly))
                {
                    /* If missing readonly see if the user specified
                     * they want the object to be serialized. */
                    bool requireReadOnly = customAttribute.GetField(nameof(SyncObjectAttribute.RequireReadOnly), true);
                    if (requireReadOnly)
                        base.LogError($"{fieldDef.Name} SyncObject must be readonly.");
                    error = true;
                }
                //If all checks passed.
                if (!error)
                    foundAttribute = customAttribute;
            }
            //If an error occurred then reset results.
            if (error)
                foundAttribute = null;
            return foundAttribute;
        }
        /// 
        /// Creates a syncVar class for the user's syncvar.
        ///  
        /// 
        /// Creates or gets a SyncType class for originalFieldDef.
        ///  
        /// ().GetCreatedSyncVar(originalFieldDef, true);
            if (createdSyncVar == null)
                return null;
            originalFieldDef.Attributes &= ~FieldAttributes.Private;
            originalFieldDef.Attributes |= FieldAttributes.Public;
            FieldDefinition createdFieldDef = new FieldDefinition($"{SYNCVAR_PREFIX}{originalFieldDef.Name}", originalFieldDef.Attributes, createdSyncVar.SyncVarGit);
            if (createdFieldDef == null)
            {
                base.LogError($"Could not create field for Sync type {originalFieldDef.FieldType.FullName}, name of {originalFieldDef.Name}.");
                return null;
            }
            typeDef.Fields.Add(createdFieldDef);
            return createdFieldDef;
        }
        /// 
        /// Validates and gets the hook MethodReference for a SyncVar if available.
        ///  
        /// 
        /// Creates accessor for a SyncVar.
        ///  
        /// ().CreateParameter(createdSetMethodDef, originalFd.FieldType, "value");
            ParameterDefinition calledByUserParameterDef = base.GetClass().CreateParameter(createdSetMethodDef, typeof(bool), "asServer");
            processor = createdSetMethodDef.Body.GetILProcessor();
            /* Assign to new value. Do this first because SyncVar calls hook 
             * and value needs to be updated before hook. Only update
             * value if calledByUser(asServer) or (!calledByUser && !base.IsServer).
             * This ensures clientHost will not overwrite server value. */
            Instruction afterChangeFieldInst = processor.Create(OpCodes.Nop);
            Instruction beforeChangeFieldInst = processor.Create(OpCodes.Nop);
            //if (calledByUser || !base.IsServer)
            processor.Emit(OpCodes.Ldarg, calledByUserParameterDef);
            processor.Emit(OpCodes.Brtrue, beforeChangeFieldInst);
            processor.Emit(OpCodes.Ldarg_0); //this.            
            processor.Emit(OpCodes.Call, base.GetClass().IsServer_MethodRef);
            processor.Emit(OpCodes.Brtrue, afterChangeFieldInst);
            //      _originalField = value;
            processor.Append(beforeChangeFieldInst);
            processor.Emit(OpCodes.Ldarg_0); //this.
            processor.Emit(OpCodes.Ldarg, valueParameterDef);
            processor.Emit(OpCodes.Stfld, originalFd);
            processor.Append(afterChangeFieldInst);
            Instruction retInst = processor.Create(OpCodes.Ret);
            if (!Configuration.Configurations.CodeStripping.IsBuilding)
            {
                processor.Emit(OpCodes.Call, base.GetClass().Application_IsPlaying_MethodRef);
                processor.Emit(OpCodes.Brfalse_S, retInst);
            }
            //      SyncVar<>.SetValue(....);
            processor.Emit(OpCodes.Ldarg_0); //this.
            processor.Emit(OpCodes.Ldfld, createdSyncVarFd);
            processor.Emit(OpCodes.Ldarg, valueParameterDef);
            processor.Emit(OpCodes.Ldarg, calledByUserParameterDef);
            processor.Emit(createdSyncVar.SetValueMr.GetCallOpCode(base.Session), createdSyncVar.SetValueMr);
            processor.Append(retInst);
            accessorSetValueMr = base.ImportReference(createdSetMethodDef);
            //Add setter to properties.
            createdPropertyDef.SetMethod = createdSetMethodDef;
            return originalFd;
        }
        /// 
        /// Sets methods used from SyncBase for typeDef.
        ///  
        /// 
        /// Initializes a custom SyncObject.
        ///  
        internal bool InitializeCustom(uint syncCount, TypeDefinition typeDef, FieldDefinition originalFieldDef, CustomAttribute attribute)
        {
            float sendRate = 0.1f;
            WritePermission writePermissions = WritePermission.ServerOnly;
            ReadPermission readPermissions = ReadPermission.Observers;
            Channel channel = Channel.Reliable;
            //If attribute isn't null then override values.
            if (attribute != null)
            {
                sendRate = attribute.GetField(SENDRATE_NAME, -1f);
                writePermissions = WritePermission.ServerOnly;
                readPermissions = attribute.GetField(READPERMISSIONS_NAME, ReadPermission.Observers);
                channel = Channel.Reliable; //attribute.GetField("Channel", Channel.Reliable);
            }
            //Set needed methods from syncbase.
            MethodReference setSyncIndexMr;
            MethodReference initializeInstanceMr;
            if (!SetSyncBaseMethods(originalFieldDef.FieldType.CachedResolve(base.Session), out setSyncIndexMr, out initializeInstanceMr))
                return false;
            MethodDefinition injectionMethodDef;
            ILProcessor processor;
            uint hash = (uint)syncCount;
            List insts = new List();
            /* Initialize with attribute settings. */
            injectionMethodDef = typeDef.GetMethod(NetworkBehaviourProcessor.NETWORKINITIALIZE_EARLY_INTERNAL_NAME);
            processor = injectionMethodDef.Body.GetILProcessor();
            //
            insts.Add(processor.Create(OpCodes.Ldarg_0)); //this.
            insts.Add(processor.Create(OpCodes.Ldfld, originalFieldDef));
            insts.Add(processor.Create(OpCodes.Ldarg_0)); //this again for NetworkBehaviour.
            insts.Add(processor.Create(OpCodes.Ldc_I4, (int)hash));
            insts.Add(processor.Create(OpCodes.Ldc_I4, (int)writePermissions));
            insts.Add(processor.Create(OpCodes.Ldc_I4, (int)readPermissions));
            insts.Add(processor.Create(OpCodes.Ldc_R4, sendRate));
            insts.Add(processor.Create(OpCodes.Ldc_I4, (int)channel));
            insts.Add(processor.Create(OpCodes.Ldc_I4_1)); //true for syncObject.
            insts.Add(processor.Create(OpCodes.Call, initializeInstanceMr));
            processor.InsertFirst(insts);
            insts.Clear();
            /* Set NetworkBehaviour and SyncIndex to use. */
            injectionMethodDef = typeDef.GetMethod(NetworkBehaviourProcessor.NETWORKINITIALIZE_LATE_INTERNAL_NAME);
            processor = injectionMethodDef.Body.GetILProcessor();
            //
            insts.Add(processor.Create(OpCodes.Ldarg_0)); //this.
            insts.Add(processor.Create(OpCodes.Ldfld, originalFieldDef));
            insts.Add(processor.Create(setSyncIndexMr.GetCallOpCode(base.Session), setSyncIndexMr));
            processor.InsertLast(insts);
            return true;
        }
        /// 
        /// Initializes a SyncList.
        ///  
        internal bool InitializeSyncList_SyncHashSet(uint syncCount, TypeDefinition typeDef, FieldDefinition originalFieldDef, CustomAttribute attribute)
        {
            float sendRate = 0.1f;
            WritePermission writePermissions = WritePermission.ServerOnly;
            ReadPermission readPermissions = ReadPermission.Observers;
            Channel channel = Channel.Reliable;
            //If attribute isn't null then override values.
            if (attribute != null)
            {
                sendRate = attribute.GetField(SENDRATE_NAME, -1f);
                writePermissions = WritePermission.ServerOnly;
                readPermissions = attribute.GetField(READPERMISSIONS_NAME, ReadPermission.Observers);
                channel = Channel.Reliable; //attribute.GetField("Channel", Channel.Reliable);
            }
            //This import shouldn't be needed but cecil is stingy so rather be safe than sorry.
            base.ImportReference(originalFieldDef);
            //Set needed methods from syncbase.
            MethodReference setSyncIndexMr;
            MethodReference initializeInstanceMr;
            if (!SetSyncBaseMethods(originalFieldDef.FieldType.CachedResolve(base.Session), out setSyncIndexMr, out initializeInstanceMr))
                return false;
            MethodDefinition injectionMethodDef;
            ILProcessor processor;
            uint hash = (uint)syncCount;
            List insts = new List();
            /* Initialize with attribute settings. */
            injectionMethodDef = typeDef.GetMethod(NetworkBehaviourProcessor.NETWORKINITIALIZE_EARLY_INTERNAL_NAME);
            processor = injectionMethodDef.Body.GetILProcessor();
            //InitializeInstance.
            insts.Add(processor.Create(OpCodes.Ldarg_0)); //this.
            insts.Add(processor.Create(OpCodes.Ldfld, originalFieldDef));
            insts.Add(processor.Create(OpCodes.Ldarg_0)); //this again for NetworkBehaviour.
            insts.Add(processor.Create(OpCodes.Ldc_I4, (int)hash));
            insts.Add(processor.Create(OpCodes.Ldc_I4, (int)writePermissions));
            insts.Add(processor.Create(OpCodes.Ldc_I4, (int)readPermissions));
            insts.Add(processor.Create(OpCodes.Ldc_R4, sendRate));
            insts.Add(processor.Create(OpCodes.Ldc_I4, (int)channel));
            insts.Add(processor.Create(OpCodes.Ldc_I4_1)); //true for syncObject.
            insts.Add(processor.Create(OpCodes.Call, initializeInstanceMr));
            processor.InsertFirst(insts);
            insts.Clear();
            /* Set NetworkBehaviour and SyncIndex to use. */
            injectionMethodDef = typeDef.GetMethod(NetworkBehaviourProcessor.NETWORKINITIALIZE_LATE_INTERNAL_NAME);
            processor = injectionMethodDef.Body.GetILProcessor();
            insts.Add(processor.Create(OpCodes.Ldarg_0)); //this.
            insts.Add(processor.Create(OpCodes.Ldfld, originalFieldDef));
            insts.Add(processor.Create(setSyncIndexMr.GetCallOpCode(base.Session), setSyncIndexMr));
            processor.InsertLast(insts);
            return true;
        }
        /// 
        /// Initializes a SyncDictionary.
        ///  
        internal bool InitializeSyncDictionary(uint syncCount, TypeDefinition typeDef, FieldDefinition originalFieldDef, CustomAttribute attribute)
        {
            float sendRate = 0.1f;
            WritePermission writePermissions = WritePermission.ServerOnly;
            ReadPermission readPermissions = ReadPermission.Observers;
            Channel channel = Channel.Reliable;
            //If attribute isn't null then override values.
            if (attribute != null)
            {
                sendRate = attribute.GetField(SENDRATE_NAME, -1f);
                writePermissions = WritePermission.ServerOnly;
                readPermissions = attribute.GetField(READPERMISSIONS_NAME, ReadPermission.Observers);
                channel = Channel.Reliable; //attribute.GetField("Channel", Channel.Reliable);
            }
            //This import shouldn't be needed but cecil is stingy so rather be safe than sorry.
            base.ImportReference(originalFieldDef);
            //Set needed methods from syncbase.
            MethodReference setRegisteredMr;
            MethodReference initializeInstanceMr;
            if (!SetSyncBaseMethods(originalFieldDef.FieldType.CachedResolve(base.Session), out setRegisteredMr, out initializeInstanceMr))
                return false;
            MethodDefinition injectionMethodDef = typeDef.GetMethod(NetworkBehaviourProcessor.NETWORKINITIALIZE_EARLY_INTERNAL_NAME);
            ILProcessor processor = injectionMethodDef.Body.GetILProcessor();
            uint hash = (uint)syncCount;
            List insts = new List();
            /* Initialize with attribute settings. */
            insts.Add(processor.Create(OpCodes.Ldarg_0)); //this.
            insts.Add(processor.Create(OpCodes.Ldfld, originalFieldDef));
            insts.Add(processor.Create(OpCodes.Ldarg_0)); //this again for NetworkBehaviour.
            insts.Add(processor.Create(OpCodes.Ldc_I4, (int)hash));
            insts.Add(processor.Create(OpCodes.Ldc_I4, (int)writePermissions));
            insts.Add(processor.Create(OpCodes.Ldc_I4, (int)readPermissions));
            insts.Add(processor.Create(OpCodes.Ldc_R4, sendRate));
            insts.Add(processor.Create(OpCodes.Ldc_I4, (int)channel));
            insts.Add(processor.Create(OpCodes.Ldc_I4_1)); //true for syncObject.
            insts.Add(processor.Create(OpCodes.Call, initializeInstanceMr));
            processor.InsertFirst(insts);
            insts.Clear();
            /* Set NetworkBehaviour and SyncIndex to use. */
            injectionMethodDef = typeDef.GetMethod(NetworkBehaviourProcessor.NETWORKINITIALIZE_LATE_INTERNAL_NAME);
            processor = injectionMethodDef.Body.GetILProcessor();
            insts.Add(processor.Create(OpCodes.Ldarg_0)); //this.
            insts.Add(processor.Create(OpCodes.Ldfld, originalFieldDef));
            insts.Add(processor.Create(setRegisteredMr.GetCallOpCode(base.Session), setRegisteredMr));
            processor.InsertFirst(insts);
            return true;
        }
        /// 
        /// Initializes a SyncVar<>.
        ///  
        internal void InitializeSyncVar(uint syncCount, FieldDefinition createdFd, TypeDefinition typeDef, FieldDefinition originalFd, CustomAttribute attribute, CreatedSyncVar createdSyncVar)
        {
            GeneralHelper gh = base.GetClass();
            //Get all possible attributes.
            float sendRate = attribute.GetField(SENDRATE_NAME, -1f);
            WritePermission writePermissions = WritePermission.ServerOnly;
            ReadPermission readPermissions = attribute.GetField(READPERMISSIONS_NAME, ReadPermission.Observers);
            Channel channel = attribute.GetField("Channel", Channel.Reliable);
            MethodDefinition injectionMethodDef = typeDef.GetMethod(NetworkBehaviourProcessor.NETWORKINITIALIZE_EARLY_INTERNAL_NAME);
            ILProcessor processor = injectionMethodDef.Body.GetILProcessor();
            uint hash = (uint)syncCount;
            List insts = new List();
            //Initialize fieldDef with values from attribute.
            insts.Add(processor.Create(OpCodes.Ldarg_0)); //this.
            insts.Add(processor.Create(OpCodes.Ldarg_0)); //this again for NetworkBehaviour.
            insts.Add(processor.Create(OpCodes.Ldc_I4, (int)hash));
            insts.Add(processor.Create(OpCodes.Ldc_I4, (int)writePermissions));
            insts.Add(processor.Create(OpCodes.Ldc_I4, (int)readPermissions));
            insts.Add(processor.Create(OpCodes.Ldc_R4, sendRate));
            insts.Add(processor.Create(OpCodes.Ldc_I4, (int)channel));
            insts.Add(processor.Create(OpCodes.Ldarg_0)); //this.
            insts.Add(processor.Create(OpCodes.Ldfld, originalFd.MakeHostGenericIfNeeded(base.Session))); //initial value.
            insts.Add(processor.Create(OpCodes.Newobj, createdSyncVar.ConstructorMr));
            insts.Add(processor.Create(OpCodes.Stfld, createdFd.MakeHostGenericIfNeeded(base.Session)));
            //If there is a hook method.
            if (createdSyncVar.HookMr != null)
            {
                //SyncVar.add_OnChanged (event).
                TypeDefinition svTd = base.GetClass().SyncVar_TypeRef.CachedResolve(base.Session);
                GenericInstanceType svGit = svTd.MakeGenericInstanceType(new TypeReference[] { originalFd.FieldType });
                MethodDefinition addMd = svTd.GetMethod("add_OnChange");
                MethodReference syncVarAddMr = addMd.MakeHostInstanceGeneric(base.Session, svGit);
                //Action constructor.
                GenericInstanceType actionGit = gh.ActionT3_TypeRef.MakeGenericInstanceType(
                    originalFd.FieldType, originalFd.FieldType,
                    base.GetClass().GetTypeReference(typeof(bool)));
                MethodReference gitActionCtorMr = gh.ActionT3Constructor_MethodRef.MakeHostInstanceGeneric(base.Session, actionGit);
                //      syncVar___field.OnChanged += UserHookMethod;
                insts.Add(processor.Create(OpCodes.Ldarg_0));
                insts.Add(processor.Create(OpCodes.Ldfld, createdFd));
                insts.Add(processor.Create(OpCodes.Ldarg_0));
                //Load the callback function.
                MethodDefinition hookMd = createdSyncVar.HookMr.CachedResolve(base.Session);
                OpCode ldOpCode;
                if (hookMd.IsVirtual)
                {
                    insts.Add(processor.Create(OpCodes.Dup));
                    ldOpCode = OpCodes.Ldvirtftn;
                }
                else
                {
                    ldOpCode = OpCodes.Ldftn;
                }
                insts.Add(processor.Create(ldOpCode, hookMd));
                insts.Add(processor.Create(OpCodes.Newobj, gitActionCtorMr));
                insts.Add(processor.Create(syncVarAddMr.GetCallOpCode(base.Session), syncVarAddMr));
            }
            processor.InsertFirst(insts);
            insts.Clear();
            /* Set NetworkBehaviour and SyncIndex to use. */
            injectionMethodDef = typeDef.GetMethod(NetworkBehaviourProcessor.NETWORKINITIALIZE_LATE_INTERNAL_NAME);
            processor = injectionMethodDef.Body.GetILProcessor();
            //Set NB and SyncIndex to SyncVar<>.
            insts.Add(processor.Create(OpCodes.Ldarg_0)); //this.
            insts.Add(processor.Create(OpCodes.Ldfld, createdFd));
            insts.Add(processor.Create(createdSyncVar.SetSyncIndexMr.GetCallOpCode(base.Session), createdSyncVar.SetSyncIndexMr));
            processor.InsertFirst(insts);
        }
        /// 
        /// Replaces GetSets for methods which may use a SyncType.
        ///  
        ///  modifiableMethods, List<(SyncType, ProcessedSync)> processedSyncs)
        {
            //Build processed syncs into dictionary for quicker loookups.
            Dictionary> processedLookup = new Dictionary>();
            foreach ((SyncType st, ProcessedSync ps) in processedSyncs)
            {
                if (st != SyncType.Variable)
                    continue;
                List result;
                if (!processedLookup.TryGetValue(ps.OriginalFieldRef, out result))
                {
                    result = new List() { ps };
                    processedLookup.Add(ps.OriginalFieldRef, result);
                }
                result.Add(ps);
            }
            bool modified = false;
            foreach (MethodDefinition methodDef in modifiableMethods)
                modified |= ReplaceGetSetDirty(methodDef, processedLookup);
            return modified;
        }
        /// 
        /// Replaces GetSets for a method which may use a SyncType.
        ///  
        /// > processedLookup)
        {
            if (methodDef == null)
            {
                base.LogError($"An object expecting value was null. Please try saving your script again.");
                return false;
            }
            if (methodDef.IsAbstract)
                return false;
            if (_createdSyncTypeMethodDefinitions.Contains(methodDef))
                return false;
            if (methodDef.Name == NetworkBehaviourProcessor.NETWORKINITIALIZE_EARLY_INTERNAL_NAME)
                return false;
            bool modified = false;
            for (int i = 0; i < methodDef.Body.Instructions.Count; i++)
            {
                Instruction inst = methodDef.Body.Instructions[i];
                /* Loading a field. (Getter) */
                if (inst.OpCode == OpCodes.Ldfld && inst.Operand is FieldReference opFieldld)
                {
                    FieldReference resolvedOpField = opFieldld.CachedResolve(base.Session);
                    if (resolvedOpField == null)
                        resolvedOpField = opFieldld.DeclaringType.CachedResolve(base.Session).GetFieldReference(opFieldld.Name, base.Session);
                    modified |= ProcessGetField(methodDef, i, resolvedOpField, processedLookup);
                }
                /* Load address, reference field. */
                else if (inst.OpCode == OpCodes.Ldflda && inst.Operand is FieldReference opFieldlda)
                {
                    FieldReference resolvedOpField = opFieldlda.CachedResolve(base.Session);
                    if (resolvedOpField == null)
                        resolvedOpField = opFieldlda.DeclaringType.CachedResolve(base.Session).GetFieldReference(opFieldlda.Name, base.Session);
                    modified |= ProcessAddressField(methodDef, i, resolvedOpField, processedLookup);
                }
                /* Setting a field. (Setter) */
                else if (inst.OpCode == OpCodes.Stfld && inst.Operand is FieldReference opFieldst)
                {
                    FieldReference resolvedOpField = opFieldst.CachedResolve(base.Session);
                    if (resolvedOpField == null)
                        resolvedOpField = opFieldst.DeclaringType.CachedResolve(base.Session).GetFieldReference(opFieldst.Name, base.Session);
                    modified |= ProcessSetField(methodDef, i, resolvedOpField, processedLookup);
                }
            }
            return modified;
        }
        /// 
        /// Replaces Gets for a method which may use a SyncType.
        ///  
        /// > processedLookup)
        {
            Instruction inst = methodDef.Body.Instructions[instructionIndex];
            //If was a replaced field.
            if (processedLookup.TryGetValue(resolvedOpField, out List psLst))
            {
                ProcessedSync ps = GetProcessedSync(resolvedOpField, psLst);
                if (ps == null)
                    return false;
                //Don't modify the accessor method.
                if (ps.GetMethodRef.CachedResolve(base.Session) == methodDef)
                    return false;
                //Generic type.
                if (resolvedOpField.DeclaringType.IsGenericInstance || resolvedOpField.DeclaringType.HasGenericParameters)
                {
                    FieldReference newField = inst.Operand as FieldReference;
                    GenericInstanceType git = (GenericInstanceType)newField.DeclaringType;
                    MethodReference syncvarGetMr = ps.GetMethodRef.MakeHostInstanceGeneric(base.Session, git);
                    inst.OpCode = syncvarGetMr.GetCallOpCode(base.Session);
                    inst.Operand = syncvarGetMr;
                }
                //Strong type.
                else
                {
                    inst.OpCode = OpCodes.Call;
                    inst.Operand = ps.GetMethodRef;
                }
                return true;
            }
            else
            {
                return false;
            }
        }
        /// 
        /// Replaces Sets for a method which may use a SyncType.
        ///  
        /// > processedLookup)
        {
            Instruction inst = methodDef.Body.Instructions[instructionIndex];
            /* Find any instructions that are jmp/breaking to the one we are modifying.
             * These need to be modified to call changed instruction. */
            HashSet brInstructions = new HashSet();
            foreach (Instruction item in methodDef.Body.Instructions)
            {
                bool canJmp = (item.OpCode == OpCodes.Br || item.OpCode == OpCodes.Brfalse || item.OpCode == OpCodes.Brfalse_S || item.OpCode == OpCodes.Brtrue || item.OpCode == OpCodes.Brtrue_S || item.OpCode == OpCodes.Br_S);
                if (!canJmp)
                    continue;
                if (item.Operand == null)
                    continue;
                if (item.Operand is Instruction jmpInst && jmpInst == inst)
                    brInstructions.Add(item);
            }
            //If was a replaced field.
            if (processedLookup.TryGetValue(resolvedOpField, out List psLst))
            {
                ProcessedSync ps = GetProcessedSync(resolvedOpField, psLst);
                if (ps == null)
                    return false;
                //Don't modify the accessor method.
                if (ps.SetMethodRef.CachedResolve(base.Session) == methodDef)
                    return false;
                ILProcessor processor = methodDef.Body.GetILProcessor();
                //Generic type.
                if (resolvedOpField.DeclaringType.IsGenericInstance || resolvedOpField.DeclaringType.HasGenericParameters)
                {
                    //Pass in true for as server.
                    Instruction boolTrueInst = processor.Create(OpCodes.Ldc_I4_1);
                    methodDef.Body.Instructions.Insert(instructionIndex, boolTrueInst);
                    FieldReference newField = inst.Operand as FieldReference;
                    GenericInstanceType git = (GenericInstanceType)newField.DeclaringType;
                    inst.OpCode = OpCodes.Call;
                    inst.Operand = ps.SetMethodRef.MakeHostInstanceGeneric(base.Session, git);
                }
                //Strong typed.
                else
                {
                    //Pass in true for as server.
                    Instruction boolTrueInst = processor.Create(OpCodes.Ldc_I4_1);
                    methodDef.Body.Instructions.Insert(instructionIndex, boolTrueInst);
                    inst.OpCode = OpCodes.Call;
                    inst.Operand = ps.SetMethodRef;
                }
                /* If any instructions are still pointing
                 * to modified value then they need to be
                 * redirected to the instruction right above it.
                 * This is because the boolTrueInst, to indicate
                 * value is being set as server. */
                foreach (Instruction item in brInstructions)
                {
                    if (item.Operand is Instruction jmpInst && jmpInst == inst)
                    {
                        //Use the same index that was passed in, which is now one before modified instruction.
                        Instruction newInst = methodDef.Body.Instructions[instructionIndex];
                        item.Operand = newInst;
                    }
                }
                return true;
            }
            else
            {
                return false;
            }
        }
        /// 
        /// Replaces address Sets for a method which may use a SyncType.
        ///  
        /// > processedLookup)
        {
            Instruction inst = methodDef.Body.Instructions[instructionIndex];
            //Check if next instruction is Initobj, which would be setting a new instance.
            Instruction nextInstr = inst.Next;
            if (nextInstr.OpCode != OpCodes.Initobj)
                return false;
            //If was a replaced field.
            if (processedLookup.TryGetValue(resolvedOpField, out List psLst))
            {
                ProcessedSync ps = GetProcessedSync(resolvedOpField, psLst);
                if (ps == null)
                    return false;
                //Don't modify the accessor method.
                if (ps.GetMethodRef.CachedResolve(base.Session) == methodDef || ps.SetMethodRef.CachedResolve(base.Session) == methodDef)
                    return false;
                ILProcessor processor = methodDef.Body.GetILProcessor();
                VariableDefinition tmpVariableDef = base.GetClass().CreateVariable(methodDef, resolvedOpField.FieldType);
                processor.InsertBefore(inst, processor.Create(OpCodes.Ldloca, tmpVariableDef));
                processor.InsertBefore(inst, processor.Create(OpCodes.Initobj, resolvedOpField.FieldType));
                processor.InsertBefore(inst, processor.Create(OpCodes.Ldloc, tmpVariableDef));
                Instruction newInstr = processor.Create(OpCodes.Call, ps.SetMethodRef);
                processor.InsertBefore(inst, newInstr);
                /* Pass in true for as server.
                 * The instruction index is 3 past ld. */
                Instruction boolTrueInst = processor.Create(OpCodes.Ldc_I4_1);
                methodDef.Body.Instructions.Insert(instructionIndex + 3, boolTrueInst);
                processor.Remove(inst);
                processor.Remove(nextInstr);
                return true;
            }
            else
            {
                return false;
            }
        }
        /// 
        /// Calls ReadSyncVar going up the hierarchy.
        ///  
        /// ().ReadSyncVar_MethodRef.Name;
            //TypeDef which needs to make the base call.
            MethodDefinition callerMd = null;
            TypeDefinition copyTd = firstTypeDef;
            do
            {
                MethodDefinition readMd;
                readMd = copyTd.GetMethod(readSyncVarName);
                if (readMd != null)
                    callerMd = readMd;
                /* If baseType exist and it's not networkbehaviour
                 * look into calling the ReadSyncVar method. */
                if (copyTd.BaseType != null && copyTd.BaseType.FullName != base.GetClass().FullName)
                {
                    readMd = copyTd.BaseType.CachedResolve(base.Session).GetMethod(readSyncVarName);
                    //Not all classes will have syncvars to read.
                    if (!_baseCalledReadSyncVars.Contains(callerMd) && readMd != null && callerMd != null)
                    {
                        MethodReference baseReadMr = copyTd.GetMethodReferenceInBase(base.Session, readSyncVarName);//  readMd.GetMethodReferenceInBase (base.Session, base.ImportReference(readMd);
                        ILProcessor processor = callerMd.Body.GetILProcessor();
                        ParameterDefinition asServerPd = callerMd.Parameters[2];
                        /* Calls base.ReadSyncVar and if result is true
                         * then exit methods. This is because a true return means the base
                         * was able to process the syncvar. */
                        List baseCallInsts = new List();
                        Instruction skipBaseReturn = processor.Create(OpCodes.Nop);
                        baseCallInsts.Add(processor.Create(OpCodes.Ldarg_0)); //This.
                        baseCallInsts.Add(processor.Create(OpCodes.Ldarg_1)); //PooledReader.
                        baseCallInsts.Add(processor.Create(OpCodes.Ldarg_2)); //Index.
                        baseCallInsts.Add(processor.Create(OpCodes.Ldarg, asServerPd)); //AsServer.
                        baseCallInsts.Add(processor.Create(OpCodes.Call, baseReadMr));
                        baseCallInsts.Add(processor.Create(OpCodes.Brfalse_S, skipBaseReturn));
                        baseCallInsts.Add(processor.Create(OpCodes.Ldc_I4_1));
                        baseCallInsts.Add(processor.Create(OpCodes.Ret));
                        baseCallInsts.Add(skipBaseReturn);
                        processor.InsertFirst(baseCallInsts);
                        _baseCalledReadSyncVars.Add(callerMd);
                    }
                }
                copyTd = TypeDefinitionExtensionsOld.GetNextBaseClassToProcess(copyTd, base.Session);
            } while (copyTd != null);
        }
        /// 
        /// Reads a PooledReader locally then sets value to the SyncVars accessor.
        ///  
        /// ().ReadSyncVar_MethodRef.Name);
            if (readSyncMethodDef == null)
            {
                readSyncMethodDef = new MethodDefinition(base.GetClass().ReadSyncVar_MethodRef.Name,
                (MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Virtual),
                    typeDef.Module.TypeSystem.Void);
                readSyncMethodDef.ReturnType = base.GetClass().GetTypeReference(typeof(bool));
                base.GetClass().CreateParameter(readSyncMethodDef, typeof(PooledReader));
                base.GetClass().CreateParameter(readSyncMethodDef, typeof(uint));
                base.GetClass().CreateParameter(readSyncMethodDef, typeof(bool));
                readSyncMethodDef.Body.InitLocals = true;
                processor = readSyncMethodDef.Body.GetILProcessor();
                //Return false as fall through.
                processor.Emit(OpCodes.Ldc_I4_0);
                processor.Emit(OpCodes.Ret);
                typeDef.Methods.Add(readSyncMethodDef);
            }
            //Already created. 
            else
            {
                processor = readSyncMethodDef.Body.GetILProcessor();
            }
            ParameterDefinition pooledReaderPd = readSyncMethodDef.Parameters[0];
            ParameterDefinition indexPd = readSyncMethodDef.Parameters[1];
            ParameterDefinition asServerPd = readSyncMethodDef.Parameters[2];
            VariableDefinition nextValueVariableDef;
            List readInsts;
            /* Create a nop instruction placed at the first index of the method.
             * All instructions will be added before this, then the nop will be
             * removed afterwards. This ensures the newer instructions will
             * be above the previous. This let's the IL jump to a previously
             * created read instruction when the latest one fails conditions. */
            Instruction nopPlaceHolderInst = processor.Create(OpCodes.Nop);
            readSyncMethodDef.Body.Instructions.Insert(0, nopPlaceHolderInst);
            /* If there was a previously made read then set jmp goal to the first
             * condition for it. Otherwise set it to the last instruction, which would
             * be a ret. Keep in mind if ret has a value we must go back 2 index
             * rather than one. */
            jmpGoalInst = (_lastReadInstruction != null) ? _lastReadInstruction :
                readSyncMethodDef.Body.Instructions[readSyncMethodDef.Body.Instructions.Count - 2];
            //Check index first. if (index != syncIndex) return
            Instruction nextLastReadInstruction = processor.Create(OpCodes.Ldarg, indexPd);
            processor.InsertBefore(jmpGoalInst, nextLastReadInstruction);
            uint hash = (uint)syncIndex;
            processor.InsertBefore(jmpGoalInst, processor.Create(OpCodes.Ldc_I4, (int)hash));
            //processor.InsertBefore(jmpGoalInst, processor.Create(OpCodes.Ldc_I4, syncIndex));
            processor.InsertBefore(jmpGoalInst, processor.Create(OpCodes.Bne_Un, jmpGoalInst));
            //PooledReader.ReadXXXX()
            readInsts = base.GetClass().CreateRead(readSyncMethodDef, pooledReaderPd,
                 originalFieldDef.FieldType, out nextValueVariableDef);
            if (readInsts == null)
                return null;
            //Add each instruction from CreateRead.
            foreach (Instruction i in readInsts)
                processor.InsertBefore(jmpGoalInst, i);
            //Call accessor with new value and passing in asServer.
            processor.InsertBefore(jmpGoalInst, processor.Create(OpCodes.Ldarg_0)); //this.
            processor.InsertBefore(jmpGoalInst, processor.Create(OpCodes.Ldloc, nextValueVariableDef));
            //processor.InsertBefore(jmpGoalInst, processor.Create(OpCodes.Ldc_I4_0));
            processor.InsertBefore(jmpGoalInst, processor.Create(OpCodes.Ldarg, asServerPd));
            processor.InsertBefore(jmpGoalInst, processor.Create(OpCodes.Call, accessorSetMethodRef));
            //Return true when able to process.
            processor.InsertBefore(jmpGoalInst, processor.Create(OpCodes.Ldc_I4_1));
            processor.InsertBefore(jmpGoalInst, processor.Create(OpCodes.Ret));
            _lastReadInstruction = nextLastReadInstruction;
            processor.Remove(nopPlaceHolderInst);
            return readSyncMethodDef;
        }
        /// 
        /// Returns methods which may be modified by code generation.
        ///  
        ///  GetModifiableMethods(TypeDefinition typeDef)
        {
            List results = new List();
            CheckTypeDefinition(typeDef);
            //Have to add nested types because this are where courotines are stored.
            foreach (TypeDefinition nestedTd in typeDef.NestedTypes)
                CheckTypeDefinition(nestedTd);
            void CheckTypeDefinition(TypeDefinition td)
            {
                foreach (MethodDefinition methodDef in td.Methods)
                {
                    if (methodDef.Name == ".cctor")
                        continue;
                    if (methodDef.IsConstructor)
                        continue;
                    if (methodDef.Body == null)
                        continue;
                    results.Add(methodDef);
                }
                foreach (PropertyDefinition propertyDef in td.Properties)
                {
                    if (propertyDef.GetMethod != null)
                        results.Add(propertyDef.GetMethod);
                    if (propertyDef.SetMethod != null)
                        results.Add(propertyDef.SetMethod);
                }
            }
            return results;
        }
        /// 
        /// Returns the ProcessedSync entry for resolvedOpField.
        ///  
        ///  psLst)
        {
            for (int i = 0; i < psLst.Count; i++)
            {
                if (psLst[i].OriginalFieldRef == resolvedOpField)
                    return psLst[i];
            }
            /* Fall through, not found. */
            base.LogError($"Unable to find user referenced field for {resolvedOpField.Name}.");
            return null;
        }
    }
}