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

namespace Parking
{
    public class InitialConfigurationGenerator
    {
        private static readonly List<int[]> Combinations = new(3000);
        private static readonly float[] AvailableSizesCombinations = {0, 2.5f, 4, 4.5f, 5}; //, 7.5f};
        private readonly float _spotWidth = 2.25f;

        public int[,] FindSolution()
        {
            PreProcessCombinations(out var spotCountsPerpendicular, out var spotCountsParallel);

            var spotsCreated = new int[4, 4];
            
            var maxCount = TestCombinations(spotCountsPerpendicular, spotCountsParallel, spotsCreated);

            Debug.Log($"Best solution count {maxCount}");

            int[] counts = new int[] {0, 0, 0, 0};
            for (int i = 0; i < spotsCreated.GetLength(0); i++) {
                for (int j = 0; j < spotsCreated.GetLength(1); j++) {
                    counts[j] += spotsCreated[i, j];
                }
            }
            
            var countsString = $"Małe: {counts[0]} Średnie: {counts[1]} Duże: {counts[2]} " +
                               $"Suma: {counts.Sum() + 1}";
            ParkingManager.Instance.UpdateText(countsString);
            Debug.Log(countsString);

            
            return spotsCreated;
        }

        private int TestCombinations(int[] spotCountsPerpendicular, int[,] spotCountsParallel, int[,] spotsCreated)
        {
            int[] counts = {0, 0, 0};
            counts[(int) Size.A] += 2;
            counts[(int) Size.B] += 2;
            var fixedCarSpots = 0;
            foreach (var c in counts) fixedCarSpots += c;

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

            int maxCount = 0;
            int[] maxComb = new []{1, 1, 1, 1};

            // Debug.Log("Printing top 5 combinations...");
            foreach (var comb in Combinations)
            {
                int res = TryFillCombination(comb.ToArray(), spotCountsPerpendicular, spotCountsParallel, counts,
                    spotsCreated);
                if (res > maxCount) {
                    maxCount = res;
                    maxComb = comb;
                }
            }

            TryFillCombination(maxComb, spotCountsPerpendicular, spotCountsParallel, counts,
                spotsCreated, true);

            return maxCount;
        }

        private int TryFillCombination(int[] sizeIds, int[] spotCountsPerpendicularRef, int[,] spotCountsParallelRef, int[] counts, int[,] spotsCreated, bool copyToArray = false)
        {
            var spotsCreatedTemp = new int[4, 4];
            
            for (var i = 0; i < spotsCreatedTemp.GetLength(0); i++)
            for (var j = 0; j < spotsCreatedTemp.GetLength(1); j++)
                spotsCreatedTemp[i, j] = 0;
            
            var spotCountsPerpendicular = new int[spotCountsPerpendicularRef.Length];
            spotCountsPerpendicularRef.CopyTo((Span<int>) spotCountsPerpendicular);

            var spotCountsParallel = new int[spotCountsParallelRef.GetLength(0), spotCountsParallelRef.GetLength(1)];
            for (var i = 0; i < spotCountsParallelRef.GetLength(0); i++)
            for (var j = 0; j < spotCountsParallelRef.GetLength(1); j++)
                spotCountsParallel[i, j] = spotCountsParallelRef[i, j];

            float[] spotSizes = {4, 4.5f, 5};
            foreach (Driver driver in DataImporter.Drivers) {
                List<int> laneIds = new List<int>(){0, 1, 2, 3};
                if (driver.Size == Size.C)
                    laneIds.Reverse();
                
                for (int i = 0; i < 4; i++) {
                    int laneId = laneIds[i];
                    bool freeSpotsAvailable = spotCountsPerpendicular[laneId] != 0;
                    bool carFits = AvailableSizesCombinations[sizeIds[laneId]] >= spotSizes[(int)driver.Size];
                    if (carFits && freeSpotsAvailable) {
                        spotCountsPerpendicular[laneId]--;
                        spotsCreatedTemp[laneId, (int)driver.Size]++;
                        break;
                    }
                }
            }


            int count = 0;
            
            for (var i = 0; i < spotsCreatedTemp.GetLength(0); i++)
            for (var j = 0; j < spotsCreatedTemp.GetLength(1); j++)
                count += spotsCreatedTemp[i, j];


            if (!copyToArray)
                return count;
            
            for (var laneId = 3; laneId >= 0; laneId--) {
                if (spotCountsPerpendicular[laneId] != 0) {
                    if (sizeIds[laneId] == 0) {
                        // empty
                    }
                    else if (sizeIds[laneId] == 1) {
                        // parallel only
                    }
                    else {
                        spotsCreatedTemp[laneId, sizeIds[laneId] - 2] += spotCountsPerpendicular[laneId];
                        count += spotCountsPerpendicular[laneId];
                        spotCountsPerpendicular[laneId] = 0;
                    }
                }
            }

                
            for (var i = 0; i < spotsCreatedTemp.GetLength(0); i++)
            for (var j = 0; j < spotsCreatedTemp.GetLength(1); j++)
                spotsCreated[i, j] = spotsCreatedTemp[i, j];
            

            return count;
        }

        private void PreProcessCombinations(out int[] spotCountsPerpendicular, out int[,] spotCountsParallel)
        {
            // Debug.Log("Calculating spot counts...");
            float[] spotLengthsParallel = {4f, 4.5f, 5f}; //, 7.5f};
            spotCountsPerpendicular = new[] {0, 0, 0, 0};
            spotCountsPerpendicular[0] = Mathf.FloorToInt((ParkingManager.Width - 5.5f) / _spotWidth);
            spotCountsPerpendicular[1] = Mathf.FloorToInt((ParkingManager.Width - 5.5f - 2.5f) / _spotWidth);
            spotCountsPerpendicular[2] = Mathf.FloorToInt((ParkingManager.Width - 5.5f - 2.5f) / _spotWidth);
            spotCountsPerpendicular[3] =
                Mathf.FloorToInt((ParkingManager.Width - 5.5f - 3.6f * 2 - 2.5f * 4) / _spotWidth);
            spotCountsParallel = new int[4, 4];
            for (var i = 0; i < 3; i++)
            for (var j = 0; j < spotLengthsParallel.Length; j++)
                spotCountsParallel[i, j] = Mathf.FloorToInt((ParkingManager.Width - 5.5f) / spotLengthsParallel[j]);
            for (var j = 0; j < spotLengthsParallel.Length; j++)
                spotCountsParallel[3, j] =
                    Mathf.FloorToInt((ParkingManager.Width - 5.5f - 3.6f * 2 - 2.5f * 4) / spotLengthsParallel[j]);
            // Debug.Log("" +
            //           $"P1: {spotCountsPerpendicular[0]}/ P2: {spotCountsPerpendicular[1]} " +
            //           $"P3: {spotCountsPerpendicular[2]} P4: {spotCountsPerpendicular[3]} " +
            //           $"Sum: {spotCountsPerpendicular.Sum()}");

            // Debug.Log("Generating combinations...");
            int[] arr = {0, 1, 2, 3, 4};
            var n = arr.Length;
            var r = 4;
            CombinationRepetition(arr, n, r);
            // Debug.Log($"Found {Combinations.Count} available combinations");

            // Debug.Log("Sorting available combinations...");
            Combinations.Sort(UsesMoreSpaceComparator);
        }
        
        private int UsesMoreSpaceComparator(int[] a1, int[] a2)
        {
            float sum1 = 0;
            float sum2 = 0;
            foreach (float val in a1) sum1 += val;

            foreach (float val in a2) sum2 += val;

            return -1 * sum1.CompareTo(sum2);
        }
        
        private int BiggerSizeComparator(Driver d1, Driver d2)
        {
            int sizeComp = d1.Size.CompareTo(d2.Size);
            if (sizeComp == 0)
                return d1.Priority.CompareTo(d2.Priority);
            return sizeComp;
        }

        private void CombinationRepetitionUtil(int[] chosen, int[] arr,
            int index, int r, int start, int end)
        {
            if (index == r)
            {
                // combinations.Add(new[] {arr[chosen[0]]});
                var tempArr = new List<int>(r);
                for (var i = 0; i < r; i++) tempArr.Add(arr[chosen[i]]);
                var hasEnoughSpace = tempArr.Select(x => AvailableSizesCombinations[x]).Sum() <=
                                     ParkingManager.Height - 11;
                var contains5 = tempArr.Contains(4);
                tempArr.Sort();
                if (hasEnoughSpace && contains5)
                    Combinations.Add(tempArr.ToArray());

                return;
            }

            for (var i = start; i <= end; i++)
            {
                chosen[index] = i;
                CombinationRepetitionUtil(chosen, arr, index + 1,
                    r, i, end);
            }
        }

        private void CombinationRepetition(int[] arr, int n, int r)
        {
            var chosen = new int[r + 1];
            CombinationRepetitionUtil(chosen, arr, 0, r, 0, n - 1);
        }
    }
}