using System;
using System.Collections.Generic;
using VOID.Generic.Dict;
using System.Linq;
using Sirenix.Utilities;
using UnityEngine;
using VOID.Generic.Objects.Item;

namespace VOID.Generic.Util
{
    public static class ItemObjectSortUtil
    {
        public static void Sort(ref ItemObject[] items, Func<ItemObject, IComparable> valueGetter, SortDirectionType sortDirection)
        {
            if (items.IsNullOrEmpty()) return;

            // Find items that can be stacked and then merge them (empty leftovers will be destroyed and removed)
            StackGroups(GetStackGroups(items));

            Array.Sort(items, (item1, item2) =>
            {
                // Empty item slots always to the end
                if (!item1 && !item2) return 0;
                if (!item1) return 1;
                if (!item2) return -1;
                
                // Existing items sort by given value getter with given sort direction
                var result = valueGetter(item1).CompareTo(valueGetter(item2));
                if (sortDirection is SortDirectionType.Descending) result *= - 1;
                return result;
            });
        }

        /// <summary>Returns list of item groups that can be stacked together</summary>
        private static List<List<ItemObject>> GetStackGroups(ItemObject[] items)
        {
            var groups = new List<List<ItemObject>>();

            foreach (var item in items)
            {
                // Ignore: empty, non-stackable, those with max stack size already
                if (!item) continue;
                if (!item.itemData.stackable) continue;
                if (item.itemData.stackSize >= item.itemData.stackSizeMax) continue;

                // Each group has items that can be stacked together
                var group = groups.FirstOrDefault(group => group.First().CanStackWith(item));
                if (group == null)
                {
                    group = new List<ItemObject>();
                    groups.Add(group);
                }
                group.Add(item);
            }

            // Ignore groups with only 1 item
            return groups.Where(group => group.Count > 1).ToList();
        }

        /// <summary>
        /// 1. Stacks items together as tight as possible
        /// 2. Destroys and removes items that have no more stacks inside (by invoking event with Remove())
        /// </summary>
        private static void StackGroups(List<List<ItemObject>> groups)
        {
            foreach (var group in groups)
            {
                var stackSize = group.Sum(item => item.itemData.stackSize);
                var maxStackSize = group.First().itemData.stackSizeMax;

                var maxStackSizeItems = Mathf.FloorToInt((float)stackSize / maxStackSize);
                var remainingStackSize = stackSize % maxStackSize;
                
                // Items that will have 100% of its stack size
                group.Take(maxStackSizeItems).ForEach(item => item.itemData.stackSize = maxStackSize);
                
                // Remaining of stack will go to next item
                if (remainingStackSize > 0) group.Skip(maxStackSizeItems).First().itemData.stackSizeMax = maxStackSize;
                
                // IMPORTANT! Because we got ref to items here, event caused by Remove() should remove items from list itself
                // All other items are left without stacks, so they're "empty" - destroy and remove from list
                group.Skip(maxStackSizeItems).Skip(remainingStackSize > 0 ? 1 : 0).ForEach(item => item.Remove());
            }
        }
    }
}
