using System;
using System.Collections.Generic;
using System.Reflection;
using Gamecraft.Blocks.BlockGroups;
using Gamecraft.GUI.Blueprints;
using GamecraftModdingAPI.Engines;
using GamecraftModdingAPI.Utility;
using HarmonyLib;
using RobocraftX.Blocks;
using RobocraftX.Common;
using RobocraftX.CR.MachineEditing.BoxSelect;
using RobocraftX.CR.MachineEditing.BoxSelect.ClipboardOperations;
using Svelto.DataStructures;
using Svelto.ECS;
using Svelto.ECS.DataStructures;
using Svelto.ECS.EntityStructs;
using Svelto.ECS.Serialization;
using Unity.Collections;
using Unity.Mathematics;
using UnityEngine;
using Allocator = Svelto.Common.Allocator;

namespace GamecraftModdingAPI.Blocks
{
    public class BlueprintEngine : IFactoryEngine
    {
        private readonly MethodInfo getBlocksFromGroup =
            AccessTools.Method("RobocraftX.CR.MachineEditing.PlaceBlockUtility:GetBlocksSharingBlockgroup");

        private NativeDynamicArray selectedBlocksInGroup;
        private NativeHashSet<ulong> removedConnections = new NativeHashSet<ulong>();
        private int addingToBlockGroup = -1;

        private static readonly Type PlaceBlueprintUtilityType =
            AccessTools.TypeByName("RobocraftX.CR.MachineEditing.PlaceBlueprintUtility");
        private static readonly FieldInfo LocalBlockMap =
            AccessTools.DeclaredField(PlaceBlueprintUtilityType, "_localBlockMap");
        private static readonly MethodInfo BuildBlock = AccessTools.Method(PlaceBlueprintUtilityType, "BuildBlock");
        private static readonly MethodInfo BuildWires = AccessTools.Method(PlaceBlueprintUtilityType, "BuildWires");
        private static readonly Type SerializeGhostBlueprintType =
            AccessTools.TypeByName("RobocraftX.CR.MachineEditing.BoxSelect.SerializeGhostChildrenOnAddEngine");
        private static readonly MethodInfo SerializeGhostBlueprint =
            AccessTools.Method(SerializeGhostBlueprintType, "SerializeClipboardGhostEntities");

        private static NativeEntityRemove nativeRemove;
        private static MachineGraphConnectionEntityFactory connectionFactory;
        private static IEntityFunctions entityFunctions;
        private static ClipboardSerializationDataResourceManager clipboardManager;
        private static IEntitySerialization entitySerialization;
        private static IEntityFactory entityFactory;
        private static FasterList<EGID> globalBlockMap;
        private static object SerializeGhostBlueprintInstance;
        private static GhostChildEntityFactory BuildGhostBlueprintFactory;

        public void Ready()
        {
            selectedBlocksInGroup = NativeDynamicArray.Alloc<EGID>(Allocator.Persistent);
        }

        public EntitiesDB entitiesDB { get; set; }

        public void Dispose()
        {
            selectedBlocksInGroup.Dispose();
        }

        public Block[] GetBlocksFromGroup(EGID blockID, out float3 pos, out quaternion rot)
        {
            var blockPos = default(float3);
            var blockRot = default(quaternion);
            var parameters = new object[] {blockID, selectedBlocksInGroup, entitiesDB, blockPos, blockRot};
            getBlocksFromGroup.Invoke(null, parameters);
            pos = (float3) parameters[3];
            rot = (quaternion) parameters[4];
            int count = selectedBlocksInGroup.Count<EGID>();
            var ret = new Block[count];
            for (uint i = 0; i < count; i++)
                ret[i] = new Block(selectedBlocksInGroup.Get<EGID>(i));
            selectedBlocksInGroup.FastClear();
            return ret;
        }

        public void RemoveBlockGroup(int id)
        {
            BlockGroupUtility.RemoveAllBlocksInBlockGroup(id, entitiesDB, removedConnections, nativeRemove,
                connectionFactory, default).Complete();
        }

        public int CreateBlockGroup(float3 position, quaternion rotation)
        {
            int nextFilterId = BlockGroupUtility.NextFilterId;
            Factory.BuildEntity<BlockGroupEntityDescriptor>((uint) nextFilterId,
                BlockGroupExclusiveGroups.BlockGroupEntityGroup).Init(new BlockGroupTransformEntityComponent
            {
                blockGroupGridRotation = rotation,
                blockGroupGridPosition = position
            });
            return nextFilterId;
        }

        public void AddBlockToGroup(EGID blockID, int groupID)
        {
            if (globalBlockMap == null)
                globalBlockMap = FullGameFields._deserialisedBlockMap;
            if (groupID != addingToBlockGroup)
            {
                Logging.MetaDebugLog("Changing current block group from " + addingToBlockGroup + " to " + groupID);
                addingToBlockGroup = groupID;
                globalBlockMap.Clear();
            }

            globalBlockMap.Add(blockID);
        }

        public void SelectBlueprint(uint resourceID)
        {
            if (resourceID == uint.MaxValue)
                BlueprintUtil.UnselectBlueprint(entitiesDB);
            else
                BlueprintUtil.SelectBlueprint(entitiesDB, resourceID, false, -1);
        }

        public uint CreateBlueprint()
        {
            uint index = clipboardManager.AllocateSerializationData();
            return index;
        }

        public void ReplaceBlueprint(uint playerID, uint blueprintID, ICollection<Block> selected, float3 pos, quaternion rot)
        {
            var blockIDs = new EGID[selected.Count];
            using (var enumerator = selected.GetEnumerator())
            {
                for (var i = 0; enumerator.MoveNext(); i++)
                {
                    var block = enumerator.Current;
                    blockIDs[i] = block.Id;
                }
            }

            var serializationData = clipboardManager.GetSerializationData(blueprintID);
            SelectionSerializationUtility.ClearClipboard(playerID, entitiesDB, entityFunctions, serializationData.blueprintData, -1);
            if (selected.Count == 0)
                return;
            //ref BlockGroupTransformEntityComponent groupTransform = ref EntityNativeDBExtensions.QueryEntity<BlockGroupTransformEntityComponent>(entitiesDb, (uint) local1.currentBlockGroup, BlockGroupExclusiveGroups.BlockGroupEntityGroup);
            //ref ColliderAabb collider = ref EntityNativeDBExtensions.QueryEntity<ColliderAabb>(entitiesDB, (uint) groupID, BlockGroupExclusiveGroups.BlockGroupEntityGroup);
            //float3 bottomOffset = PlaceBlockUtility.GetBottomOffset(collider);
            //var rootPosition = math.mul(groupTransform.blockGroupGridRotation, bottomOffset) + groupTransform.blockGroupGridPosition;
            //var rootRotation = groupTransform.blockGroupGridRotation;

            clipboardManager.SetGhostSerialized(blueprintID, false);
            SelectionSerializationUtility.CopySelectionToClipboard(playerID, entitiesDB,
                serializationData.blueprintData, entitySerialization, entityFactory, blockIDs,
                (uint) blockIDs.Length, pos, rot, -1);
            BuildGhostBlueprint(selected, pos, rot, playerID);
            SerializeGhostBlueprint.Invoke(SerializeGhostBlueprintInstance, new object[] {playerID, blueprintID});

        }

        private void BuildGhostBlueprint(ICollection<Block> blocks, float3 pos, quaternion rot, uint playerID)
        {
            GhostChildUtility.ClearGhostChildren(playerID, entitiesDB, entityFunctions);
            foreach (var block in blocks)
            {
                GhostChildUtility.BuildGhostChild(in playerID, block.Id, in pos, in rot, entitiesDB,
                    BuildGhostBlueprintFactory, false);
            }
        }

        public Block[] PlaceBlueprintBlocks(uint blueprintID, uint playerID, float3 pos, float3 rot)
        { //RobocraftX.CR.MachineEditing.PlaceBlueprintUtility.PlaceBlocksFromSerialisedData
            var serializationData = clipboardManager.GetSerializationData(blueprintID);
            var blueprintData = serializationData.blueprintData;
            blueprintData.dataPos = 0U;
            uint selectionSize;
            PositionEntityStruct selectionPosition;
            RotationEntityStruct selectionRotation;
            uint version;
            BoxSelectSerializationUtilities.ReadClipboardHeader(blueprintData, out selectionSize, out selectionPosition, out selectionRotation, out version);
            ((FasterList<EGID>) LocalBlockMap.GetValue(null)).Clear();
            if (version <= 1U)
            {
                uint groupsCount;
                BoxSelectSerializationUtilities.ReadBlockGroupData(blueprintData, out groupsCount);
                for (int index = 0; (long) index < (long) groupsCount; ++index)
                {
                    int nextFilterId = BlockGroupUtility.NextFilterId;
                    entitySerialization.DeserializeNewEntity(new EGID((uint) nextFilterId, BlockGroupExclusiveGroups.BlockGroupEntityGroup), blueprintData, 1);
                }
            }
            int nextFilterId1 = BlockGroupUtility.NextFilterId;
            entityFactory.BuildEntity<BlockGroupEntityDescriptor>(new EGID((uint) nextFilterId1,
                    BlockGroupExclusiveGroups.BlockGroupEntityGroup)).Init(new BlockGroupTransformEntityComponent
                {
                    blockGroupGridPosition = selectionPosition.position,
                    blockGroupGridRotation = selectionRotation.rotation
                });
            var frot = Quaternion.Euler(rot);
            var grid = new GridRotationStruct {position = pos, rotation = frot};
            var poss = new PositionEntityStruct {position = pos};
            var rots = new RotationEntityStruct {rotation = frot};
            for (int index = 0; (long) index < (long) selectionSize; ++index)
                BuildBlock.Invoke(null,
                    new object[]
                    {
                        playerID, grid, poss, rots, selectionPosition, selectionRotation, blueprintData,
                        entitySerialization, nextFilterId1
                    });
            /*
             uint playerId, in GridRotationStruct ghostParentGrid,
             in PositionEntityStruct ghostParentPosition, in RotationEntityStruct ghostParentRotation,
             in PositionEntityStruct selectionPosition, in RotationEntityStruct selectionRotation,
              ISerializationData serializationData, EntitiesDB entitiesDb,
              IEntitySerialization entitySerialization, int blockGroupId
             */
            if (globalBlockMap == null)
                globalBlockMap = FullGameFields._deserialisedBlockMap;
            var placedBlocks = (FasterList<EGID>) LocalBlockMap.GetValue(null);
            globalBlockMap.Clear();
            globalBlockMap.AddRange(placedBlocks);
            BuildWires.Invoke(null,
                new object[] {playerID, blueprintData, entitySerialization, entitiesDB, entityFactory});
            var blocks = new Block[placedBlocks.count];
            for (int i = 0; i < blocks.Length; i++)
                blocks[i] = new Block(placedBlocks[i]);
            return blocks;
        }

        public void GetBlueprintInfo(uint blueprintID, out float3 pos, out quaternion rot, out uint selectionSize)
        {
            var serializationData = clipboardManager.GetSerializationData(blueprintID);
            var blueprintData = serializationData.blueprintData;
            blueprintData.dataPos = 0U;
            BoxSelectSerializationUtilities.ReadClipboardHeader(blueprintData, out selectionSize, out var posst,
                out var rotst, out _);
            blueprintData.dataPos = 0U; //Just to be sure, it gets reset when it's read anyway
            pos = posst.position;
            rot = rotst.rotation;
        }

        public void InitBlueprint(uint blueprintID)
        {
            clipboardManager.IncrementRefCount(blueprintID);
        }

        public void DisposeBlueprint(uint blueprintID)
        {
            clipboardManager.DecrementRefCount(blueprintID);
        }

        public string Name { get; } = "GamecraftModdingAPIBlueprintGameEngine";
        public bool isRemovable { get; } = false;

        [HarmonyPatch]
        private static class RemoveEnginePatch
        {
            public static void Prefix(IEntityFunctions entityFunctions,
                MachineGraphConnectionEntityFactory machineGraphConnectionEntityFactory)
            {
                nativeRemove = entityFunctions.ToNativeRemove<BlockEntityDescriptor>("GCAPI" + nameof(BlueprintEngine));
                connectionFactory = machineGraphConnectionEntityFactory;
                BlueprintEngine.entityFunctions = entityFunctions;
            }

            public static MethodBase TargetMethod()
            {
                return AccessTools.GetDeclaredConstructors(AccessTools.TypeByName("RobocraftX.CR.MachineEditing.RemoveBlockEngine"))[0];
            }
        }

        [HarmonyPatch]
        private static class SelectEnginePatch
        {
            public static void Prefix(ClipboardSerializationDataResourceManager clipboardSerializationDataResourceManager,
                IEntitySerialization entitySerialization,
                IEntityFactory entityFactory)
            {
                clipboardManager = clipboardSerializationDataResourceManager;
                BlueprintEngine.entitySerialization = entitySerialization;
                BlueprintEngine.entityFactory = entityFactory;
            }

            public static MethodBase TargetMethod()
            {
                return AccessTools.GetDeclaredConstructors(AccessTools.TypeByName("RobocraftX.CR.MachineEditing.SelectBlockEngine"))[0];
            }
        }

        [HarmonyPatch]
        private static class SerializeGhostBlueprintPatch
        {
            public static void Postfix(object __instance)
            {
                SerializeGhostBlueprintInstance = __instance;
            }

            public static MethodBase TargetMethod()
            {
                return AccessTools.GetDeclaredConstructors(SerializeGhostBlueprintType)[0];
            }
        }

        [HarmonyPatch]
        private static class BuildGhostBlueprintPatch
        {
            public static void Postfix(GhostChildEntityFactory ghostChildEntityFactory)
            {
                BuildGhostBlueprintFactory = ghostChildEntityFactory;
            }

            public static MethodBase TargetMethod()
            {
                return AccessTools.GetDeclaredConstructors(AccessTools.TypeByName("RobocraftX.CR.MachineEditing.BuildGhostChildForMultiblockPickEngine"))[0];
            }
        }

        public IEntityFactory Factory { get; set; }
    }
}