using System;
using System.Collections.Generic;
using System.Linq;
using Intrepid.Diagnostics;
using Intrepid.Gameplay.Flooring;
using Intrepid.Gameplay.Journey;
using Intrepid.Gameplay.Prototyping;
using UnityEditor;
using UnityEngine;

namespace Editor.GON.StageEditor.Baker
{
    public struct StageBuilderRequest
    {
        public StageDefinition Stage { get; set; }
        public GameObject Root { get; set; }
        public PrefabTable PrefabTable { get; set; }
    }
    
    public static class StageBuilder
    {
        private static StageManager AddStageManager(StageBuilderRequest request)
        {
            var stageManager = request.Root.AddComponent<StageManager>();
            stageManager.StageTag = request.Stage.stageTag;
            return stageManager;
        }
        
        private static FloorManager AddFloorManager(StageBuilderRequest request)
        {
            return request.Root.AddComponent<FloorManager>();
        }
        
        public static void BuildStage(StageBuilderRequest request)
        {
            var stageManager = AddStageManager(request);
            var floorManager = AddFloorManager(request);
            
            var floorRootTransforms = new Dictionary<string, Transform>();

            foreach (var stageFloorDefinition in request.Stage.floors)
            {
                var cellsG = new HashSet<Vector2Int>();
                var cellsRF = new HashSet<Vector2Int>();
                var cellsRR = new HashSet<Vector2Int>();
                var cellsRL = new HashSet<Vector2Int>();
                var cellsSF = new HashSet<Vector2Int>();
                var cellsSR = new HashSet<Vector2Int>();
                var cellsSL = new HashSet<Vector2Int>();
                var cellsOW = new HashSet<Vector2Int>();

                foreach (var logicalCell in stageFloorDefinition.Cells)
                {
                    switch (logicalCell.structural)
                    {
                        case StructuralCellKind.Empty:
                            break;
                        case StructuralCellKind.Ground:
                            cellsG.Add(new Vector2Int(logicalCell.x, logicalCell.y));
                            break;
                        case StructuralCellKind.Ramp_Front:
                            cellsRF.Add(new Vector2Int(logicalCell.x, logicalCell.y));
                            break;
                        case StructuralCellKind.Ramp_Left:
                            cellsRL.Add(new Vector2Int(logicalCell.x, logicalCell.y));
                            break;
                        case StructuralCellKind.Ramp_Right:
                            cellsRR.Add(new Vector2Int(logicalCell.x, logicalCell.y));
                            break;
                        case StructuralCellKind.Stair_Front:
                            cellsSF.Add(new Vector2Int(logicalCell.x, logicalCell.y));
                            break;
                        case StructuralCellKind.Stair_Left:
                            cellsSL.Add(new Vector2Int(logicalCell.x, logicalCell.y));
                            break;
                        case StructuralCellKind.Stair_Right:
                            cellsSR.Add(new Vector2Int(logicalCell.x, logicalCell.y));
                            break;
                        case StructuralCellKind.Wall:
                            cellsOW.Add(new Vector2Int(logicalCell.x, logicalCell.y));
                            break;
                        default:
                            throw new ArgumentOutOfRangeException();
                    }
                }

                var floorRootTransform = new GameObject(stageFloorDefinition.floorId)
                {
                    transform =
                    {
                        parent = request.Root.transform,
                        position = new Vector3(0, stageFloorDefinition.FloorHeight, 0)
                    }
                }.transform;
                floorRootTransforms.Add(stageFloorDefinition.floorId, floorRootTransform);

                InstantiateGround(
                    request.PrefabTable.GroundSlabPrefab,
                    floorRootTransform,
                    cellsG,
                    StraightAddressSize
                );
                InstantiateGround(
                    request.PrefabTable.GroundRampFrontPrefab,
                    floorRootTransform,
                    cellsRF,
                    StraightAddressSize
                );
                InstantiateGround(
                    request.PrefabTable.GroundRampLeftPrefab,
                    floorRootTransform,
                    cellsRL,
                    InverseAddressSize
                );
                InstantiateGround(
                    request.PrefabTable.GroundRampRightPrefab,
                    floorRootTransform,
                    cellsRR,
                    InverseAddressSize
                );
                InstantiateGround(
                    request.PrefabTable.GroundStairFrontPrefab,
                    floorRootTransform,
                    cellsSF,
                    StraightAddressSize
                );
                InstantiateGround(
                    request.PrefabTable.GroundStairLeftPrefab,
                    floorRootTransform,
                    cellsSL,
                    InverseAddressSize
                );
                InstantiateGround(
                    request.PrefabTable.GroundStairRightPrefab,
                    floorRootTransform,
                    cellsSR,
                    InverseAddressSize
                );
                InstantiateObstacle(
                    request.PrefabTable.ObstacleWallPrefab,
                    floorRootTransform,
                    cellsOW,
                    StraightAddressSize
                );
            }

            foreach (var stageObjectDefinition in request.Stage.objects)
            {
                var floor = request.Stage.GetFloor(stageObjectDefinition.floorId);
                switch (stageObjectDefinition.kind)
                {
                    case StageObjectKind.SocketEast:
                    {
                        var h = stageObjectDefinition.height / 2f;
                        var x = stageObjectDefinition.x;
                        var y = -(stageObjectDefinition.y + h - 0.5f);
                        var localPosition = new Vector3(x, 0, y); 
                        InstantiateInteractive(
                            request.PrefabTable.InteractableSocketEastPrefab,
                            "Socket East",
                            floorRootTransforms[floor.floorId],
                            localPosition);
                    } break;
                    case StageObjectKind.SocketNorth:
                    {
                        var w = stageObjectDefinition.width / 2f;
                        var x = stageObjectDefinition.x + w - 0.5f;
                        var y = -stageObjectDefinition.y;
                        var localPosition = new Vector3(x, 0, y); 
                        InstantiateInteractive(
                            request.PrefabTable.InteractableSocketNorthPrefab,
                            "Socket North",
                            floorRootTransforms[floor.floorId],
                            localPosition);
                    } break;
                    case StageObjectKind.SocketSouth:
                    {
                        var w = stageObjectDefinition.width / 2f;
                        var x = stageObjectDefinition.x + w - 0.5f;
                        var y = -stageObjectDefinition.y;
                        var localPosition = new Vector3(x, 0, y); 
                        InstantiateInteractive(
                            request.PrefabTable.InteractableSocketSouthPrefab,
                            "Socket South",
                            floorRootTransforms[floor.floorId],
                            localPosition);
                    } break;
                    case StageObjectKind.SocketWest:
                    {
                        var h = stageObjectDefinition.height / 2f;
                        var x = stageObjectDefinition.x;
                        var y = -(stageObjectDefinition.y + h - 0.5f);
                        var localPosition = new Vector3(x, 0, y); 
                        InstantiateInteractive(
                            request.PrefabTable.InteractableSocketWestPrefab,
                            "Socket West",
                            floorRootTransforms[floor.floorId],
                            localPosition);
                    } break;
                    default:
                        throw new ArgumentOutOfRangeException();
                }
            }
            
            return;
            
            Vector2Int StraightAddressSize(RectInt rect)
            {
                return new Vector2Int(rect.width, rect.height);
            }
    
            Vector2Int InverseAddressSize(RectInt rect)
            {
                return new Vector2Int(rect.height, rect.width);
            }
        }
        
        private static void InstantiateGround<G>(
            G prefab,
            Transform parent,
            HashSet<Vector2Int> set,
            Func<RectInt, Vector2Int> addressSize
            ) where G: GroundModelPrototype
        {
            var releaseName =  new string(typeof(G).Name.Where(char.IsUpper).ToArray());
            
            if (prefab.IsNull())
            {
                DLogger.LogError($"Prefab is not configured in PrefabTable: {releaseName}");
                return;
            }

            foreach (var rect in StageBuilderHelpers.BuildRectanglesFromCellsBest(set))
            {
                if (PrefabUtility.InstantiatePrefab(prefab, parent) is not G g)
                {
                    DLogger.LogError($"Failed to instantiate prefab: {prefab.name}");
                    continue;
                }

                StageBuilderHelpers.ResetTransform(g.transform);
                g.name = $"{releaseName} ({rect.x}, {rect.y}) {rect.width}x{rect.height}";
                g.transform.localPosition = new Vector3(
                    rect.x + (rect.width - 1) * 0.5f,
                    0,
                    -(rect.y + (rect.height - 1) * 0.5f)
                );
                g.SetSize(addressSize(rect));
            }
        }
        
        private static void InstantiateObstacle<O>(
            O prefab,
            Transform parent,
            HashSet<Vector2Int> set,
            Func<RectInt, Vector2Int> addressSize
        ) where O: ObstacleModelPrototype
        {
            var releaseName =  new string(typeof(O).Name.Where(char.IsUpper).ToArray());
            
            if (prefab.IsNull())
            {
                DLogger.LogError($"Prefab is not configured in PrefabTable: {releaseName}");
                return;
            }

            foreach (var rect in StageBuilderHelpers.BuildRectanglesFromCellsBest(set))
            {
                if (PrefabUtility.InstantiatePrefab(prefab, parent) is not O o)
                {
                    DLogger.LogError($"Failed to instantiate prefab: {prefab.name}");
                    continue;
                }

                StageBuilderHelpers.ResetTransform(o.transform);
                o.name = $"{releaseName} ({rect.x}, {rect.y}) {rect.width}x{rect.height}";
                o.transform.localPosition = new Vector3(
                    rect.x + (rect.width - 1) * 0.5f,
                    0,
                    -(rect.y + (rect.height - 1) * 0.5f)
                );
                o.SetSize(addressSize(rect));
            }
        }
        
        private static void InstantiateInteractive<I>(
            I prefab,
            string description,
            Transform parent,
            Vector3 localPosition) where I: InteractableModelPrototype
        {
            var releaseName =  new string(typeof(I).Name.Where(char.IsUpper).ToArray());
            
            if (prefab.IsNull())
            {
                DLogger.LogError($"Prefab is not configured in PrefabTable: {releaseName}");
                return;
            }
            
            if (PrefabUtility.InstantiatePrefab(prefab, parent) is not I i)
            {
                DLogger.LogError($"Failed to instantiate prefab: {prefab.name}");
                return;
            }

            StageBuilderHelpers.ResetTransform(i.transform);
            i.name = $"{releaseName} ({description})";
            i.transform.localPosition = localPosition;
        }

    }
}