using System;

namespace MemoryPools.Collections.Linq
{
    internal class SelectManyExprWithContextEnumerable<T, TR, TContext> : IPoolingEnumerable<TR>
    {
        private IPoolingEnumerable<T> _src;
        private Func<T, TContext, IPoolingEnumerable<TR>> _mutator;
        private int _count;
        private TContext _context;

        public SelectManyExprWithContextEnumerable<T, TR, TContext> Init(
            IPoolingEnumerable<T> src, 
            Func<T, TContext, IPoolingEnumerable<TR>> mutator,
            TContext context)
        {
            _src = src;
            _count = 0;
            _context = context;
            _mutator = mutator;
            return this;
        }

        public IPoolingEnumerator<TR> GetEnumerator()
        {
            _count++;
            return Pool<SelectManyExprWithContextEnumerator>.Get().Init(this, _src.GetEnumerator(), _mutator, _context);
        }

        private void Dispose()
        {
            if (_count == 0) return;
            _count--;
            if (_count == 0)
            {
                _src = default;
                _count = 0;
                _context = default;
                _mutator = default;
                Pool<SelectManyExprWithContextEnumerable<T, TR, TContext>>.Return(this);
            }
        }

        internal class SelectManyExprWithContextEnumerator : IPoolingEnumerator<TR>
        {
            private TContext _context;
            private Func<T, TContext, IPoolingEnumerable<TR>> _mutator;
            private SelectManyExprWithContextEnumerable<T, TR, TContext> _parent;
            private IPoolingEnumerator<T> _src;
            private IPoolingEnumerator<TR> _currentEnumerator;
            private bool _finished;
            
            public SelectManyExprWithContextEnumerator Init(
                SelectManyExprWithContextEnumerable<T, TR, TContext> parent,
                IPoolingEnumerator<T> src,
                Func<T, TContext, IPoolingEnumerable<TR>> mutator,
                TContext context) 
            {
                _src = src;
                _finished = false;
                _parent = parent;
                _mutator = mutator;
                _context = context;
                _currentEnumerator = default;
                return this;
            }

            public bool MoveNext()
            {
                if (_finished) return false;
                if (_currentEnumerator == default)
                {
                    if (!_src.MoveNext())
                    {
                        _finished = true;
                        return false;
                    }
                    _currentEnumerator = _mutator(_src.Current, _context).GetEnumerator();
                }

                do
                {
                    var hasValue = _currentEnumerator.MoveNext();
                    if (hasValue) return true;
                    if (!_src.MoveNext())
                    {
                        _finished = true;
                        return false;
                    }
                    _currentEnumerator?.Dispose();
                    _currentEnumerator = _mutator(_src.Current, _context).GetEnumerator();
                } while (true);
            }

            public void Reset()
            {
                _currentEnumerator?.Dispose();
                _currentEnumerator = default;
                _src.Reset();
            }

            object IPoolingEnumerator.Current => Current;

            public TR Current => _currentEnumerator.Current;

            public void Dispose()
            {
                _currentEnumerator?.Dispose();
                _currentEnumerator = default;
	            
                _parent?.Dispose();
                _parent = default;
        		
                _src.Dispose();
                _src = default;
        		
                Pool<SelectManyExprWithContextEnumerator>.Return(this);
            }
        }

        IPoolingEnumerator IPoolingEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }
}