using AyCode.Core.Enums;
using AyCode.Core.Interfaces;
using AyCode.Utils.Extensions;

namespace AyCode.Core.Extensions
{
    public static class CollectionExtensions
    {
        public static void UpdateCollection<TDataItem>(this IList<TDataItem> source, IList<TDataItem> dataItems, bool isRemove) where TDataItem : IId<Guid>
        {
            if (source == null) throw new ArgumentNullException(nameof(source), $"source == null");
            if (dataItems == null) throw new ArgumentNullException(nameof(dataItems), $"dataItems == null");

            foreach (var dataItem in dataItems)
            {
                source.UpdateCollection(dataItem, isRemove);
            }
        }

        public static TrackingState UpdateCollection<TDataItem>(this IList<TDataItem> source, TDataItem dataItem, bool isRemove) where TDataItem : IId<Guid>
        {
            if (source == null) throw new ArgumentNullException(nameof(source), $"source == null");
            if (dataItem.Id.IsNullOrEmpty()) throw new ArgumentNullException(nameof(dataItem), "UpdateCollection->dataItem.Id.IsNullOrEmpty()");

            var index = source.FindIndex(x => x.Id == dataItem.Id);

            return source.UpdateCollectionByIndex(index, dataItem, isRemove);
        }

        public static TrackingState UpdateCollectionById<TId>(this IList<IId<TId>> source, IId<TId> dataItem, bool isRemove) where TId : struct 
        {
            if (source == null) throw new ArgumentNullException(nameof(source), $"source == null");
            
            var equalityComparer = EqualityComparer<TId>.Default;
            if (equalityComparer.Equals(dataItem.Id, default(TId))) throw new ArgumentNullException(nameof(dataItem), "UpdateCollection->dataItem.Id.IsNullOrEmpty()");

            var index = source.FindIndex(x => equalityComparer.Equals(x.Id, dataItem.Id));

            return source.UpdateCollectionByIndex(index, dataItem, isRemove);
        }

        public static TrackingState UpdateCollectionByIndex<TDataItem>(this IList<TDataItem> source, int index, TDataItem dataItem, bool isRemove)
        {
            if (isRemove)
            {
                if (index > -1) source.RemoveAt(index);

                return TrackingState.Remove;
            }

            if (index > -1)
            {
                source[index] = dataItem;
                return TrackingState.Update;
            }

            source.Add(dataItem);
            return TrackingState.Add;
        }

        public static int FindIndex<T>(this IList<T> list, Predicate<T> predicate)
        {
            for (var index = 0; index < list.Count; ++index)
            {
                if (predicate(list[index]))
                    return index;
            }

            return -1;
        }

        public static bool IsValidIndex<T>(this IList<T>? array, int index)
        {
            return array != null && index >= 0 && index < array.Count;
        }

        public static bool TryGetValue<T>(this IList<T> array, int index, out T? value)
        {
            if (array.IsValidIndex<T>(index))
            {
                value = array[index];
                return true;
            }

            value = default(T);
            return false;
        }

        public static int BinarySearch<TValue, TKey>(this IList<TValue> list, TKey key, Func<TValue, TKey> getKey) where TKey : IComparable
        {
            var index1 = 0;
            var index2 = list.Count - 1;

            while (index2 >= index1)
            {
                var obj1 = list[index1];
                if (getKey(obj1).CompareTo((object)key) == 0) return index1;

                var obj2 = list[index2];
                if (getKey(obj2).CompareTo((object)key) == 0) return index2;

                var index3 = (index1 + index2) / 2;
                var obj3 = list[index3];
                var num = getKey(obj3).CompareTo((object)key); 

                if (num == 0) return index3;

                if (num < 0)
                {
                    index1 = index3 + 1;
                    --index2;
                }
                else
                {
                    index2 = index3 - 1;
                    ++index1;
                }
            }

            return -1;
        }
    }
}
