namespace IMLibrary.MySqlData
{
    using System;
    using System.Collections;
    using System.Data;
    using MySql.Data;
    using MySql.Data.MySqlClient;

    /// <summary>
    /// 快速缓冲区参数类
    /// </summary>
    public sealed class DataAccessParameterCache
    {
        private static Hashtable paramCache = Hashtable.Synchronized(new Hashtable());
        private static Hashtable paramDirections = Hashtable.Synchronized(new Hashtable());
        private static Hashtable paramTypes = Hashtable.Synchronized(new Hashtable());

        static DataAccessParameterCache()
        {
            paramTypes.Add("CHAR".ToLower() , MySqlDbType.String);
            paramTypes.Add("VARCHAR".ToLower(), MySqlDbType.VarChar);
            paramTypes.Add("DATE".ToLower(), MySqlDbType.Date);
            paramTypes.Add("DATETIME".ToLower(), MySqlDbType.DateTime);
            paramTypes.Add("NUMERIC".ToLower(), MySqlDbType.Decimal);
            paramTypes.Add("DEC".ToLower(), MySqlDbType.Decimal);
            paramTypes.Add("FIXED".ToLower(), MySqlDbType.Decimal);
            //if (connection.driver.Version.isAtLeast(5, 0, 3))
            //    return MySqlDbType.NewDecimal;
            //else
            //    return MySqlDbType.Decimal;
            paramTypes.Add("YEAR".ToLower(), MySqlDbType.Year);
            paramTypes.Add("TIME".ToLower(), MySqlDbType.Time);
            paramTypes.Add("TIMESTAMP".ToLower(), MySqlDbType.Timestamp);
            paramTypes.Add("SET".ToLower(), MySqlDbType.Set);
            paramTypes.Add("ENUM".ToLower(), MySqlDbType.Enum);
            paramTypes.Add("BIT".ToLower(), MySqlDbType.Bit);
            paramTypes.Add("TINYINT".ToLower(), MySqlDbType.Byte);
            //return unsigned ? MySqlDbType.UByte : MySqlDbType.Byte;
            paramTypes.Add("BOOL".ToLower(), MySqlDbType.Byte);
            paramTypes.Add("BOOLEAN".ToLower(), MySqlDbType.Byte);
            paramTypes.Add("SMALLINT".ToLower(), MySqlDbType.Int16);
            //return unsigned ? MySqlDbType.UInt16 : MySqlDbType.Int16;
            paramTypes.Add("MEDIUMINT".ToLower(), MySqlDbType.Int24);
            //return unsigned ? MySqlDbType.UInt24 : MySqlDbType.Int24;
            paramTypes.Add("INT".ToLower(), MySqlDbType.Int32);
            paramTypes.Add("INTEGER".ToLower(), MySqlDbType.Int32);
            //return unsigned ? MySqlDbType.UInt32 : MySqlDbType.Int32; 
            paramTypes.Add("SERIAL".ToLower(), MySqlDbType.UInt64);
            paramTypes.Add("BIGINT".ToLower(), MySqlDbType.Int64);
            //return unsigned ? MySqlDbType.UInt64 : MySqlDbType.Int64;

            paramTypes.Add("FLOAT".ToLower(), MySqlDbType.Float);
            paramTypes.Add("DOUBLE".ToLower(), MySqlDbType.Double);
            //realAsFloat? MySqlDbType.Float: MySqlDbType.Double;

            paramTypes.Add("TEXT".ToLower(), MySqlDbType.Text);
            paramTypes.Add("BLOB".ToLower(), MySqlDbType.Blob);
            paramTypes.Add("LONGBLOB".ToLower(), MySqlDbType.LongBlob);
            paramTypes.Add("LONGTEXT".ToLower(), MySqlDbType.LongText);
            paramTypes.Add("MEDIUMBLOB".ToLower(), MySqlDbType.MediumBlob);
            paramTypes.Add("MEDIUMTEXT".ToLower(), MySqlDbType.MediumText);
            paramTypes.Add("TINYBLOB".ToLower(), MySqlDbType.TinyBlob);
            paramTypes.Add("TINYTEXT".ToLower(), MySqlDbType.TinyText);
            paramTypes.Add("BINARY".ToLower(), MySqlDbType.Binary);
            paramTypes.Add("VARBINARY".ToLower(), MySqlDbType.VarBinary);
            

            #region sqlserver
            //paramTypes.Add("binary", SqlDbType.Binary);
            //paramTypes.Add("bit", SqlDbType.Bit);
            //paramTypes.Add("char", SqlDbType.Char);
            //paramTypes.Add("datetime", SqlDbType.DateTime);
            //paramTypes.Add("decimal", SqlDbType.Decimal);
            //paramTypes.Add("float", SqlDbType.Float);
            //paramTypes.Add("image", SqlDbType.Image);
            //paramTypes.Add("int", SqlDbType.Int);
            //paramTypes.Add("money", SqlDbType.Money);
            //paramTypes.Add("nchar", SqlDbType.NChar);
            //paramTypes.Add("ntext", SqlDbType.NText);
            //paramTypes.Add("numeric", SqlDbType.Decimal);
            //paramTypes.Add("nvarchar", SqlDbType.NVarChar);
            //paramTypes.Add("real", SqlDbType.Real);
            //paramTypes.Add("smalldatetime", SqlDbType.SmallDateTime);
            //paramTypes.Add("smallint", SqlDbType.SmallInt);
            //paramTypes.Add("smallmoney", SqlDbType.SmallMoney);
            //paramTypes.Add("sql_variant", SqlDbType.Variant);
            //paramTypes.Add("text", SqlDbType.Text);
            //paramTypes.Add("timestamp", SqlDbType.Timestamp);
            //paramTypes.Add("tinyint", SqlDbType.TinyInt);
            //paramTypes.Add("uniqueidentifier", SqlDbType.UniqueIdentifier);
            //paramTypes.Add("varbinary", SqlDbType.VarBinary);
            //paramTypes.Add("varchar", SqlDbType.VarChar);
            #endregion 

            paramDirections.Add((short) 1, ParameterDirection.Input);
            paramDirections.Add((short) 2, ParameterDirection.InputOutput);
            paramDirections.Add((short) 4, ParameterDirection.ReturnValue);
        }

        private DataAccessParameterCache()
        {

        }

        /// <summary>
        /// 参数设置
        /// </summary>
        /// <param name="connectionString">联接字符串</param>
        /// <param name="commandText">命令文件</param>
        /// <param name="commandParameters">命令参数</param>
        public static void CacheParameterSet(string connectionString, string commandText, params MySqlParameter[] commandParameters)
        {
            string text = connectionString + ":" + commandText;
            paramCache[text] = commandParameters;
        }

        private static MySqlParameter[] CloneParameters(MySqlParameter[] originalParameters)
        {
            MySqlParameter[] parameterArray = new MySqlParameter[originalParameters.Length];
            int index = 0;
            int length = originalParameters.Length;
            while (index < length)
            {
                parameterArray[index] = (MySqlParameter) ((ICloneable) originalParameters[index]).Clone();
                index++;
            }
            return parameterArray;
        }

        private static MySqlParameter[] DiscoverSpParameterSet(string connectionString, string spName, bool includeReturnValueParameter)
        {
            MySqlParameter[] parameterArray;
            int num;
            DataTable dataTable = new DataTable("paramDescriptions");
            using (MySqlConnection connection = new MySqlConnection(connectionString))
            {
                connection.Open();
                MySqlCommand selectCommand = new MySqlCommand(string.Format("select * from information_schema.PARAMETERS where SPECIFIC_NAME='{0}' and SPECIFIC_SCHEMA='{1}'", spName, connection.Database), connection);
                selectCommand.CommandType = CommandType.Text;
                new MySqlDataAdapter   (selectCommand).Fill(dataTable);
            }
            if (dataTable.Rows.Count <= 0)
            {
                throw new ArgumentException("Stored procedure '" + spName + "' not found", "spName");
            }
            //if (includeReturnValueParameter)
            {
                parameterArray = new MySqlParameter[dataTable.Rows.Count];
                num = 0;
            }
            //else
            //{
            //    parameterArray = new MySqlParameter[dataTable.Rows.Count - 1];
            //    num = 1;
            //}
            int index = 0;
            int length = parameterArray.Length;
            while (index < length)
            {
                DataRow row = dataTable.Rows[index + num];
                parameterArray[index] = new MySqlParameter();
                parameterArray[index].ParameterName = (string)row["PARAMETER_NAME"];
                parameterArray[index].MySqlDbType =   (MySqlDbType)paramTypes[(string)row["DATA_TYPE"]];

                string mode = row["PARAMETER_MODE"].ToString().ToUpper();
                if (mode == "IN")
                    parameterArray[index].Direction = ParameterDirection.Input; 
                else if (mode == "OUT")
                    parameterArray[index].Direction = ParameterDirection.Output;
                else if (mode == "INOUT")
                    parameterArray[index].Direction = ParameterDirection.InputOutput;
                else 
                    parameterArray[index].Direction = ParameterDirection.ReturnValue;

                parameterArray[index].Size = (row["CHARACTER_OCTET_LENGTH"] == DBNull.Value) ? 0 : ((int)row["CHARACTER_OCTET_LENGTH"]);
                var NUMERIC_PRECISION = row["NUMERIC_PRECISION"];
                if (NUMERIC_PRECISION == DBNull.Value)
                    parameterArray[index].Precision = (byte)0;
                else
                    parameterArray[index].Precision = Convert.ToByte(NUMERIC_PRECISION);  
                 
                var NUMERIC_SCALE = row["NUMERIC_SCALE"];
                if (NUMERIC_SCALE == DBNull.Value)
                    parameterArray[index].Scale = (byte)0;
                else
                    parameterArray[index].Scale = Convert.ToByte(NUMERIC_SCALE);   

                //parameterArray[index].ParameterName = (string) row["PARAMETER_NAME"];
                //parameterArray[index].MySqlDbType = (MySqlDbType) paramTypes[(string) row["TYPE_NAME"]];
                //parameterArray[index].Direction = (ParameterDirection) paramDirections[(short) row["PARAMETER_TYPE"]];
                //parameterArray[index].Size = (row["CHARACTER_OCTET_LENGTH"] == DBNull.Value) ? 0 : ((int) row["CHARACTER_OCTET_LENGTH"]);
                //parameterArray[index].Precision = (row["NUMERIC_PRECISION"] == DBNull.Value) ? ((byte) 0) : ((byte) ((short) row["NUMERIC_PRECISION"]));
                //parameterArray[index].Scale = (row["NUMERIC_SCALE"] == DBNull.Value) ? ((byte) 0) : ((byte) ((short) row["NUMERIC_SCALE"]));
                index++;
            }
           
            return parameterArray;
        }

        /// <summary>
        /// 获取参数设置 
        /// </summary>
        /// <param name="connectionString">联接字符串</param>
        /// <param name="commandText">命令文本</param>
        /// <returns></returns>
        public static MySqlParameter[] GetCachedParameterSet(string connectionString, string commandText)
        {
            string text = connectionString + ":" + commandText;
            MySqlParameter[] originalParameters = (MySqlParameter[]) paramCache[text];
            if (originalParameters == null)
            {
                return null;
            }
            return CloneParameters(originalParameters);
        }

        /// <summary>
        /// 获取存储过程参数
        /// </summary>
        /// <param name="connectionString">连接字符串</param>
        /// <param name="spName">存储过程名称</param>
        /// <returns></returns>
        public static MySqlParameter[] GetSpParameterSet(string connectionString, string spName)
        {
            return GetSpParameterSet(connectionString, spName, false);
        }

        /// <summary>
        /// 获取存储过程参数
        /// </summary>
        /// <param name="connectionString">连接字符串</param>
        /// <param name="spName">存储过程名称</param>
        /// <param name="includeReturnValueParameter">是否包括返回值参数</param>
        /// <returns></returns>
        public static MySqlParameter[] GetSpParameterSet(string connectionString, string spName, bool includeReturnValueParameter)
        {
            string text = connectionString + ":" + spName + (includeReturnValueParameter ? ":include ReturnValue Parameter" : "");
            MySqlParameter[] originalParameters = (MySqlParameter[]) paramCache[text];
            if (originalParameters == null)
            {
                object obj2;
                paramCache[text] = obj2 = DiscoverSpParameterSet(connectionString, spName, includeReturnValueParameter);
                originalParameters = (MySqlParameter[]) obj2;
            }
            return CloneParameters(originalParameters);
        }
    }
}

