using System;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using Multiplayer.API;
using Verse;
using CombatExtended.Loader;
using System.Collections.Generic;

using SyncMethodAttribute = global::CombatExtended.Compatibility.Multiplayer.SyncMethodAttribute;
using SyncFieldAttribute = global::CombatExtended.Compatibility.Multiplayer.SyncFieldAttribute;

namespace CombatExtended.Compatibility.MultiplayerAPI;
public class MultiplayerCompat : IModPart
{
    private static Dictionary<string, ISyncField> syncFieldAttributes;
    public MultiplayerCompat() { }
    public Type GetSettingsType()
    {
        return null;

    }

    public IEnumerable<string> GetCompatList()
    {
        yield break;
    }

    public void PostLoad(ModContentPack content, ISettingsCE _)
    {
        LongEventHandler.QueueLongEvent(() => this.SlowInit(content), "CE_LongEvent_CompatibilityPatches", false, null);
    }
    public void SlowInit(ModContentPack content)
    {
        syncFieldAttributes = new();
        var fields = content.assemblies.loadedAssemblies
                      .SelectMany(a => a.GetTypes())
                      .SelectMany(t => t.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public))
                      .Where(m => m.HasAttribute<SyncFieldAttribute>());

        foreach (var field in fields)
        {
            var syncField = MP.RegisterSyncField(field.DeclaringType, field.Name).SetBufferChanges();
            syncFieldAttributes[field.DeclaringType.FullName + "/" + field.Name] = syncField;
        }

        var methods = content.assemblies.loadedAssemblies
                      .SelectMany(a => a.GetTypes())
                      .SelectMany(t => t.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public))
                      .Where(m => !m.IsAbstract)
                      .Where(m => m.HasAttribute<SyncMethodAttribute>());

        //MP.RegisterSyncMethod(typeof(Building_TurretGunCE), nameof(Building_TurretGunCE.OrderAttack));
        foreach (var method in methods)
        {
            if (!method.TryGetAttribute<SyncMethodAttribute>(out var attribute))
            {
                continue;
            }

            var syncMethod = MP.RegisterSyncMethod(method);

            if (attribute.syncContext != -1)
            {
                syncMethod.SetContext((SyncContext)attribute.syncContext);
            }

            if (attribute.exposeParameters == null)
            {
                continue;
            }

            foreach (var parameter in attribute.exposeParameters)
            {
                syncMethod.ExposeParameter(parameter);
            }
        }

        MP.RegisterAll();

        global::CombatExtended.Compatibility.Multiplayer.registerCallbacks((() => MP.IsInMultiplayer),
                                                                           (() => MP.IsExecutingSyncCommand),
                                                                           (() => MP.IsExecutingSyncCommandIssuedBySelf),
                                                                           SynchronizeField);
    }

    private static bool SynchronizeField(object obj, string field, object val)
    {
        syncFieldAttributes[field].DoSync(obj, val);
        return true;
    }
    /*
      We enable nullable context for the rest of this file, because the Multiplayer API can call these functions with a null thing to synchronize on the reading end.
      When writing, the comp should never be null.  
     */
#nullable enable
    [SyncWorker]
    private static void SyncCompAmmoUser(SyncWorker sync, ref CompAmmoUser? comp)
    {
        if (sync.isWriting)
        {
            var caster = comp!.parent.GetComp<CompEquippable>().PrimaryVerb.Caster;

            // Sync the turret because in that case syncing fails, due to comp.parent.Map being null,
            // which causes it to be inaccessible in MP for general syncing
            if (caster is Building_TurretGunCE turret)
            {
                sync.Write(true);
                sync.Write(turret);
            }
            // Sync the comp itself, as the parent is accessible in MP
            else
            {
                sync.Write(false);
                // Sync using ThingComp worker, not this one
                // Prevents infinite iteration
                sync.Write(comp as ThingComp);
            }
        }
        else
        {
            if (sync.Read<bool>())
            {
                comp = sync.Read<Building_TurretGunCE>().CompAmmo;
            }
            else
            {
                comp = sync.Read<ThingComp>() as CompAmmoUser;
            }
        }
    }

    [SyncWorker]
    private static void SyncCompFireMode(SyncWorker sync, ref CompFireModes? comp)
    {
        if (sync.isWriting)
        {
            var caster = comp!.Caster;

            // Sync the turret because in that case syncing fails, due to comp.parent.Map being null,
            // which causes it to be inaccessible in MP for general syncing
            if (caster is Building_TurretGunCE turret)
            {
                sync.Write(true);
                sync.Write(turret);
            }
            // Sync the comp itself, as the parent is accessible in MP
            else
            {
                sync.Write(false);
                // Sync using ThingComp worker, not this one
                // Prevents infinite iteration
                sync.Write(comp as ThingComp);
            }
        }
        else
        {
            if (sync.Read<bool>())
            {
                comp = sync.Read<Building_TurretGunCE>().CompFireModes;
            }
            else
            {
                comp = sync.Read<ThingComp>() as CompFireModes;
            }
        }
    }

    [SyncWorker]
    private static void SyncLoadout(SyncWorker sync, ref Loadout? loadout)
    {
        if (sync.isWriting)
        {
            sync.Write(loadout!.UniqueID);
        }
        else
        {
            var id = sync.Read<int>();
            loadout = LoadoutManager.GetLoadoutById(id);
        }
    }

    [SyncWorker]
    private static void SyncLoadoutSlot(SyncWorker sync, ref LoadoutSlot? loadoutSlot)
    {
        if (sync.isWriting)
        {
            var loadoutIndex = 0;
            var slotIndex = -1;

            var list = LoadoutManager.Loadouts;
            for (; loadoutIndex < list.Count; loadoutIndex++)
            {
                var value = list[loadoutIndex];
                slotIndex = value.Slots.IndexOf(loadoutSlot);
                if (slotIndex >= 0)
                {
                    break;
                }
            }

            if (slotIndex >= 0)
            {
                sync.Write(loadoutIndex);
                sync.Write(slotIndex);
            }
            else
            {
                sync.Write(-1);
            }
        }
        else
        {
            var loadoutIndex = sync.Read<int>();
            if (loadoutIndex < 0)
            {
                return;
            }

            loadoutSlot = LoadoutManager.Loadouts[loadoutIndex].Slots[sync.Read<int>()];
        }
    }

    // Don't sync anything, we just want a blank instance for method calling purposes
    // We only care about shouldConstruct being true
    [SyncWorker(shouldConstruct = true)]
    private static void SyncITab_Inventory(SyncWorker sync, ref ITab_Inventory? inventory)
    { }
}
#nullable restore
