using System;
using System.Collections.Generic;
using MemoryPools.Collections.Specialized;

namespace MemoryPools.Collections.Linq
{
    internal sealed class GroupedResultEnumerable<TSource, TKey, TResult> : IPoolingEnumerable<TResult>
    {
        private IPoolingEnumerable<TSource> _source;
        private Func<TSource, TKey> _keySelector;
        private Func<TKey, IPoolingEnumerable<TSource>, TResult> _resultSelector;
        private IEqualityComparer<TKey> _comparer;
        private int _count;

        public GroupedResultEnumerable<TSource, TKey, TResult> Init(
            IPoolingEnumerable<TSource> source,
            Func<TSource, TKey> keySelector,
            Func<TKey, IPoolingEnumerable<TSource>, TResult> resultSelector,
            IEqualityComparer<TKey> comparer)
        {
            _source = source ?? throw new ArgumentNullException(nameof(source));
            _keySelector = keySelector ?? throw new ArgumentNullException(nameof(keySelector));
            _resultSelector = resultSelector ?? throw new ArgumentNullException(nameof(resultSelector));
            _comparer = comparer ?? EqualityComparer<TKey>.Default;
            _count = 0;
            return this;
        }

        public IPoolingEnumerator<TResult> GetEnumerator()
        {
            var tmpDict = Pool<PoolingDictionary<TKey, PoolingGrouping>>.Get().Init(0, _comparer);
            
            PoolingGrouping grp;
            foreach (var item in _source)
            {
                var key = _keySelector(item);
                if (!tmpDict.TryGetValue(key, out grp))
                {
                    tmpDict[key] = grp = Pool<PoolingGrouping>.Get().Init(key);
                }

                grp.InternalList.Add(item);
            }

            _count++;
            return Pool<GroupedResultEnumerator>.Get().Init(this, tmpDict);
        }

        private void Dispose()
        {
            if (_count == 0) return;
            _count--;
            
            if (_count == 0)
            {
                _comparer = default;
                _resultSelector = default;
                _keySelector = default;
                Pool<GroupedResultEnumerable<TSource, TKey, TResult>>.Return(this);
            }
        }
        
        IPoolingEnumerator IPoolingEnumerable.GetEnumerator() => GetEnumerator();
    
        internal class GroupedResultEnumerator : IPoolingEnumerator<TResult>
        {
            private PoolingDictionary<TKey, PoolingGrouping> _src;
            private GroupedResultEnumerable<TSource, TKey, TResult> _parent;
            private IPoolingEnumerator<KeyValuePair<TKey, PoolingGrouping>> _enumerator;
            
            public GroupedResultEnumerator Init(
                GroupedResultEnumerable<TSource, TKey, TResult> parent,
                PoolingDictionary<TKey, PoolingGrouping> src)
            {
                _src = src;
                _parent = parent;
                _enumerator = _src.GetEnumerator();
                return this;
            }

            public void Dispose()
            {
                // Cleanup contents
                foreach (var grouping in _src)
                {
                    grouping.Value.Dispose();
                    Pool<PoolingGrouping>.Return(grouping.Value);
                }
                
                // cleanup collection
                _src?.Dispose();
                Pool<PoolingDictionary<TKey, PoolingGrouping>>.Return(_src);
                _src = default;
                
                _enumerator?.Dispose();
                _enumerator = default;
                
                _parent?.Dispose();
                _parent = default;
                
                Pool<GroupedResultEnumerator>.Return(this);
            }

            public bool MoveNext() => _enumerator.MoveNext();

            public void Reset() => _enumerator.Reset();

            public TResult Current => _parent._resultSelector(_enumerator.Current.Key, _enumerator.Current.Value.InternalList);

            object IPoolingEnumerator.Current => Current;
        }

        internal class PoolingGrouping : IPoolingGrouping<TKey, TSource>, IDisposable
        {
            private PoolingList<TSource> _elements;

            public PoolingGrouping Init(TKey key)
            {
                _elements = Pool<PoolingList<TSource>>.Get().Init();
                Key = key;
                return this;
            }

            internal PoolingList<TSource> InternalList => _elements;

            public IPoolingEnumerator<TSource> GetEnumerator() => _elements.GetEnumerator();

            IPoolingEnumerator IPoolingEnumerable.GetEnumerator() => GetEnumerator();

            public TKey Key { get; private set; }

            public void Dispose()
            {
                _elements?.Dispose();
                Pool<PoolingList<TSource>>.Return(_elements);
                _elements = null;
                
                Key = default;
            }
        }
    }
}