using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;

namespace VCommon.Collections
{
    public static class EnumerableExtensionMethod
    {
        /// <summary> 按提供的条件以及变换方法叠加序列  </summary>
        /// <typeparam name="TInput">序列元素类型</typeparam>
        /// <typeparam name="TStackedGroup">分组类型</typeparam>
        /// <param name="sequence">序列</param>
        /// <param name="groupingCondition">叠加条件</param>
        /// <param name="createStackGroup">分组创建方法,提供当前元素</param>
        /// <param name="addToGroup">分组叠加方法,提供分组和当前元素</param>
        public static TStackedGroup[] Stack<TInput, TStackedGroup>(this IEnumerable<TInput> sequence, Func<TInput, bool> groupingCondition, Func<TInput, TStackedGroup> createStackGroup, Action<TStackedGroup, TInput> addToGroup) where TStackedGroup : class
        {
            var list = new List<TStackedGroup>();

            TStackedGroup lasStackedGroup = null;
            foreach (var item in sequence)
            {
                var toGroup = groupingCondition(item);

                if (null != lasStackedGroup && toGroup)
                {
                    addToGroup(lasStackedGroup, item);
                }
                else if (toGroup)
                {
                    lasStackedGroup = createStackGroup(item);
                    list.Add(lasStackedGroup);
                }
                else
                {
                    lasStackedGroup = null;
                    list.Add(createStackGroup(item));
                }
            }

            return list.ToArray();
        }

        /// <summary>
        /// 将序列中连续且符合条件的元素,保留第一个其余移除
        /// </summary>
        /// <typeparam name="T">序列元素类型</typeparam>
        /// <param name="sequence">序列</param>
        /// <param name="stackCondition">移除条件</param>
        public static T[] StackSequence<T>(this IEnumerable<T> sequence, Func<T, bool> stackCondition) where T : class
        {
            return sequence.Stack(stackCondition, p => p, (a, b) => { });
        }

        public class Group<TKey, TElement>
        {
            private readonly List<TElement> _elements = new List<TElement>();
            public TElement[] Elements => _elements.ToArray();

            public Group(TKey key) => Key = key;

            public void Add(TElement element) => _elements.Add(element);

            public TKey Key { get; }
        }

        public static Group<TKey, TElement>[] GroupContiguous<TElement, TKey>(this TElement[] sequence, Func<TElement, TKey> keySelector) => GroupContiguous(sequence, keySelector, EqualityComparer<TKey>.Default);

        public static Group<TKey, TElement>[] GroupContiguous<TElement, TKey>(this TElement[] sequence, Func<TElement, TKey> keySelector, IEqualityComparer<TKey> comparer)
        {
            var result = new List<Group<TKey, TElement>>();
            Group<TKey, TElement> group = null;
            foreach (var element in sequence)
            {
                var key = keySelector(element);
                if (null == group || false == comparer.Equals(group.Key, key))
                {
                    group = new Group<TKey, TElement>(key);
                    result.Add(group);
                    group.Add(element);
                }
                else
                {
                    group.Add(element);
                }
            }
            return result.ToArray();
        }

        /// <summary> 包裹泛型IEnumerable实例, 避免可能的数组实例造成EF动态过滤失败 </summary>
        public static IEnumerable<T> WrapEnumerable<T>(this IEnumerable<T> source) => new EnumerableWrapper<T>(source);

        private class EnumerableWrapper<T> : IEnumerable<T>
        {
            private readonly IEnumerable<T> _underlyingInstance;

            public EnumerableWrapper(IEnumerable<T> underlyingInstance) => _underlyingInstance = underlyingInstance;

            IEnumerator<T> IEnumerable<T>.GetEnumerator() => _underlyingInstance.GetEnumerator();

            IEnumerator IEnumerable.GetEnumerator() => _underlyingInstance.GetEnumerator();
        }

        //public static HashSet<T> ToHashSet<T>(this IEnumerable<T> source) => new HashSet<T>(source);

        public static ConcurrentDictionary<TKey, TElement> ToConcurrentDictionary<TSource, TKey, TElement>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector, IEqualityComparer<TKey> comparer = null)
        {
            //REF: stackoverflow.com/a/27064366/2430943 with MODIFY

            if (source == null) throw new ArgumentNullException(nameof(source));
            if (keySelector == null) throw new ArgumentNullException(nameof(keySelector));
            if (elementSelector == null) throw new ArgumentNullException(nameof(elementSelector));

            var d = comparer == null
                ? new ConcurrentDictionary<TKey, TElement>()
                : new ConcurrentDictionary<TKey, TElement>(comparer);

            foreach (var element in source)
                if (false == d.TryAdd(keySelector(element), elementSelector(element)))
                    throw new ArgumentException("KeyDuplicated");

            return d;
        }
    }
}