using System;
using System.Data;
using DotNetMock;

namespace DotNetMock.Framework.Data
{
	/// <summary>
	/// This Mock Object implements the IDbCommand interface
	/// </summary>
	public class MockCommand : MockObject, IDbCommand, ICloneable
	{
		private ExpectationStringQueue _commandText = new ExpectationStringQueue("MockCommand.CommandText");
		private ExpectationValue _commandTimeout = new ExpectationValue("MockCommand.CommandTimeout");
		private ExpectationValue _commandType = new ExpectationValue("MockCommand.CommandType");
		private ExpectationCounter _executeCalls = new ExpectationCounter("MockCommand.ExecuteCalls");

		private Exception _executeException = null;
		private int _updateCount = 0;

		private MockDbConnection _connection = null;
		private MockTransaction _transaction = null;
		private MockDataParameterCollection _parameters = null;
		private MockDataReader _reader = null;

		private object _expectedScalar = null;

		private UpdateRowSource _updateRowSource = UpdateRowSource.None;
		/// <summary>
		/// Default constructor
		/// </summary>
		public MockCommand() : base()
		{
			_parameters = new MockDataParameterCollection();
		}
		/// <summary>
		/// Constructor that assigns given name to the mock
		/// </summary>
		/// <param name="mockName"></param>
		public MockCommand( string mockName ) : base(mockName)
		{
			_parameters = new MockDataParameterCollection();
		}

		#region Mock Methods
		/// <summary>
		/// Set update count to be returned from ExecuteNonQuery()
		/// </summary>
		/// <param name="count">Count to return</param>
		public void SetUpdateCount(int count)
		{
			_updateCount = count;
		}
		/// <summary>
		/// Set number of Execute calls to be expected
		/// </summary>
		/// <param name="calls">Calls to expect</param>
		public void SetExpectedExecuteCalls(int calls)
		{
			_executeCalls.Expected = calls;
		}
		/// <summary>
		/// Set Command Text to be expected
		/// </summary>
		/// <param name="commandText">Command Text to be expected</param>
		public void SetExpectedCommandText(string commandText)
		{
			_commandText.Expected = commandText;
		}
		/// <summary>
		/// Set Command Timeout to be expected
		/// </summary>
		/// <param name="commandTimeout">Timeout to expect</param>
		public void SetExpectedCommandTimeout(int commandTimeout)
		{
			_commandTimeout.Expected = commandTimeout;
		}
		/// <summary>
		/// Set Command Type to expected.  <see cref="System.Data.CommandType"/>
		/// </summary>
		/// <param name="commandType">Command Type to expect</param>
		public void SetExpectedCommandType(System.Data.CommandType commandType)
		{
			_commandType.Expected = commandType;
		}
		/// <summary>
		/// Add parameter to expected parameters
		/// </summary>
		/// <param name="parameter">Parameter to add</param>
		public void SetExpectedParameter(IDataParameter parameter)
		{
			_parameters.AddExpected(parameter);
		}
		/// <summary>
		/// Set expected Data Reader to return from ExecuteReader()
		/// </summary>
		/// <param name="reader">Mock Data Reader to use</param>
		public void SetExpectedReader(IDataReader reader)
		{
			_reader = (MockDataReader)reader;
		}
		/// <summary>
		/// Set exception to throw on execute calls
		/// </summary>
		/// <param name="exception">Exception to call</param>
		public void SetExecuteException(System.Exception exception)
		{
			_executeException = exception;
		}
		/// <summary>
		/// Sets the expected result to return from ExecuteScalar() calls.
		/// </summary>
		/// <param name="scalar">Result to return</param>
		public void SetExpectedScalar( object scalar ) 
		{
			_expectedScalar = scalar;
		}
		/// <summary>
		/// Private function called by all execute methods.  Serves two functions: 
		/// Increments the ExecuteCalls counter and throws an exception if one is set
		/// </summary>
		private void innerExecute()
		{
			_executeCalls.Inc();
			if (_executeException != null)
			{
				throw _executeException;
			}
		}
		#endregion

		#region Implementation of IDbCommand
		#region IDbCommand Methods
		/// <summary>
		/// Cancel the execution of the command.  Currently Not Implemented
		/// </summary>
		public void Cancel()
		{
			// Does nothing in Mock implementation		
		}
		/// <summary>
		/// Creates a compiled version of the command.  Currently Not Implemented
		/// </summary>
		public void Prepare()
		{
			// Does nothing in Mock implementation
		}
		/// <summary>
		/// Executes the command, with the given behavior, then returns a Data Reader of results.
		/// Currently, only CommandBehaviro.CloseConnection is supported.
		/// </summary>
		/// <param name="behavior">Command Behavior to use.  <see cref="System.Data.CommandBehavior"/></param>
		/// <returns></returns>
		public System.Data.IDataReader ExecuteReader(System.Data.CommandBehavior behavior)
		{
			IDataReader returnReader = ExecuteReader();
			if ( returnReader is MockDataReader )
			{
				if ( _connection != null ) 
				{
					((MockDataReader) returnReader).Connection = _connection;
					if ( behavior == CommandBehavior.CloseConnection )
					{
						((MockDataReader) returnReader).ShouldCloseConnectionOnReaderClose = true;
					}
				}
			}
			return returnReader;
		}
		/// <summary>
		/// Increments the number of execute calls and returns the MockDataReader previously setup.
		/// </summary>
		/// <returns>MockDataReader</returns>
		public System.Data.IDataReader ExecuteReader()
		{
			innerExecute();
			if ( _reader != null ) 
			{
				return _reader;
			} else
			{
				return new MockDataReader();
			}
		}
		/// <summary>
		/// Increments the number of execute calls and returns a null object
		/// </summary>
		/// <returns>Null</returns>
		public object ExecuteScalar()
		{
			innerExecute();
			return _expectedScalar;
		}
		/// <summary>
		/// Increments the number of execute calls and returns update count.
		/// </summary>
		/// <returns>Update Count</returns>
		public int ExecuteNonQuery()
		{
			innerExecute();
			return _updateCount;
		}
		/// <summary>
		/// Creates a new MockDataParameter
		/// </summary>
		/// <returns>MockDataParameter</returns>
		public System.Data.IDbDataParameter CreateParameter()
		{
			return (IDbDataParameter) new MockDataParameter();
		}
		#endregion

		#region IDbCommand Properties
		/// <summary>
		/// Gets/Sets the actual command type to use. 
		/// <see cref="System.Data.CommandType"/>
		/// </summary>
		public System.Data.CommandType CommandType
		{
			get
			{
				return (CommandType)_commandType.Actual;
			}
			set
			{
				_commandType.Actual = value;
			}
		}
		// TODO: Implement actual CommandTimeout usage
		/// <summary>
		/// Gets/Sets actual command timeout to use.  Note: Currently, only values of 0 are supported
		/// </summary>
		public int CommandTimeout
		{
			get
			{
				return (int)_commandTimeout.Actual;
			}
			set
			{
				_commandTimeout.Actual = value;
			}
		}
		/// <summary>
		/// Gets/Sets connection to associate with this command
		/// </summary>
		public System.Data.IDbConnection Connection
		{
			get
			{
				return _connection;
			}
			set
			{
				if (_connection != value)
				{
					this.Transaction = null;
				}
				_connection = (MockDbConnection)value;
			}
		}

		/// <summary>
		/// Gets/Sets how command results are applied to the <see cref="System.Data.DataRow">DataRow</see> 
		/// when used by the <see cref="System.Data.Common.DbDataAdapter.Update">Update</see> method
		/// of a <see cref="System.Data.Common.DbDataAdapter">DbDataAdapter</see>
		/// </summary>
		public System.Data.UpdateRowSource UpdatedRowSource
		{
			get
			{
				return _updateRowSource;
			}
			set
			{
				_updateRowSource = value;
			}
		}
		/// <summary>
		/// Gets/Sets actual command text for this command
		/// </summary>
		public string CommandText
		{
			get
			{
				return _commandText.Actual;
			}
			set
			{
				_commandText.Actual = value;
			}
		}
		/// <summary>
		/// Gets the MockDataParameterCollection
		/// </summary>
		public MockDataParameterCollection Parameters
		{
			get
			{
				return _parameters;
			}
		}
		/// <summary>
		/// Gets the <see cref="System.Data.IDataParameterCollection">IDataParameterCollection</see>
		/// </summary>
		System.Data.IDataParameterCollection IDbCommand.Parameters
		{
			get
			{
				return _parameters;
			}
		}
		// TODO: Takes steps to ensure that transaction is compatible with the current connection
		/// <summary>
		/// Gets/Sets current transaction associated with this command
		/// </summary>
		public System.Data.IDbTransaction Transaction
		{
			get
			{
				return _transaction;
			}
			set
			{
				_transaction = (MockTransaction)value;
			}
		}
		#endregion
		#endregion

		#region Implementation of IDisposable
		/// <summary>
		/// Releases all resources used by the MockCommand.
		/// </summary>
		public void Dispose() 
		{
			Dispose(true);
			GC.SuppressFinalize(this); 
		}
		/// <summary>
		/// Releases the unmanaged resources used by the MockCommand and optionally releases the managed resources.
		/// </summary>
		/// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
		protected virtual void Dispose(bool disposing) 
		{
			if (disposing) 
			{
				if (_connection != null)
					_connection.Dispose();
			}
		}
		/// <summary>
		/// Object Finalizer
		/// </summary>
		~MockCommand()
		{
			Dispose (false);
		}
		#endregion

		#region ICloneable Members
		/// <summary>
		/// Creates a new object that is a copy of the current instance.
		/// </summary>
		/// <returns>A new object that is a copy of this instance.</returns>
		public object Clone()
		{
			return this.MemberwiseClone();
		}
		#endregion
	}
}
