using System;
using System.Collections;
using System.Data;
using System.Data.Common;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
namespace WeilandYutani.Database.Utilities
{
    public class RowNumberDataReader : DbDataReader
    {
        public RowNumberDataReader(DbDataReader inner)
        {
            Inner = inner;
            RowNumberOrdinal = Inner.FieldCount;
            if (GetColumnNames().Any(n => n == RowNumberColumnName))
            {
                var id = Guid.NewGuid().ToString("N");
                RowNumberColumnName = $"RowNumber{id}";
            }
            IEnumerable<string> GetColumnNames()
            {
                for (int i = 0; i < Inner.FieldCount; i++)
                    yield return GetName(i);
            }
        }
        ulong _rowNumber = 0;
        public DbDataReader Inner { get; private set; }
        public string RowNumberColumnName { get; private set; } = "RowNumber";
        public int RowNumberOrdinal { get; private set; }
        public override object this[int ordinal] => ordinal == RowNumberOrdinal ? _rowNumber : Inner[ordinal];
        public override object this[string name] => name == RowNumberColumnName ? _rowNumber : Inner[name];
        public override int Depth => Inner.Depth;
        public override int FieldCount => Inner.FieldCount + 1;
        public override bool HasRows => Inner.HasRows;
        public override bool IsClosed => Inner.IsClosed;
        public override int RecordsAffected => Inner.RecordsAffected;
        public override bool GetBoolean(int ordinal) => GetInnerOrThrow(ordinal, Inner.GetBoolean);
        public override byte GetByte(int ordinal) => GetInnerOrRowNumber(ordinal, Inner.GetByte, (byte)_rowNumber);
        public override char GetChar(int ordinal) => GetInnerOrThrow((int)ordinal, Inner.GetChar);
        public override DateTime GetDateTime(int ordinal) => GetInnerOrThrow(ordinal, Inner.GetDateTime);
        public override decimal GetDecimal(int ordinal) => GetInnerOrRowNumber(ordinal, Inner.GetDecimal, _rowNumber);
        public override double GetDouble(int ordinal) => GetInnerOrRowNumber(ordinal, Inner.GetDouble, _rowNumber);
        public override float GetFloat(int ordinal) => GetInnerOrRowNumber(ordinal, Inner.GetFloat, _rowNumber);
        public override Guid GetGuid(int ordinal) => GetInnerOrThrow(ordinal, Inner.GetGuid);
        public override short GetInt16(int ordinal) => GetInnerOrRowNumber(ordinal, Inner.GetInt16, (short)_rowNumber);
        public override int GetInt32(int ordinal) => GetInnerOrRowNumber(ordinal, Inner.GetInt32, (int)_rowNumber);
        public override long GetInt64(int ordinal) => GetInnerOrRowNumber(ordinal, Inner.GetInt64, (long)_rowNumber);
        public override string GetName(int ordinal) => GetInnerOrRowNumber(ordinal, Inner.GetName, RowNumberColumnName);
        public override int GetOrdinal(string name) => name == RowNumberColumnName ? RowNumberOrdinal : Inner.GetOrdinal(name);
        public override string GetString(int ordinal) => GetInnerOrRowNumber(ordinal, Inner.GetString, _rowNumber.ToString());
        public override object GetValue(int ordinal) => GetInnerOrRowNumber(ordinal, Inner.GetValue, _rowNumber);
        public override bool IsDBNull(int ordinal) => GetInnerOrRowNumber(ordinal, Inner.IsDBNull, false);
        public override IEnumerator GetEnumerator() => new DbEnumerator(this);
        public override bool NextResult() => IncrementRowNumberAndDo(Inner.NextResult);
        public override bool Read() => IncrementRowNumberAndDo(Inner.Read);
        [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)]
        public override Type GetFieldType(int ordinal) => GetInnerOrRowNumber(ordinal, Inner.GetFieldType, _rowNumber.GetType());
        public override string GetDataTypeName(int ordinal) => GetInnerOrRowNumber(ordinal, Inner.GetDataTypeName, _rowNumber.GetType().Name);
        public override long GetBytes(int ordinal, long dataOffset, byte[]? buffer, int bufferOffset, int length)
            => GetInnerOrThrow(ordinal, () => Inner.GetBytes(ordinal, dataOffset, buffer, bufferOffset, length));
        public override long GetChars(int ordinal, long dataOffset, char[]? buffer, int bufferOffset, int length)
            => GetInnerOrThrow(ordinal, () => Inner.GetChars(ordinal, dataOffset, buffer, bufferOffset, length));
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        T GetInnerOrThrow<T>(int ordinal, Func<int, T> func)
            => GetInnerOrThrow<T>(ordinal, () => func(ordinal));
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        T GetInnerOrThrow<T>(int ordinal, Func<T> func)
            => ordinal < RowNumberOrdinal ? func() : throw new InvalidOperationException();
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        T GetInnerOrRowNumber<T>(int ordinal, Func<int, T> func, T rowNumberValue)
            => ordinal < RowNumberOrdinal ? func(ordinal) : rowNumberValue;
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        T IncrementRowNumberAndDo<T>(Func<T> func)
        {
            _rowNumber++;
            return func();
        }
        public override int GetValues(object[] values)
        {
            values[RowNumberOrdinal] = _rowNumber;
            return Inner.GetValues(values) + 1;
        }
    }
}