using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace RDH.Data
{
    public class GridReader : IDisposable
    {
        private IDataReader reader;
        private IDbCommand command;
        private Identity identity;

        internal GridReader(IDbCommand command, IDataReader reader, Identity identity)
        {
            this.command = command;
            this.reader = reader;
            this.identity = identity;
        }
        /// <summary>
        /// Read the next grid of results
        /// </summary>
        public IEnumerable<T> Read<T>()
        {
            if (reader == null) throw new ObjectDisposedException(GetType().Name);
            if (consumed) throw new InvalidOperationException("Each grid can only be iterated once");
            var typedIdentity = identity.ForGrid(typeof(T), gridIndex);
            CacheInfo cache = SqlMapper.GetCacheInfo(typedIdentity);
            var deserializer = cache.Deserializer;

            Func<Func<IDataReader, object>> deserializerGenerator = () =>
            {
                deserializer = SqlMapper.GetDeserializer(typeof(T), reader, 0, -1, false);
                cache.Deserializer = deserializer;
                return deserializer;
            };

            if (deserializer == null)
            {
                deserializer = deserializerGenerator();
            }
            consumed = true;
            return ReadDeferred<T>(gridIndex, deserializer, typedIdentity, deserializerGenerator);
        }

        private IEnumerable<TReturn> MultiReadInternal<TFirst, TSecond, TThird, TFourth, TFifth, TSixth,TSeventh,TReturn>(object func, string splitOn)
        {

            var identity = this.identity.ForGrid(typeof(TReturn), new Type[] { 
                    typeof(TFirst), 
                    typeof(TSecond),
                    typeof(TThird),
                    typeof(TFourth),
                    typeof(TFifth),
                     typeof(TSixth),
                      typeof(TSeventh)
                }, gridIndex);
            try
            {
                foreach (var r in SqlMapper.MultiMapImpl<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(null, null, func, null, null, splitOn, null, null, reader, identity))
                {
                    yield return r;
                }
            }
            finally
            {
                NextResult();
            }
        }

#if CSHARP30
        public IEnumerable<TReturn> Read<TFirst, TSecond, TReturn>(Func<TFirst, TSecond, TReturn> func, string splitOn)
#else
            public IEnumerable<TReturn> Read<TFirst, TSecond, TReturn>(Func<TFirst, TSecond, TReturn> func, string splitOn = "id")
#endif
        {
            return MultiReadInternal<TFirst, TSecond, DontMap,DontMap, DontMap, DontMap, DontMap, TReturn>(func, splitOn);
        }

#if CSHARP30
        public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TReturn>(Func<TFirst, TSecond, TThird, TReturn> func, string splitOn)
#else
            public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TReturn>(Func<TFirst, TSecond, TThird, TReturn> func, string splitOn = "id")
#endif
        {
            return MultiReadInternal<TFirst, TSecond, TThird, DontMap,DontMap, DontMap, DontMap, TReturn>(func, splitOn);
        }

#if CSHARP30
        public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TFourth, TReturn>(Func<TFirst, TSecond, TThird, TFourth, TReturn> func, string splitOn)
#else
            public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TFourth, TReturn>(Func<TFirst, TSecond, TThird, TFourth, TReturn> func, string splitOn = "id")
#endif
        {
            return MultiReadInternal<TFirst, TSecond, TThird, TFourth, DontMap, DontMap, DontMap, TReturn>(func, splitOn);
        }

#if CSHARP30
        public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TFourth, TReturn>(Func<TFirst, TSecond, TThird, TFourth, TReturn> func, string splitOn)
#else
            public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(Func<TFirst, TSecond, TThird, TFourth, TFifth,TReturn> func, string splitOn = "id")
#endif
            {
                return MultiReadInternal<TFirst, TSecond, TThird, TFourth, TFifth, DontMap, DontMap, TReturn>(func, splitOn);
            }

#if CSHARP30
        public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TFourth, TReturn>(Func<TFirst, TSecond, TThird, TFourth, TReturn> func, string splitOn)
#else
            public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TFourth, TFifth,TSixth, TReturn>(Func<TFirst, TSecond, TThird, TFourth, TFifth,TSixth, TReturn> func, string splitOn = "id")
#endif
            {
                return MultiReadInternal<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, DontMap, TReturn>(func, splitOn);
            }


#if !CSHARP30
            public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(Func<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn> func, string splitOn = "id")
            {
                return MultiReadInternal<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(func, splitOn);
            }
#endif

        private IEnumerable<T> ReadDeferred<T>(int index, Func<IDataReader, object> deserializer, Identity typedIdentity, Func<Func<IDataReader, object>> deserializerGenerator)
        {
            try
            {
                while (index == gridIndex && reader.Read())
                {
                    object next;
                    try
                    {
                        next = deserializer(reader);
                    }
                    catch (DataException)
                    {
                        deserializer = deserializerGenerator();
                        next = deserializer(reader);
                    }
                    yield return (T)next;
                }
            }
                finally // finally so that First etc progresses things even when multiple rows
            {
                if (index == gridIndex)
                {
                    NextResult();
                }
            }
        }
        private int gridIndex;
        private bool consumed;
        private void NextResult()
        {
            if (reader.NextResult())
            {
                gridIndex++;
                consumed = false;
            }
            else
            {
                Dispose();
            }

        }
        public void Dispose()
        {
            if (reader != null)
            {
                reader.Dispose();
                reader = null;
            }
            if (command != null)
            {
                command.Dispose();
                command = null;
            }
        }
    }
}
