using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using NHibernate.Engine;
using NHibernate.SqlCommand;
using NHibernate.SqlTypes;

namespace NHibernate.Driver
{
	public partial class BasicResultSetsCommand: IResultSetsCommand
	{
		private static readonly IInternalLogger log = LoggerProvider.LoggerFor(typeof(BasicResultSetsCommand));
		private SqlString sqlString = SqlString.Empty;

		public BasicResultSetsCommand(ISessionImplementor session)
		{
			Commands = new List<ISqlCommand>();
			Session = session;
		}

		protected List<ISqlCommand> Commands { get; private set; }

		protected ISessionImplementor Session { get; private set; }

		public virtual void Append(ISqlCommand command)
		{
			Commands.Add(command);
			sqlString = sqlString.Append(command.Query).Append(";").Append(Environment.NewLine);
		}

		public bool HasQueries
		{
			get { return Commands.Count > 0; }
		}

		public virtual SqlString Sql
		{
			get { return sqlString; }
		}

		public virtual DbDataReader GetReader(int? commandTimeout)
		{
			var batcher = Session.Batcher;
			SqlType[] sqlTypes = Commands.SelectMany(c => c.ParameterTypes).ToArray();
			ForEachSqlCommand((sqlLoaderCommand, offset) => sqlLoaderCommand.ResetParametersIndexesForTheCommand(offset));
			var command = batcher.PrepareQueryCommand(CommandType.Text, sqlString, sqlTypes);
			if (commandTimeout.HasValue)
			{
				command.CommandTimeout = commandTimeout.Value;
			}
			log.Info(command.CommandText);
			BindParameters(command);
			return BatcherDataReaderWrapper.Create(batcher, command);
		}

		protected virtual void BindParameters(DbCommand command)
		{
			var wholeQueryParametersList = Sql.GetParameters().ToList();
			ForEachSqlCommand((sqlLoaderCommand, offset) => sqlLoaderCommand.Bind(command, wholeQueryParametersList, offset, Session));
		}

		/// <summary>
		/// Execute the given <paramref name="actionToDo"/> for each command of the resultset.
		/// </summary>
		/// <param name="actionToDo">The action to perform where the first parameter is the <see cref="ISqlCommand"/> and the second parameter is the parameters offset of the .
		protected void ForEachSqlCommand(Action<ISqlCommand, int> actionToDo)
		{
			var singleQueryParameterOffset = 0;
			foreach (var sqlLoaderCommand in Commands)
			{
				actionToDo(sqlLoaderCommand, singleQueryParameterOffset);
				singleQueryParameterOffset += sqlLoaderCommand.ParameterTypes.Length;
			}
		}
	}

	/// <summary>
	/// Datareader wrapper with the same life cycle of its command (through the batcher)
	/// </summary>
	public partial class BatcherDataReaderWrapper: DbDataReader
	{
		private readonly IBatcher batcher;
		private readonly DbCommand command;
		private DbDataReader reader;

		protected BatcherDataReaderWrapper(IBatcher batcher, DbCommand command)
		{
			if (batcher == null)
			{
				throw new ArgumentNullException("batcher");
			}
			if (command == null)
			{
				throw new ArgumentNullException("command");
			}
			this.batcher = batcher;
			this.command = command;
		}

		public static BatcherDataReaderWrapper Create(IBatcher batcher, DbCommand command)
		{
			return new BatcherDataReaderWrapper(batcher, command)
			{
				reader = batcher.ExecuteReader(command)
			};
		}

		public override string GetName(int i)
		{
			return reader.GetName(i);
		}

		public override string GetDataTypeName(int i)
		{
			return reader.GetDataTypeName(i);
		}

		public override IEnumerator GetEnumerator()
		{
			return reader.GetEnumerator();
		}

		public override System.Type GetFieldType(int i)
		{
			return reader.GetFieldType(i);
		}

		public override object GetValue(int i)
		{
			return reader.GetValue(i);
		}

		public override int GetValues(object[] values)
		{
			return reader.GetValues(values);
		}

		public override int GetOrdinal(string name)
		{
			return reader.GetOrdinal(name);
		}

		public override bool GetBoolean(int i)
		{
			return reader.GetBoolean(i);
		}

		public override byte GetByte(int i)
		{
			return reader.GetByte(i);
		}

		public override long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length)
		{
			return reader.GetBytes(i, fieldOffset, buffer, bufferoffset, length);
		}

		public override char GetChar(int i)
		{
			return reader.GetChar(i);
		}

		public override long GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length)
		{
			return reader.GetChars(i, fieldoffset, buffer, bufferoffset, length);
		}

		public override Guid GetGuid(int i)
		{
			return reader.GetGuid(i);
		}

		public override short GetInt16(int i)
		{
			return reader.GetInt16(i);
		}

		public override int GetInt32(int i)
		{
			return reader.GetInt32(i);
		}

		public override long GetInt64(int i)
		{
			return reader.GetInt64(i);
		}

		public override float GetFloat(int i)
		{
			return reader.GetFloat(i);
		}

		public override double GetDouble(int i)
		{
			return reader.GetDouble(i);
		}

		public override string GetString(int i)
		{
			return reader.GetString(i);
		}

		public override decimal GetDecimal(int i)
		{
			return reader.GetDecimal(i);
		}

		public override DateTime GetDateTime(int i)
		{
			return reader.GetDateTime(i);
		}

		protected override DbDataReader GetDbDataReader(int ordinal)
		{
			return reader.GetData(ordinal);
		}

		public override bool IsDBNull(int i)
		{
			return reader.IsDBNull(i);
		}

		public override int FieldCount
		{
			get { return reader.FieldCount; }
		}

		public override bool HasRows
		{
			get { return reader.HasRows; }
		}

		public override object this[int i]
		{
			get { return reader[i]; }
		}

		public override object this[string name]
		{
			get { return reader[name]; }
		}

		public override bool Equals(object obj)
		{
			return reader.Equals(obj);
		}

		public override int GetHashCode()
		{
			return reader.GetHashCode();
		}

		public override void Close()
		{
			batcher.CloseCommand(command, reader);
		}

		public override DataTable GetSchemaTable()
		{
			return reader.GetSchemaTable();
		}

		public override bool NextResult()
		{
			return reader.NextResult();
		}

		public override bool Read()
		{
			return reader.Read();
		}

		public override Task<bool> ReadAsync(CancellationToken cancellationToken)
		{
			return reader.ReadAsync(cancellationToken);
		}

		public override Task<bool> IsDBNullAsync(int ordinal, CancellationToken cancellationToken)
		{
			return reader.IsDBNullAsync(ordinal, cancellationToken);
		}

		public override Task<bool> NextResultAsync(CancellationToken cancellationToken)
		{
			return reader.NextResultAsync(cancellationToken);
		}

		public override int Depth
		{
			get { return reader.Depth; }
		}

		public override bool IsClosed
		{
			get { return reader.IsClosed; }
		}

		public override int RecordsAffected
		{
			get { return reader.RecordsAffected; }
		}
	}
}