#region License

/*
 * Copyright © 2002-2005 the original author or authors.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#endregion

#region Imports

using System;
using System.Collections;
using System.Data;
using Spring.Dao;
using Spring.Data.Common;

#endregion

namespace Spring.Data
{
	/// <summary>
    /// Helper class that can efficiently create multiple IDbCommand
    /// objects with different parameters based on a SQL statement and a single
    /// set of parameter declarations.
	/// </summary>
	/// <author>Mark Pollack (.NET)</author>
	public class IDbCommandCreatorFactory 
	{
		#region Fields

	    private String sql;
	    
	    private IDbParameters declaredParameters;
	    
	    private IDbProvider dbProvider;
	    
	    private CommandType commandType;
	    
		#endregion


		#region Constructor (s)
		/// <summary>
		/// Initializes a new instance of the <see cref="IDbCommandCreatorFactory"/> class.
        /// </summary>
	    public IDbCommandCreatorFactory(IDbProvider dbProvider, CommandType commandType, string sql, IDbParameters declaredParameters)
	    {
            this.dbProvider = dbProvider;
            this.sql = sql;	
		    this.commandType = commandType;
		    this.declaredParameters = declaredParameters;
	    }

	    #endregion

		#region Properties
	    
	    public IDbParameters DeclaredParameters
	    {
	        get
	        {
	            return declaredParameters;
	        }
	    }
	    
        public string Sql
        {
            get { return sql; }
        }

        public IDbProvider DbProvider
        {
            get { return dbProvider; }
        }

        public CommandType CommandType
        {
            get { return commandType; }
        }
		#endregion
	    
		#region Methods

        public IDbCommandCreator NewDbCommandCreatorWithParamValues(params object[] inParamValues)
        {
	        
            return new CommandCreatorWithParamValues(this, inParamValues != null ? ArrayList.Adapter(inParamValues) : new ArrayList());
        }


	    public IDbCommandCreator NewDbCommandCreator(IDictionary inParameters)
	    {
	        
	        return new CommandCreatorImpl(this, inParameters != null ? inParameters : new Hashtable());
	    }
	   
		#endregion

        private class CommandCreatorWithParamValues : IDbCommandCreator
        {
            private IDbCommandCreatorFactory factory;
            private IList inParamValues;
            
            public CommandCreatorWithParamValues(IDbCommandCreatorFactory factory, IList inParamValues)
            {
                this.factory = factory;                
                this.inParamValues = inParamValues;
            }

            public IDbCommand CreateDbCommand(IDbProvider provider)
            {
                IDbCommand command = factory.dbProvider.CreateCommand();
                command.CommandText = factory.sql;
                command.CommandType = factory.commandType;
                if (factory.declaredParameters != null)
                {
                    int parameterIndex = 0;
                    foreach ( IDbDataParameter declaredParameter in factory.declaredParameters.DataParameterCollection)
                    {
                        IDbDataParameter clonedParameter = (IDbDataParameter)((ICloneable)declaredParameter).Clone();
                        if (!(clonedParameter.Direction == ParameterDirection.Output) &&
                            !(clonedParameter.Direction == ParameterDirection.ReturnValue))
                        {
                            if (clonedParameter.Direction == ParameterDirection.InputOutput)
                            {
                                //heuristic for case when have output parameter from stored procedure discovery classified
                                //as in-out.  Assume out parameters come last in the stored proc definition and ignore
                                //setting values if there are not enough input values as passed in by the user.
                                if (parameterIndex < inParamValues.Count)
                                {
                                    //The value may still be null
                                    Object inValue = inParamValues[parameterIndex];
                                    clonedParameter.Value = (inValue == null) ? DBNull.Value : inValue;
                                } else
                                {
                                    //Since it thinks it is an input-output value and we have no value from the user - pass in null
                                    //as a workaround.
                                    clonedParameter.Value = DBNull.Value;
                                }

                            }
                            else
                            {
                                //The value may still be null
                                Object inValue = inParamValues[parameterIndex];
                                clonedParameter.Value = (inValue == null) ? DBNull.Value : inValue;
                            }
                            // only increment index if we could assign a value from the user specified inputs.
                            parameterIndex++;
                        }

                        command.Parameters.Add(clonedParameter);
                        
                    }
                }
                return command;
            }
            
        }
	    private class CommandCreatorImpl : IDbCommandCreator{

            private IDbCommandCreatorFactory factory;
	        private IDictionary inParams;
	        

	        public CommandCreatorImpl(IDbCommandCreatorFactory factory, IDictionary inParameters)
	        {
	            this.factory = factory;    
	            this.inParams = inParameters;
	        }

	        public IDbCommand CreateDbCommand(IDbProvider provider)
	        {
	            IDbCommand command = factory.dbProvider.CreateCommand();
	            command.CommandText = factory.sql;
	            command.CommandType = factory.commandType;
                if (factory.declaredParameters != null)
                {
                    foreach ( IDbDataParameter declaredParameter in factory.declaredParameters.DataParameterCollection)
                    {
                        IDbDataParameter clonedParameter = (IDbDataParameter)((ICloneable)declaredParameter).Clone();
                        
                        if (IsInputParamter(clonedParameter) && !inParams.Contains(clonedParameter.ParameterName))
                        {
                            throw new InvalidDataAccessApiUsageException("Required input parameter '" +
                                                                         clonedParameter.ParameterName + "' is missing");
                        }
                        //The value may still be null
//                        Object inValue =
//                            inParams[factory.dbProvider.CreateParameterName(clonedParameter.ParameterName)];

                        // TODO: The above code doesn't work as it duplicates parameter prefix, thus failing
                        // to obtain parameter values. The whole parameter handling mechanism should be refactored
                        // not to require parameter prefix to be specified when populating named parameters collection
                        // within AdoOperations. Code below is just a temporary workaround until this issue is resolved.
                        
                        Object inValue = inParams[clonedParameter.ParameterName];
                        if (!(clonedParameter.Direction == ParameterDirection.Output) &&
                            !(clonedParameter.Direction == ParameterDirection.ReturnValue))
                        {
                            clonedParameter.Value = (inValue == null) ? DBNull.Value : inValue;
                        }
                        
                        command.Parameters.Add(clonedParameter);
                    }
                }
	            return command;
	        }

            private bool IsInputParamter(IDataParameter parameter)
            {
                //Due to quirk in DeriveParameters can only verify Input parameter as output parameters
                //are incorrectly listed as InputOutput
                return (parameter.Direction == ParameterDirection.Input);
            }

	    }
	}
}
