using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Text.RegularExpressions;
using Mutagen.Bethesda;
using Mutagen.Bethesda.Synthesis;
using Mutagen.Bethesda.Skyrim;
using Mutagen.Bethesda.FormKeys.SkyrimSE;
using Mutagen.Bethesda.Plugins.Cache;
using Mutagen.Bethesda.Plugins;
using Noggog;


namespace ContainersRespawnPatcher
{
    public class Program
    {
        // Dictionaries of containers
        internal static Dictionary<FormKey, Container> containersRespawn = new();
        internal static Dictionary<FormKey, Container> containersNoRespawn = new();

        public static Lazy<Settings> _settings = null!;
        public static Settings Settings => _settings.Value;

        public static async Task<int> Main(string[] args)
        {
            return await SynthesisPipeline.Instance
                .AddPatch<ISkyrimMod, ISkyrimModGetter>(RunPatch)
                .SetAutogeneratedSettings("Settings", "settings.json", out _settings)
                .SetTypicalOpen(GameRelease.SkyrimSE, "SynthesisContainers.esp")
                .Run(args);
        }

        public static void CreateNewContainers(IPatcherState<ISkyrimMod, ISkyrimModGetter> state)
        {
            System.Console.WriteLine("Creating new 'No Respawn' containers!");

            // Counter
            int nbCont = 0;

            foreach (var containerGetter in state.LoadOrder.PriorityOrder.WinningOverrides<IContainerGetter>())
            {
                // Skip null container EditorID
                if (containerGetter.EditorID is null) continue;

                // If the EditorID of the container is found in the settings
                if (Settings.SafeContainersSettings.ContainerEditorIDs.Contains(containerGetter.EditorID))
                {
                    if (Settings.debug)
                        System.Console.WriteLine("Container found: " + containerGetter.EditorID);

                    // Check if the container already exists
                    state.LinkCache.TryResolve<IContainer>(containerGetter.EditorID + "_NoRespawn", out var existing);
                    if (existing is not null)
                    {
                        if (Settings.debug)
                            System.Console.WriteLine("   > Container _NoRespawn already exists: " + existing?.EditorID);

                        continue;
                    }

                    // Duplicate the record
                    Container contNew = state.PatchMod.Containers.DuplicateInAsNewRecord<Container, IContainerGetter>(containerGetter);

                    // Get the existing record
                    var contOld = state.PatchMod.Containers.GetOrAddAsOverride(containerGetter);

                    // Skip null
                    if (contOld.EditorID is null || contNew.EditorID is null) continue;

                    // If the container has a Respawn flag already, duplicate it and add no respawn
                    if (containerGetter.Flags.HasFlag(Container.Flag.Respawns))
                    {
                        // Name the new container NORESPAWN & remove the respawn flag 
                        contNew.EditorID = containerGetter.EditorID + "_NoRespawn";
                        contNew.Flags.SetFlag(Container.Flag.Respawns, false);

                        if (Settings.debug)
                            System.Console.WriteLine("   > Created new container: " + contNew.EditorID);

                        //Count
                        nbCont++;
                    }
                    // The container does not have the flag
                    else
                    {
                        // If the existing container has NoRespawn in their editorID, ignore it altogether
                        if (contOld.EditorID.Contains("norespawn", StringComparison.OrdinalIgnoreCase)) continue;

                        // Append the _NoRespawn text to the copy
                        contNew!.EditorID = contNew.EditorID + "_NoRespawn";

                        // Add the flag to the original container
                        contOld.Flags |= Container.Flag.Respawns;


                        if (Settings.debug)
                        {
                            System.Console.WriteLine("   > Created new container: " + contNew.EditorID);
                            System.Console.WriteLine("   > Added Respawn flag to container: " + contOld.EditorID);
                        }

                        // Count
                        nbCont++;
                    }

                    // Add the containers to the lists
                    containersRespawn.Add(contOld.FormKey, contOld);
                    containersNoRespawn.Add(contNew.FormKey, contNew);
                }
                else
                {
                    // Do nothing
                    //System.Console.WriteLine("Container not found: " + containerGetter.EditorID);
                }
            }

            System.Console.WriteLine("Created " + nbCont + " new containers!");
        }

        public static void RunPatch(IPatcherState<ISkyrimMod, ISkyrimModGetter> state)
        {
            // Create a link cache
            ILinkCache cache = state.LinkCache;

            // Counter
            int nbContTotal = 0;

            /*
             * Swap the placed container in that cell with a No Respawn version
             * 
             */
            void DoContainerSwap(IModContext<ISkyrimMod, ISkyrimModGetter, IPlacedObject, IPlacedObjectGetter> placed, IModContext<ICellGetter> cell)
            {
                // Find the base record
                placed.Record.Base.TryResolve(cache, out var baseObject);
                if (baseObject is null || baseObject.EditorID is null) return;

                // If the containers have this editor ID, it is a container
                if (containersRespawn.ContainsKey(baseObject.FormKey))
                {
                    if (Settings.debug)
                        System.Console.WriteLine("Swapping object:" + baseObject.EditorID + " in cell: " + cell.Record.EditorID);

                    // Swap the container base
                    var placedCopy = placed.GetOrAddAsOverride(state.PatchMod);
                    string edidnorespawn = baseObject.EditorID + "_NoRespawn";

                    Container c = containersNoRespawn.Values.Where(x => x?.EditorID == edidnorespawn).First();
                    if (c is null) return;
                    placedCopy.Base.SetTo(c);

                    nbContTotal++;

                    // Set the parent object
                    var parent = (ICellGetter?)placed.Parent?.Record;

                    // Handle ownership properly
                    if (placed?.Record.Owner is null) return;
                    if (placedCopy.Owner is null) return;

                    // Just in case the copy has ownership issues
                    if (placedCopy.Owner != placed.Record.Owner)
                        placedCopy.Owner = placed.Record.Owner.AsNullable();
                }
                // Container already flagged as "No Respawn"
                else if (containersNoRespawn.ContainsKey(baseObject.FormKey))
                {
                    // Nothing to do!
                }
                // Not a container
                else
                {
                    return;
                }
            }

            /// Check settings
            System.Console.WriteLine("Doing settings checks...");

            if (Settings.CellsNotRespawningSettings.CellNoRespawnEditorIDs.Count == 0)
            {
                System.Console.WriteLine("WARNING: NO SAFE PLAYER HOME SET, ALL CONTAINERS WILL RESPAWN IN ALL CELLS");
            }
            if (Settings.SafeContainersSettings.ContainerEditorIDs.Count == 0)
            {
                System.Console.WriteLine("ERROR: NO CONTAINERS SET, ABORTING! THIS WILL CAUSE MAJOR ISSUES IN YOUR GAME!");
                throw new Exception("Invalid settings!");
            }

            System.Console.WriteLine("Settings seem valid, starting!");

            /// Create the No Respawn containers
            CreateNewContainers(state);

            /// Create a dictionary of placed containers
            System.Console.WriteLine("Starting building containers contexts!");

            // Create the dictionary
            var containerContext = new Lazy<Dictionary<FormKey, IModContext<ISkyrimMod, ISkyrimModGetter, IPlacedObject, IPlacedObjectGetter>>>();

            // Fill the dictionary from the link cache
            state.LoadOrder.PriorityOrder.PlacedObject().WinningContextOverrides(cache)
                .Where(ctx => {
                    return containersRespawn.ContainsKey(ctx.Record.Base.FormKey) || containersNoRespawn.ContainsKey(ctx.Record.Base.FormKey);
                })
                .ForEach(ctx => containerContext.Value.Add(ctx.Record.FormKey, ctx));

            System.Console.WriteLine("Containers contexts done!");


            /// Iterate on cells
            foreach (var cellContext in state.LoadOrder.ListedOrder.Cell().WinningContextOverrides(cache))
            {
                // Ignore null
                if (cellContext is null || cellContext.Record is null || cellContext.Record.EditorID is null) continue;

                // If the cell is listed in the "No respawn" locations
                if (Settings.CellsNotRespawningSettings.CellNoRespawnEditorIDs.Contains(cellContext.Record.EditorID))
                {
                    if (Settings.debug)
                        System.Console.WriteLine("Editing cell: " + cellContext.Record.EditorID);

                    // On all placed Temporary items
                    foreach (var obj in cellContext.Record.Temporary)
                    {
                        if (obj is null || obj is null) continue;


                        //FOR SR EXTERIOR obj.DeepCopy().RemapLinks


                        containerContext.Value.TryGetValue(obj.FormKey, out var placedContext);
                        if (placedContext is null) continue;

                        DoContainerSwap(placedContext, cellContext);
                    }

                    // On all placed Persistent items
                    foreach (var obj in cellContext.Record.Persistent)
                    {
                        if (obj is null || obj is null) continue;


                        //FOR SR EXTERIOR obj.DeepCopy().RemapLinks


                        containerContext.Value.TryGetValue(obj.FormKey, out var placedContext);
                        if (placedContext is null) continue;

                        DoContainerSwap(placedContext, cellContext);
                    }
                }

            }

            System.Console.WriteLine("Swapped " + nbContTotal + " containers for a safe No Respawn one!");

            System.Console.WriteLine("All done!");
        }
    }
}
