using System;
using System.Collections.Generic;
using System.Linq;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

namespace Parking
{
    public class ParkingManager : MonoBehaviour
    {
        public const float Width = 68;
        public const float Height = 29;

        public static ParkingManager Instance;

        private static readonly TimeSpan StartTime = TimeSpan.FromHours(5) + TimeSpan.FromMinutes(45);
        private static readonly TimeSpan EndTime = TimeSpan.FromHours(17) + TimeSpan.FromMinutes(15);
        private static TimeSpan _currentTime = StartTime;

        [SerializeField] private int stepTime = 15;
        [SerializeField] private TextMeshProUGUI timeText;
        [SerializeField] public TextMeshProUGUI countsText;
        [SerializeField] public TextMeshProUGUI rejectedText;
        [SerializeField] public TextMeshProUGUI pathText;
        [SerializeField] public GameObject startPanel;
        [SerializeField] public GameObject notFoundPanel;
        [SerializeField] public GameObject spotPrefabA;
        [SerializeField] public GameObject spotPrefabB;
        [SerializeField] public GameObject spotPrefabC;
        [SerializeField] public GameObject spotPrefabD;
        [SerializeField] public GameObject carPrefab;
        [SerializeField] public Transform mainPlanContainer;
        [SerializeField] public Transform emergencyPlanContainer;
        [SerializeField] public Toggle reconfigurationToggle;
        [SerializeField] public string defaultPath = "Assets/Data/Tablica2.csv";

        private readonly float[] _spotHeights = {4f, 4.5f, 5f, 7.5f};

        private readonly List<List<Spot>> _spotMap = new()
            {new List<Spot>(), new List<Spot>(), new List<Spot>(), new List<Spot>()};

        private bool _emergencyActivated;

        private int _initialConfigurationSpotCount;
        private int[,] _initialSolution;
        private bool _reconfigurationActive = true;
        private int[] _rejectedDrivers = {0, 0, 0, 0};
        private List<GameObject> _spotPrefabs;

        private void Awake()
        {
            Instance = this;
        }

        private void Start()
        {
            _spotPrefabs = new List<GameObject> {spotPrefabA, spotPrefabB, spotPrefabC, spotPrefabD};
            startPanel.SetActive(true);
            reconfigurationToggle.onValueChanged.AddListener(ReconfigurationToggle);
        }

        public void ReconfigurationToggle(bool value)
        {
            _reconfigurationActive = value;
            Debug.Log($"Reconfiguration {(value ? "activated" : "deactivated")}");
        }

        public void ReadDataButtonClick()
        {
            string path = pathText.text.Replace("\u200B", "");
            if (path.Length == 0)
                path = defaultPath;
            if (!DataImporter.ReadFile(path)) {
                notFoundPanel.SetActive(true);
                return;
            }

            InitiateManager();
            startPanel.SetActive(false);
        }

        private void InitiateManager()
        {
            timeText.text = _currentTime.ToString();
            rejectedText.text = $"Małe: {_rejectedDrivers[0]} Średnie: {_rejectedDrivers[1]} " +
                                $"Duże: {_rejectedDrivers[2]}";
            GenerateEmergencyPlan();

            InitialConfigurationGenerator generator = new();
            _initialSolution = generator.FindSolution();
            ArrangeSpots(_initialSolution);
            ReserveInitialSpots();
        }

        public void Emergency()
        {
            ResetDrivers();
            _emergencyActivated = true;
        }

        private void ReserveInitialSpots()
        {
            for (int j = 3; j >= 0; j--)
                foreach (Spot spot in _spotMap[j]) {
                    spot.Reserved = false;
                    spot.ReservedPriority = 0;
                }

            for (int i = 0; i < Math.Min(DataImporter.Drivers.Count, _initialConfigurationSpotCount); i++) {
                bool found = false;
                // Find spot
                for (int j = 3; j >= 0 && !found; j--)
                    foreach (Spot spot in _spotMap[j])
                        if (spot.Size == DataImporter.Drivers[i].Size && !spot.Reserved) {
                            spot.Reserved = true;
                            spot.ReservedPriority = DataImporter.Drivers[i].Priority;
                            found = true;
                            break;
                        }
            }
        }

        public void AdvanceTime()
        {
            // Update time
            _currentTime += TimeSpan.FromMinutes(stepTime);
            if (_currentTime > EndTime) {
                _currentTime = StartTime;
                timeText.text = _currentTime.ToString();
                _rejectedDrivers = new[] {0, 0, 0, 0};
                rejectedText.text = $"Małe: {_rejectedDrivers[0]} Średnie: {_rejectedDrivers[1]} " +
                                    $"Duże: {_rejectedDrivers[2]}";
                if (_emergencyActivated) {
                    emergencyPlanContainer.gameObject.SetActive(true);
                    mainPlanContainer.gameObject.SetActive(false);
                }
                else {
                    ArrangeSpots(_initialSolution);
                    ReserveInitialSpots();
                    ResetDrivers();
                }

                return;
            }

            timeText.text = _currentTime.ToString();

            if (_emergencyActivated)
                return;

            if (_reconfigurationActive)
                ReconfigureSpots();

            foreach (Driver driver in DataImporter.Drivers) {
                // TODO: Check if car can stay before other reservation - may not work for priority-based reservations
                bool triesToPark = _currentTime <= driver.Times[3].TimeOfDay &&
                                   _currentTime > driver.Times[2].TimeOfDay;
                bool leftTheParking = _currentTime > driver.Times[3].TimeOfDay && driver.Parked;
                bool reservedButTimedout =
                    (_currentTime - driver.Times[0].TimeOfDay).TotalMinutes > driver.UpdateInterval &&
                    GetReservedSpotCount(driver.Priority, driver.Size) > 0
                    && !driver.Parked && !driver.Rejected && _currentTime <= driver.Times[3].TimeOfDay;
                if (triesToPark && !driver.Parked && !driver.Rejected) {
                    if (!PlaceCarOnParking(driver)) {
                        if (_reconfigurationActive && TryReconfigureSpotForSize(driver.Size)) {
                            if (!PlaceCarOnParking(driver)) {
                                Debug.Log($"Placing failed for car {driver.Number} size {driver.Size}");
                                driver.Rejected = true;
                                _rejectedDrivers[(int) driver.Size]++;
                                rejectedText.text = $"Małe: {_rejectedDrivers[0]} Średnie: {_rejectedDrivers[1]} " +
                                                    $"Duże: {_rejectedDrivers[2]}";
                            }
                        }
                        else {
                            Debug.Log($"Placing failed for car {driver.Number} size {driver.Size}");
                            driver.Rejected = true;
                            _rejectedDrivers[(int) driver.Size]++;
                            rejectedText.text = $"Małe: {_rejectedDrivers[0]} Średnie: {_rejectedDrivers[1]} " +
                                                $"Duże: {_rejectedDrivers[2]}";
                        }
                    }
                }
                else if (leftTheParking) {
                    driver.Spot.Reserved = false;
                    driver.Reset();
                }
                else if (reservedButTimedout) {
                    Debug.Log($"Reserved but timed out driver {driver.Number}");
                    driver.Rejected = true;
                }
            }

            UpdateCounts();
        }

        private void UpdateCounts()
        {

            int[] counts = {0, 0, 0, 0};
            for (int i = 0; i < _spotMap.Count; i++)
            for (int j = 0; j < _spotMap[i].Count; j++)
                counts[(int) _spotMap[i][j].Size]++;

            string countsString = $"Małe: {counts[0]} Średnie: {counts[1]} Duże: {counts[2]} " +
                                  $"Suma: {counts.Sum()}";
            UpdateText(countsString);
        }

        private void ResetDrivers()
        {
            foreach (Driver driver in DataImporter.Drivers) {
                if (driver.Spot != null) {
                    driver.Spot.Reserved = false;
                    driver.Spot.ReservedPriority = 0;
                }

                driver.Reset();
            }
        }

        private int GetReservedSpotCount(int priority, Size size)
        {
            int count = 0;
            foreach (List<Spot> list in _spotMap)
            foreach (Spot spot in list)
                if (spot.Size == size && spot.Reserved && spot.ReservedPriority == priority)
                    count++;

            return count;
        }

        private bool FindReservedSpot(Driver driver, out Spot spot)
        {
            foreach (List<Spot> list in _spotMap)
            foreach (Spot spot1 in list)
                if (spot1.Reserved && spot1.ReservedPriority == driver.Priority) {
                    spot = spot1;
                    return true;
                }

            spot = new Spot();

            return false;
        }

        private bool PlaceCarOnParking(Driver driver)
        {
            if (FindReservedSpot(driver, out Spot reservedSpot)) {
                if (reservedSpot.Free) {
                    PlaceDriverOnSpot(driver, reservedSpot);
                    return true;
                }

                Debug.Log("Spot should be reserved but is taken");
            }


            foreach (List<Spot> t in _spotMap)
            foreach (Spot spot in t) {
                bool spotAvailable = spot.Size == driver.Size && spot.Free &&
                                     (spot.ParkingDirection == driver.ParkingPreference ||
                                      spot.ParkingDirection == ParkingPreference.Any)
                                     && !spot.Reserved;

                if (spotAvailable) {
                    PlaceDriverOnSpot(driver, spot);
                    return true;
                }
            }

            foreach (List<Spot> t in _spotMap)
            foreach (Spot spot in t)
                if (spot.Size == driver.Size && spot.Free && !spot.Reserved) {
                    PlaceDriverOnSpot(driver, spot);
                    return true;
                }

            return false;
        }

        private void PlaceDriverOnSpot(Driver driver, Spot spot)
        {
            spot.Free = false;
            spot.Reserved = false;
            driver.Spot = spot;
            driver.Parked = true;
            driver.GameObject = Instantiate(carPrefab);
            driver.GameObject.GetComponentInChildren<TextMeshProUGUI>().text = driver.Number.ToString();
            driver.GameObject.GetComponentInChildren<TextMeshProUGUI>().transform.rotation =
                Quaternion.Euler(new Vector3(0, 0, (spot.Flipped ? 180 : 0) + (!spot.Perpendicular ? -90 : 0)));
            driver.GameObject.transform.rotation =
                Quaternion.Euler(new Vector3(0, 0, (spot.Flipped ? 180 : 0) + (!spot.Perpendicular ? 90 : 0)));
            driver.GameObject.transform.position = spot.GameObject.transform.position;
            driver.GameObject.transform.SetParent(spot.GameObject.transform, true);
            driver.GameObject.transform.localPosition += new Vector3(0, 0, -1);
        }

        public void UpdateText(string text)
        {
            countsText.text = text;
        }

        private void ReconfigureSpots()
        {
            int[] freeSpots = GetFreeSpotCount();
            List<Driver> nextCars = GetNextCars(1);
            int[] plannedSpots = {0, 0, 0, 0};
            foreach (Driver driver in nextCars)
                plannedSpots[(int) driver.Size]++;

            int[] neededSpots = {0, 0, 0, 0};

            for (int i = 0; i < neededSpots.Length; i++)
                neededSpots[i] = Math.Max(plannedSpots[i] - freeSpots[i], 0);
            if (neededSpots.Sum() > 1) {
                Debug.Log($"Needed spots = {neededSpots[0]} {neededSpots[1]} {neededSpots[2]} {neededSpots[3]}");
                Debug.Log("Attempting reconfiguration...");

                List<Driver> spotlessCars = new();

                // Filter drivers by assigning free space
                foreach (Driver nextCar in nextCars)
                    if (freeSpots[(int) nextCar.Size] != 0)
                        freeSpots[(int) nextCar.Size]--;
                    else
                        spotlessCars.Add(nextCar);


                foreach (Driver nextCar in spotlessCars)
                    TryReconfigureSpotForSize(nextCar.Size);
            }
        }

        private bool TryReconfigureSpotForSize(Size newSize)
        {
            foreach (List<Spot> currentLaneSpots in _spotMap)
            foreach (Spot spot in currentLaneSpots) {
                if (!(spot.Free && spot.Size != Size.D)) // && !spot.Reserved))
                    continue;
                if (GetSpotCountOfSize(Size.A) <= 2 && spot.Size == Size.A)
                    continue;
                if (GetSpotCountOfSize(Size.B) <= 2 && spot.Size == Size.B)
                    continue;
                if (spot.LastReconfiguration == _currentTime)
                    continue;

                int size = (int) newSize;
                Vector3 position = spot.GameObject.transform.position;
                Quaternion rotation = spot.GameObject.transform.rotation;

                float diff = (_spotHeights[(int) spot.Size] - _spotHeights[size]) / 2.0f;
                if (!spot.AlignToTop)
                    diff *= -1;
                Vector3 offset = spot.Perpendicular ? new Vector3(0, diff, 0) : new Vector3(-diff, 0, 0);
                Vector3 newPosition = position + offset;

                if (spot.Size > newSize) {
                    if (!spot.Perpendicular) {
                        float newX = GetNextBorderHorizontal(position, spot.Lane, spot.Perpendicular, newSize, true) -
                                     _spotHeights[(int) spot.Size] / 2.0f;
                        newPosition = new Vector3(newX,
                            newPosition.y, newPosition.z);
                    }
                    else {
                        float newX = GetNextBorderHorizontal(position, spot.Lane, spot.Perpendicular, newSize, true) -
                                     2.25f / 2.0f;
                        if (spot.Lane == 0) {
                            float xRelativeToRight = newX - Width / 2.0f - 2.25f / 2.0f;
                            xRelativeToRight -= (xRelativeToRight + 2.25f / 2.0f) % 2.25f;
                            newX = xRelativeToRight + Width / 2.0f;
                        }
                        else {
                            float xRelativeToLeft = newX + Width / 2.0f;
                            xRelativeToLeft -= (xRelativeToLeft + 2.25f / 2.0f) % 2.25f;
                            newX = xRelativeToLeft - Width / 2.0f;
                        }

                        newPosition = new Vector3(newX,
                            newPosition.y, newPosition.z);
                    }
                }
                else if (spot.Size < newSize) {
                    float spotBorder;
                    float thisSizeOffset;
                    if (spot.Perpendicular)
                        thisSizeOffset = _spotHeights[size] / 2.0f;
                    else
                        thisSizeOffset = 2.25f / 2.0f;
                    if (!spot.AlignToTop)
                        spotBorder = position.y + thisSizeOffset;
                    else
                        spotBorder = position.y - thisSizeOffset;
                    if (spot.Perpendicular) {
                        float nextBorder = GetNextBorderVectical(position, spot.Lane, spot.Perpendicular, newSize,
                            !spot.AlignToTop);
                        if (Math.Abs(spotBorder - nextBorder) < 5.5f)
                            continue; // Try parallel (?)
                    }
                    else {
                        float nextBorderLeft = GetNextBorderHorizontal(position, spot.Lane, spot.Perpendicular, newSize,
                            !spot.AlignToTop);
                        float nextBorderRight = GetNextBorderHorizontal(position, spot.Lane, spot.Perpendicular,
                            newSize,
                            !spot.AlignToTop);
                        if (nextBorderRight - nextBorderLeft < _spotHeights[(int) newSize]) {
                            // Try perpendicular
                            float bottomBorder = position.y - 2.25f / 2.0f;
                            float topBorder = position.y + 2.25f / 2.0f;
                            if (spot.AlignToTop) {
                                float newBottomBorder = topBorder - _spotHeights[size];
                                float newTopBorder = topBorder;
                                float nextBorder = GetNextBorderVectical(position, spot.Lane, spot.Perpendicular,
                                    newSize,
                                    !spot.AlignToTop);
                                if (Math.Abs(nextBorder - newBottomBorder) < 5.5f)
                                    continue;
                            }
                            else {
                                float newBottomBorder = bottomBorder;
                                float newTopBorder = bottomBorder + _spotHeights[size];
                                float nextBorder = GetNextBorderVectical(position, spot.Lane, spot.Perpendicular,
                                    newSize,
                                    !spot.AlignToTop);
                                if (Math.Abs(newTopBorder - nextBorder) < 5.5f)
                                    continue;
                            }

                            newPosition.y = bottomBorder + _spotHeights[(int) newSize] / 2.0f;
                            float newX = nextBorderRight;
                            if (spot.Lane == 0) {
                                float xRelativeToRight = -(newX - Width / 2.0f);
                                xRelativeToRight -= (xRelativeToRight + 2.25f / 2.0f) % 2.25f;
                                xRelativeToRight += 2.25f;
                                xRelativeToRight = Math.Max(xRelativeToRight, 2.25f / 2.0f);
                                newX = -xRelativeToRight + Width / 2.0f;
                            }
                            else {
                                float xRelativeToLeft = newX + Width / 2.0f;
                                xRelativeToLeft -= (xRelativeToLeft + 2.25f / 2.0f) % 2.25f;
                                newX = xRelativeToLeft - Width / 2.0f;
                            }


                            if (spot.Lane == 0 && Width / 2.0f - newX < 5.5f + 0.5f + 2.25f)
                                continue;


                            newPosition.x = newX;
                            spot.Flipped = currentLaneSpots.FindIndex(spotPredicate => spotPredicate == spot) % 2 == 0;
                            rotation *= Quaternion.Euler(0, 0, spot.Flipped ? 90 : -90);
                            spot.Perpendicular = true;
                        }
                    }
                }

                Destroy(spot.GameObject);
                spot.Size = (Size) size;
                spot.Reserved = false;
                spot.GameObject = Instantiate(_spotPrefabs[size],
                    newPosition, rotation, mainPlanContainer);
                spot.LastReconfiguration = _currentTime;
                return true;
            }

            return false;
        }

        private float GetNextBorderVectical(Vector3 position, int lane, bool perpendicular, Size size, bool up)
        {

            float minX = position.x - (perpendicular ? 2.25f : _spotHeights[(int) size]);
            float maxX = position.x + (perpendicular ? 2.25f : _spotHeights[(int) size]);

            List<float> allBorders = new();
            List<Spot> targetLane = up ? _spotMap[lane + 1] : _spotMap[lane - 1];

            foreach (Spot spot1 in targetLane) {
                Vector3 targetSpotPosition = spot1.GameObject.transform.position;
                if (targetSpotPosition.x < minX || targetSpotPosition.x > maxX)
                    continue;
                float sizeOffset;
                if (spot1.Perpendicular)
                    sizeOffset = _spotHeights[(int) spot1.Size] / 2.0f;
                else
                    sizeOffset = 2.25f;
                if (up)
                    allBorders.Add(targetSpotPosition.y - sizeOffset);
                else
                    allBorders.Add(targetSpotPosition.y + sizeOffset);
            }

            if (allBorders.Count == 0)
                return up ? Height / 2.0f : -Height / 2.0f;
            float nextBorder = up ? allBorders.Min() : allBorders.Max();
            return nextBorder;
        }

        private float GetNextBorderHorizontal(Vector3 position, int lane, bool perpendicular, Size size, bool right)
        {
            List<float> allBorders = new();
            List<Spot> targetLane = _spotMap[lane];
            foreach (Spot spot1 in targetLane) {
                if (spot1.GameObject.transform.position == position)
                    continue;
                Vector3 spotPosition = spot1.GameObject.transform.position;
                float sizeOffset;
                if (spot1.Perpendicular)
                    sizeOffset = 2.25f / 2.0f;
                else
                    sizeOffset = _spotHeights[(int) spot1.Size] / 2.0f;
                if (right)
                    allBorders.Add(spotPosition.x - sizeOffset);
                else
                    allBorders.Add(spotPosition.x + sizeOffset);
            }

            allBorders.Sort();
            foreach (float adjacentBorder in allBorders)
                if (adjacentBorder > position.x)
                    return adjacentBorder;

            if (perpendicular)
                return position.x + (right ? 1 : -1) * _spotHeights[(int) size] / 2.0f;
            return (right ? 1 : -1) * Width / 2.0f;
        }


        private int GetSpotCountOfSize(Size size)
        {
            int count = 0;
            foreach (List<Spot> t in _spotMap)
            foreach (Spot spot in t)
                if (spot.Size == size)
                    count++;
            return count;
        }

        private int[] GetFreeSpotCount()
        {
            int[] freeSpots = {0, 0, 0, 0};
            foreach (List<Spot> t in _spotMap)
            foreach (Spot spot in t)
                if (spot.Free)
                    freeSpots[(int) spot.Size]++;
            return freeSpots;
        }

        private List<Driver> GetNextCars(int steps)
        {
            List<Driver> nextCars = new();
            TimeSpan updatedTime = _currentTime + TimeSpan.FromMinutes(stepTime * steps);
            foreach (Driver driver in DataImporter.Drivers)
                if (updatedTime <= driver.Times[1].TimeOfDay &&
                    updatedTime >= driver.Times[0].TimeOfDay &&
                    !driver.Parked && !driver.Rejected)
                    nextCars.Add(driver);

            return nextCars;
        }

        private void ArrangeSpots(int[,] spotsCreated)
        {
            List<List<Spot>> spotMap = GenerateSpotMap(spotsCreated);

            float[] spotSizes = {4, 4.5f, 5, 0};
            float maxP3 = 0;
            float maxP2 = 0;
            foreach (Spot spot in spotMap[2])
                if (spot.Size != Size.D)
                    maxP3 = Math.Max(maxP3, spot.Perpendicular ? _spotHeights[(int) spot.Size] : 2.25f);
            foreach (Spot spot in spotMap[1])
                if (spot.Size != Size.D)
                    maxP2 = Math.Max(maxP2, spot.Perpendicular ? _spotHeights[(int) spot.Size] : 2.25f);

            List<GameObject> prefabs = new() {spotPrefabA, spotPrefabB, spotPrefabC, spotPrefabD};

            for (int i = 0; i < 4; i++) {
                float currentY;
                bool parkingFromTop = i % 2 != 0;
                switch (i) {
                    case 0:
                        currentY = -Height / 2.0f;
                        break;
                    case 1:
                        currentY = Height / 2.0f - 5.5f - 5.0f - maxP3;
                        break;
                    case 2:
                        currentY = Height / 2.0f - 5.5f - 5.0f - maxP3;
                        break;
                    case 3:
                        currentY = Height / 2.0f;
                        break;
                    default:
                        currentY = -10;
                        break;
                }

                float currentX;
                if (i != 0) currentX = -Width / 2f;
                else currentX = Width / 2f; // + 5.5f - 2.25f / 2f + 2.25f + 1.75f;

                bool flipped = false;

                for (int j = 0; j < spotMap[i].Count; j++) {
                    currentX += (i != 0 ? 1 : -1) *
                                (spotMap[i][j].Perpendicular
                                    ? 2.25f / 2.0f
                                    : spotSizes[(int) spotMap[i][j].Size] / 2.0f);

                    spotMap[i][j].Flipped = flipped;
                    bool alignTop = i % 2 != 0;
                    spotMap[i][j].GameObject = Instantiate(prefabs[(int) spotMap[i][j].Size], mainPlanContainer);

                    if (spotMap[i][j].Perpendicular) {
                        spotMap[i][j].GameObject.transform.position =
                            new Vector3(currentX,
                                currentY + (alignTop ? -1 : 1) * spotSizes[(int) spotMap[i][j].Size] / 2.0f, 0);
                    }
                    else {
                        spotMap[i][j].GameObject.transform.position =
                            new Vector3(currentX,
                                currentY + (alignTop ? -1 : 1) * 2.25f / 2.0f, 0);
                    }

                    spotMap[i][j].AlignToTop = alignTop;


                    if (!spotMap[i][j].Perpendicular)
                        spotMap[i][j].Flipped = alignTop;

                    if (spotMap[i][j].Perpendicular) {
                        spotMap[i][j].GameObject.transform.rotation =
                            Quaternion.Euler(new Vector3(0, 0, spotMap[i][j].Flipped ? 180 : 0));
                    }
                    else {
                        spotMap[i][j].GameObject.transform.rotation =
                            Quaternion.Euler(new Vector3(0, 0, (spotMap[i][j].Flipped ? 180 : 0) + 90));
                    }

                    bool frontalParking = !parkingFromTop ^ flipped;
                    spotMap[i][j].ParkingDirection = frontalParking ? ParkingPreference.Front : ParkingPreference.Back;

                    spotMap[i][j].Lane = i;

                    currentX += (i != 0 ? 1 : -1) *
                                (spotMap[i][j].Perpendicular
                                    ? 2.25f / 2.0f
                                    : spotSizes[(int) spotMap[i][j].Size] / 2.0f);

                    flipped = !flipped;
                }
            }
        }

        private void GenerateEmergencyPlan()
        {
            emergencyPlanContainer.gameObject.SetActive(false);
            for (int i = 0; i < 4; i++) {
                float currentY;
                switch (i) {
                    case 0:
                        currentY = -Height / 2.0f;
                        break;
                    case 1:
                        currentY = Height / 2.0f - 5.5f - 5.0f - 5.0f;
                        break;
                    case 2:
                        currentY = Height / 2.0f - 5.5f - 5.0f - 5.0f;
                        break;
                    case 3:
                        currentY = Height / 2.0f;
                        break;
                    default:
                        currentY = -10;
                        break;
                }

                float currentX;
                if (i != 0) currentX = -Width / 2f - 2.25f / 2f + 2.25f;
                else currentX = Width / 2f - 5.0f / 2f;

                GameObject spawnedSpot;

                int[] emergencyMap = {12, 27, 27, 20};

                for (int j = 0; j < emergencyMap[i]; j++)
                    if (i == 0) {
                        Vector3 position = new(currentX, currentY + 2.25f / 2.0f, 0);
                        Instantiate(Instance.spotPrefabC, position,
                            Quaternion.Euler(new Vector3(0, 0, 90)), emergencyPlanContainer);

                        currentX -= 5;
                    }
                    else {
                        bool alignTop = i % 2 != 0;
                        Vector3 position = new(currentX, currentY, 0);
                        spawnedSpot = Instantiate(Instance.spotPrefabC, position,
                            Quaternion.Euler(new Vector3(0, 0, 0)), emergencyPlanContainer);

                        spawnedSpot.transform.position =
                            new Vector3(currentX,
                                currentY + (alignTop ? -1 : 1) * 5.0f / 2.0f,
                                0);

                        currentX += 2.25f;
                    }
            }
        }

        private List<List<Spot>> GenerateSpotMap(int[,] spotsCreated)
        {
            foreach (List<Spot> list in _spotMap) {
                foreach (Spot spot in list)
                    if (spot.GameObject != null)
                        Destroy(spot.GameObject);
                list.Clear();
            }

            for (int i = 0; i < 4; i++)
            for (int j = 0; j < spotsCreated.GetLength(1); j++)
            for (int k = 0; k < spotsCreated[i, j]; k++) {
                Spot spot = new((Size) (j % 4), false);
                spot.Perpendicular = j < 4;
                _spotMap[i].Add(spot);
                _initialConfigurationSpotCount++;
            }

            _spotMap[0].Sort((a, b) => a.Size.CompareTo(b.Size)); // ascending sort
            _spotMap[1].Sort((a, b) => b.Size.CompareTo(a.Size)); // descending sort
            _spotMap[2].Sort((a, b) => b.Size.CompareTo(a.Size)); // descending sort
            _spotMap[3].Sort((a, b) => a.Size.CompareTo(b.Size)); // ascending sort
            _spotMap[2].Add(new Spot(Size.D, false));
            return _spotMap;
        }
    }
}