//------------------------------------------------------------------------------
// <copyright file="OleDbParameter.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
// <owner current="true" primary="true">[....]</owner>
// <owner current="true" primary="false">[....]</owner>
//------------------------------------------------------------------------------

namespace System.Data.OleDb {

    using System;
    using System.ComponentModel;
    using System.Data;
    using System.Data.Common;
    using System.Data.ProviderBase;
    using System.Diagnostics;
    using System.Globalization;

    [
    System.ComponentModel.TypeConverterAttribute(typeof(System.Data.OleDb.OleDbParameter.OleDbParameterConverter))
    ]
    public sealed partial class OleDbParameter : DbParameter, ICloneable, IDbDataParameter {
        private NativeDBType _metaType;
        private int _changeID;

        private string _parameterName;
        private byte _precision;
        private byte _scale;
        private bool _hasScale;

        private NativeDBType _coerceMetaType;

        public OleDbParameter() : base() { // V1.0 nothing
        }

        public OleDbParameter(string name, object value) : this() { // MDAC 59521
            Debug.Assert(!(value is OleDbType), "use OleDbParameter(string, OleDbType)");
            Debug.Assert(!(value is SqlDbType), "use OleDbParameter(string, OleDbType)");

            ParameterName = name;
            Value = value;
        }

        public OleDbParameter(string name, OleDbType dataType) : this() {
            ParameterName = name;
            OleDbType = dataType;
        }

        public OleDbParameter(string name, OleDbType dataType, int size) : this() {
            ParameterName = name;
            OleDbType = dataType;
            Size = size;
        }

        public OleDbParameter(string name, OleDbType dataType, int size, string srcColumn) : this() {
            ParameterName = name;
            OleDbType = dataType;
            Size = size;
            SourceColumn = srcColumn;
        }

        [ EditorBrowsableAttribute(EditorBrowsableState.Advanced) ] // MDAC 69508
        public OleDbParameter(string parameterName,
                              OleDbType dbType, int size,
                              ParameterDirection direction, Boolean isNullable,
                              Byte precision, Byte scale,
                              string srcColumn, DataRowVersion srcVersion,
                              object value) : this() { // V1.0 everything
            ParameterName = parameterName;
            OleDbType = dbType;
            Size = size;
            Direction = direction;
            IsNullable = isNullable;
            PrecisionInternal = precision;
            ScaleInternal = scale;
            SourceColumn = srcColumn;
            SourceVersion = srcVersion;
            Value = value;
        }

        [ EditorBrowsableAttribute(EditorBrowsableState.Advanced) ] // MDAC 69508
        public OleDbParameter(string parameterName,
                              OleDbType dbType, int size,
                              ParameterDirection direction,
                              Byte precision, Byte scale,
                              string sourceColumn, DataRowVersion sourceVersion, bool sourceColumnNullMapping,
                              object value) : this() { // V2.0 everything - round trip all browsable properties + precision/scale
            ParameterName = parameterName;
            OleDbType = dbType;
            Size = size;
            Direction = direction;
            PrecisionInternal = precision;
            ScaleInternal = scale;
            SourceColumn = sourceColumn;
            SourceVersion = sourceVersion;
            SourceColumnNullMapping = sourceColumnNullMapping;
            Value = value;
        }

        internal int ChangeID {
            get {
                return _changeID;
            }
        }

        override public DbType DbType {
            get {
                return GetBindType(Value).enumDbType;
            }
            set {
                NativeDBType dbtype = _metaType;
                if ((null == dbtype) || (dbtype.enumDbType != value)) { // MDAC 63571
                    PropertyTypeChanging();
                    _metaType = NativeDBType.FromDbType(value);
                }
            }
        }

        public override void ResetDbType() {
            ResetOleDbType();
        }

        [
        RefreshProperties(RefreshProperties.All),
        ResCategoryAttribute(Res.DataCategory_Data),
        ResDescriptionAttribute(Res.OleDbParameter_OleDbType),
        System.Data.Common.DbProviderSpecificTypePropertyAttribute(true),
        ]
        public OleDbType OleDbType {
            get {
                return GetBindType(Value).enumOleDbType;
            }
            set {
                NativeDBType dbtype = _metaType;
                if ((null == dbtype) || (dbtype.enumOleDbType != value)) { // MDAC 63571
                    PropertyTypeChanging();
                    _metaType = NativeDBType.FromDataType(value);
                }
            }
        }

        private bool ShouldSerializeOleDbType() {
            return (null != _metaType);
        }

        public void ResetOleDbType() {
            if (null != _metaType) {
                PropertyTypeChanging();
                _metaType = null;
            }
        }

        [
        ResCategoryAttribute(Res.DataCategory_Data),
        ResDescriptionAttribute(Res.DbParameter_ParameterName),
        ]
        override public string ParameterName { // V1.2.3300, XXXParameter V1.0.3300
            get {
                string parameterName = _parameterName;
                return ((null != parameterName) ? parameterName : ADP.StrEmpty);
            }
            set {
                if (_parameterName != value) {
                    PropertyChanging();
                    _parameterName = value;
                }
            }
        }

        [DefaultValue((Byte)0)] // MDAC 65862
        [ResCategoryAttribute(Res.DataCategory_Data)]
        [ResDescriptionAttribute(Res.DbDataParameter_Precision)]
        public new Byte Precision {
            get {
                return PrecisionInternal;
            }
            set {
                PrecisionInternal = value;
            }
        }
        internal byte PrecisionInternal {
            get {
                byte precision = _precision;
                if (0 == precision) {
                    precision = ValuePrecision(Value);
                }
                return precision;
            }
            set {
                if (_precision != value) {
                    PropertyChanging();
                    _precision = value;
                }
            }
        }
        private bool ShouldSerializePrecision() {
            return (0 != _precision);
        }

        [DefaultValue((Byte)0)] // MDAC 65862
        [ResCategoryAttribute(Res.DataCategory_Data)]
        [ResDescriptionAttribute(Res.DbDataParameter_Scale)]
        public new Byte Scale {
            get {
                return ScaleInternal;
            }
            set {
                ScaleInternal = value;
            }
        }
        internal byte ScaleInternal {
            get {
                byte scale = _scale;
                if (!ShouldSerializeScale(scale)) { // WebData 94688
                    scale = ValueScale(Value);
                }
                return scale;
            }
            set {
                if (_scale != value || !_hasScale) {
                    PropertyChanging();
                    _scale = value;
                    _hasScale = true;
                }
            }
        }
        private bool ShouldSerializeScale() {
            return ShouldSerializeScale(_scale);
        }

        private bool ShouldSerializeScale(byte scale) {
            return _hasScale && ((0 != scale) ||  ShouldSerializePrecision());
        }

        object ICloneable.Clone() {
            return new OleDbParameter(this);
        }

        private void CloneHelper(OleDbParameter destination) {
            CloneHelperCore(destination);
            destination._metaType = _metaType;
            destination._parameterName = _parameterName;
            destination._precision = _precision;
            destination._scale = _scale;
            destination._hasScale = _hasScale;
        }

        private void PropertyChanging() {
            unchecked { _changeID++; }
        }

        private void PropertyTypeChanging() {
            PropertyChanging();
            _coerceMetaType = null;
            CoercedValue = null;
        }

        // goal: call virtual property getters only once per parameter
        internal bool BindParameter(int index, Bindings bindings) {
            int changeID = _changeID;
            object value = Value;

            NativeDBType dbtype = GetBindType(value);
            if (OleDbType.Empty == dbtype.enumOleDbType) {
                throw ODB.UninitializedParameters(index, dbtype.enumOleDbType);
            }
            _coerceMetaType = dbtype;
            value = CoerceValue(value, dbtype);
            CoercedValue = value;

            ParameterDirection direction = Direction;

            byte precision;
            if (ShouldSerializePrecision()) {
                precision = PrecisionInternal;
            }
            else {
                precision = ValuePrecision(value);
            }
            if (0 == precision) {
                precision = dbtype.maxpre;
            }

            byte scale;
            if (ShouldSerializeScale()) {
                scale = ScaleInternal;
            }
            else {
                scale = ValueScale(value);
            }

            int wtype = dbtype.wType;
            int bytecount, size;

            if (dbtype.islong) { // long data (image, text, ntext)
                bytecount = ADP.PtrSize;
                if (ShouldSerializeSize()) {
                    size = Size;
                }
                else {
                    if (NativeDBType.STR == dbtype.dbType) {
                        size = Int32.MaxValue; // WebData 98940
                    }
                    else if (NativeDBType.WSTR == dbtype.dbType) {
                        size = Int32.MaxValue/2;
                    }
                    else {
                        size = Int32.MaxValue;
                    }
                }
                wtype |= NativeDBType.BYREF;
            }
            else if (dbtype.IsVariableLength) { // variable length data (varbinary, varchar, nvarchar)
                if (!ShouldSerializeSize() && ADP.IsDirection(this, ParameterDirection.Output)) {
                    throw ADP.UninitializedParameterSize(index, _coerceMetaType.dataType);
                }

                bool computedSize;
                if (ShouldSerializeSize()) {
                    size = Size;
                    computedSize = false;
                }
                else {
                    size = ValueSize(value);
                    computedSize = true;
                }
                if (0 < size) {
                    if (NativeDBType.WSTR == dbtype.wType) {
                        // maximum 0x3FFFFFFE characters, computed this way to avoid overflow exception
                        bytecount = Math.Min(size, 0x3FFFFFFE) * 2 + 2;
                    }
                    else {
                        Debug.Assert(NativeDBType.STR != dbtype.wType, "should have ANSI binding, describing is okay");
                        bytecount = size;
                    }

                    if (computedSize) {
                        if (NativeDBType.STR == dbtype.dbType) { // WebData 98140
                            // maximum 0x7ffffffe characters, computed this way to avoid overflow exception
                            size = Math.Min(size, 0x3FFFFFFE) * 2;
                        }
                    }

                    if (ODB.LargeDataSize < bytecount) {
                        bytecount = ADP.PtrSize;
                        wtype |= NativeDBType.BYREF;
                    }
                }
                else if (0 == size) {
                    if (NativeDBType.WSTR == wtype) { // allow space for null termination character
                        bytecount = 2;
                        // 0 == size, okay for (STR == dbType)
                    }
                    else {
                        Debug.Assert(NativeDBType.STR != dbtype.wType, "should have ANSI binding, describing is okay");
                        bytecount = 0;
                    }
                }
                else if (-1 == size) {
                    bytecount = ADP.PtrSize;
                    wtype |= NativeDBType.BYREF;
                }
                else {
                    throw ADP.InvalidSizeValue(size);
                }
            }
            else { // fixed length data
                bytecount = dbtype.fixlen;
                size = bytecount;
            }
            bindings.CurrentIndex = index;

            // tagDBPARAMBINDINFO info for SetParameterInfo
            bindings.DataSourceType = dbtype.dbString.DangerousGetHandle(); // NOTE: This is a constant and isn't exposed publicly, so there really isn't a potential for Handle Recycling.
            bindings.Name = ADP.PtrZero;
            bindings.ParamSize = new IntPtr(size);
            bindings.Flags = GetBindFlags(direction);
          //bindings.Precision    = precision;
          //bindings.Scale        = scale;

            // tagDBBINDING info for CreateAccessor
            bindings.Ordinal      = (IntPtr)(index+1);
            bindings.Part         = dbtype.dbPart;
            bindings.ParamIO      = GetBindDirection(direction);
            bindings.Precision    = precision;
            bindings.Scale        = scale;
            bindings.DbType       = wtype;
            bindings.MaxLen       = bytecount; // also increments databuffer size (uses DbType)
          //bindings.ValueOffset  = bindings.DataBufferSize; // set via MaxLen
          //bindings.LengthOffset = i * sizeof_int64;
          //bindings.StatusOffset = i * sizeof_int64 + sizeof_int32;
          //bindings.TypeInfoPtr  = 0;
          //bindings.ObjectPtr    = 0;
          //bindings.BindExtPtr   = 0;
          //bindings.MemOwner     = /*DBMEMOWNER_CLIENTOWNED*/0;
          //bindings.Flags        = 0;

          //bindings.ParameterChangeID = changeID; // bind until something changes
            Debug.Assert(_changeID == changeID, "parameter has unexpectedly changed");

            if (Bid.AdvancedOn) {
                Bid.Trace("<oledb.struct.tagDBPARAMBINDINFO|INFO|ADV> index=%d, parameterName='%ls'\n", index, ParameterName);//, bindings.BindInfo[index]);
                Bid.Trace("<oledb.struct.tagDBBINDING|INFO|ADV>\n");//, bindings.DBBinding[index]);
            }
            return IsParameterComputed();
        }

        private static object CoerceValue(object value, NativeDBType destinationType) {
            Debug.Assert(null != destinationType, "null destinationType");
            if ((null != value) && (DBNull.Value != value) && (typeof(object) != destinationType.dataType)) {
                Type currentType = value.GetType();
                if (currentType != destinationType.dataType) {
                    try {
                        if ((typeof(string) == destinationType.dataType) && (typeof(char[]) == currentType)) {
                        }
                        else if ((NativeDBType.CY == destinationType.dbType) && (typeof(string) == currentType)) {
                            value = Decimal.Parse((string)value, NumberStyles.Currency, (IFormatProvider)null); // WebData 99376
                        }
                        else {
                            value = Convert.ChangeType(value, destinationType.dataType, (IFormatProvider)null);
                        }
                    }
                    catch (Exception e) {
                        // 
                        if (!ADP.IsCatchableExceptionType(e)) {
                            throw;
                        }

                        throw ADP.ParameterConversionFailed(value, destinationType.dataType, e); // WebData 75433
                    }
                }
            }
            return value;
        }

        private NativeDBType GetBindType(object value) {
            NativeDBType dbtype = _metaType;
            if (null == dbtype) {
                if (ADP.IsNull(value)) {
                    dbtype = OleDb.NativeDBType.Default;
                }
                else {
                    dbtype = NativeDBType.FromSystemType(value);
                }
            }
            return dbtype;
        }

        internal object GetCoercedValue() {
            object value = CoercedValue; // will also be set during binding, will rebind everytime if _metaType not set
            if (null == value) {
                value = CoerceValue(Value, _coerceMetaType);
                CoercedValue = value;
            }
            return value;
        }

        internal bool IsParameterComputed() {
            NativeDBType metaType = _metaType;
            return ((null == metaType)
                    || (!ShouldSerializeSize() && metaType.IsVariableLength)
                    || ((NativeDBType.DECIMAL == metaType.dbType) || (NativeDBType.NUMERIC == metaType.dbType)
                        && (!ShouldSerializeScale() || !ShouldSerializePrecision())
                        )
                    ); // MDAC 69299
        }

        // @devnote: use IsParameterComputed which is called in the normal case
        // only to call Prepare to throw the specialized error message
        // reducing the overall number of methods to actually jit
        internal void Prepare(OleDbCommand cmd) { // MDAC 70232
            Debug.Assert(IsParameterComputed(), "Prepare computed parameter");
            if (null == _metaType) {
                throw ADP.PrepareParameterType(cmd);
            }
            else if (!ShouldSerializeSize() && _metaType.IsVariableLength) {
                throw ADP.PrepareParameterSize(cmd);
            }
            else if (!ShouldSerializePrecision() && !ShouldSerializeScale() && ((NativeDBType.DECIMAL == _metaType.wType) || (NativeDBType.NUMERIC == _metaType.wType))) { // MDAC 71441
                throw ADP.PrepareParameterScale(cmd, _metaType.wType.ToString("G", CultureInfo.InvariantCulture));
            }

            // Disabling the assert, see Dev11 775862 for details: http://vstfdevdiv.redmond.corp.microsoft.com:8080/WorkItemTracking/WorkItem.aspx?artifactMoniker=775862
            // Debug.Assert(false, "OleDbParameter.Prepare didn't throw");
        }

        [
        RefreshProperties(RefreshProperties.All),
        ResCategoryAttribute(Res.DataCategory_Data),
        ResDescriptionAttribute(Res.DbParameter_Value),
        TypeConverterAttribute(typeof(StringConverter)),
        ]
        override public object Value { // V1.2.3300, XXXParameter V1.0.3300
            get {
                return _value;
            }
            set {
                _coercedValue = null;
                _value = value;
            }
        }

        private byte ValuePrecision(object value) {
            return ValuePrecisionCore(value);
        }

        private byte ValueScale(object value) {
            return ValueScaleCore(value);
        }

        private int ValueSize(object value) {
            return ValueSizeCore(value);
        }

        static private int GetBindDirection(ParameterDirection direction) {
            return (ODB.ParameterDirectionFlag & (int)direction);
            /*switch(Direction) {
            default:
            case ParameterDirection.Input:
                return ODB.DBPARAMIO_INPUT;
            case ParameterDirection.Output:
            case ParameterDirection.ReturnValue:
                return ODB.DBPARAMIO_OUTPUT;
            case ParameterDirection.InputOutput:
                return (ODB.DBPARAMIO_INPUT | ODB.DBPARAMIO_OUTPUT);
            }*/
        }

        static private int GetBindFlags(ParameterDirection direction) {
            return (ODB.ParameterDirectionFlag & (int)direction);
            /*switch(Direction) {
            default:
            case ParameterDirection.Input:
                return ODB.DBPARAMFLAGS_ISINPUT;
            case ParameterDirection.Output:
            case ParameterDirection.ReturnValue:
                return ODB.DBPARAMFLAGS_ISOUTPUT;
            case ParameterDirection.InputOutput:
                return (ODB.DBPARAMFLAGS_ISINPUT | ODB.DBPARAMFLAGS_ISOUTPUT);
            }*/
        }

        // implemented as nested class to take advantage of the private/protected ShouldSerializeXXX methods
        sealed internal class OleDbParameterConverter : System.ComponentModel.ExpandableObjectConverter {

            // converter classes should have public ctor
            public OleDbParameterConverter() {
            }

            public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) {
                if (typeof(System.ComponentModel.Design.Serialization.InstanceDescriptor) == destinationType) {
                    return true;
                }
                return base.CanConvertTo(context, destinationType);
            }

            public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) {
                if (null == destinationType) {
                    throw ADP.ArgumentNull("destinationType");
                }
                if ((typeof(System.ComponentModel.Design.Serialization.InstanceDescriptor) == destinationType) && (value is OleDbParameter)) {
                    return ConvertToInstanceDescriptor(value as OleDbParameter);
                }
                return base.ConvertTo(context, culture, value, destinationType);
            }

            private System.ComponentModel.Design.Serialization.InstanceDescriptor ConvertToInstanceDescriptor(OleDbParameter p) {
                int flags = 0;

                if (p.ShouldSerializeOleDbType()) {
                    flags |= 1;
                }
                if (p.ShouldSerializeSize()) {
                    flags |= 2;
                }
                if (!ADP.IsEmpty(p.SourceColumn)) {
                    flags |= 4;
                }
                if (null != p.Value) {
                    flags |= 8;
                }
                if ((ParameterDirection.Input != p.Direction) || p.IsNullable
                    || p.ShouldSerializePrecision() || p.ShouldSerializeScale()
                    || (DataRowVersion.Current != p.SourceVersion)) {
                    flags |= 16; // V1.0 everything
                }
                if (p.SourceColumnNullMapping) {
                    flags |= 32; // v2.0 everything
                }

                Type[] ctorParams;
                object[] ctorValues;
                switch(flags) {
                case  0: // ParameterName
                case  1: // OleDbType
                    ctorParams = new Type[] { typeof(string), typeof(OleDbType) };
                    ctorValues = new object[] { p.ParameterName, p.OleDbType };
                    break;
                case  2: // Size
                case  3: // Size, OleDbType
                    ctorParams = new Type[] { typeof(string), typeof(OleDbType), typeof(int) };
                    ctorValues = new object[] { p.ParameterName, p.OleDbType, p.Size };
                    break;
                case  4: // SourceColumn
                case  5: // SourceColumn, OleDbType
                case  6: // SourceColumn, Size
                case  7: // SourceColumn, Size, OleDbType
                    ctorParams = new Type[] { typeof(string), typeof(OleDbType), typeof(int), typeof(string) };
                    ctorValues = new object[] { p.ParameterName, p.OleDbType, p.Size, p.SourceColumn };
                    break;
                case  8: // Value
                    ctorParams = new Type[] { typeof(string), typeof(object) };
                    ctorValues = new object[] { p.ParameterName, p.Value };
                    break;
                default: // everything else
                    if (0 == (32 & flags)) { // V1.0 everything
                        ctorParams = new Type[] {
                            typeof(string), typeof(OleDbType), typeof(int), typeof(ParameterDirection),
                            typeof(bool), typeof(byte), typeof(byte), typeof(string),
                            typeof(DataRowVersion), typeof(object) };
                        ctorValues = new object[] {
                            p.ParameterName, p.OleDbType,  p.Size, p.Direction,
                            p.IsNullable, p.PrecisionInternal, p.ScaleInternal, p.SourceColumn,
                            p.SourceVersion, p.Value };
                    }
                    else { // v2.0 everything - round trip all browsable properties + precision/scale
                        ctorParams = new Type[] {
                            typeof(string), typeof(OleDbType), typeof(int), typeof(ParameterDirection),
                            typeof(byte), typeof(byte),
                            typeof(string), typeof(DataRowVersion), typeof(bool),
                            typeof(object) };
                        ctorValues = new object[] {
                            p.ParameterName, p.OleDbType,  p.Size, p.Direction,
                            p.PrecisionInternal, p.ScaleInternal,
                            p.SourceColumn, p.SourceVersion, p.SourceColumnNullMapping,
                            p.Value };
                    }
                    break;
                }
                System.Reflection.ConstructorInfo ctor = typeof(OleDbParameter).GetConstructor(ctorParams);
                return new System.ComponentModel.Design.Serialization.InstanceDescriptor(ctor, ctorValues);
            }
        }
    }
}
