using System.Collections.Generic;
using System.Data.Common;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text;

namespace System.Data.Linq.SqlClient {

    internal static class SqlTypeSystem {

        internal static TypeSystemProvider Create2000Provider() {
            return new Sql2000Provider();
        }

        internal static TypeSystemProvider Create2005Provider() {
            return new Sql2005Provider();
        }

        internal static TypeSystemProvider Create2008Provider() {
            return new Sql2008Provider();
        }

        internal static TypeSystemProvider CreateCEProvider() {
            return new SqlCEProvider();
        }

        // -1 is used to indicate a MAX size.  In ADO.NET, -1 is specified as the size
        // on a SqlParameter with SqlDbType = VarChar to indicate VarChar(MAX).
        internal const short LargeTypeSizeIndicator = -1;

        // class constructor will cause static initialization to be deferred
        [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Justification="Static constructors provide deferred execution, which is desired.")]
        static SqlTypeSystem() { }

        #region Factories
        private static ProviderType Create(SqlDbType type, int size) {
            return new SqlType(type, size);
        }

        private static ProviderType Create(SqlDbType type, int precision, int scale) {
            if (type != SqlDbType.Decimal && precision == 0 && scale == 0) {
                return Create(type);
            }
            else if (type == SqlDbType.Decimal && precision == defaultDecimalPrecision && scale == defaultDecimalScale) {
                return Create(type);
            }
            return new SqlType(type, precision, scale);
        }

        [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification="These issues are related to our use of if-then and case statements for node types, which adds to the complexity count however when reviewed they are easy to navigate and understand.")]
        private static ProviderType Create(SqlDbType type) {
            switch (type) {
                case SqlDbType.BigInt: return theBigInt;
                case SqlDbType.Bit: return theBit;
                case SqlDbType.Char: return theChar;
                case SqlDbType.DateTime: return theDateTime;
                case SqlDbType.Date: return theDate;
                case SqlDbType.Time: return theTime;
                case SqlDbType.DateTime2: return theDateTime2;
                case SqlDbType.DateTimeOffset: return theDateTimeOffset;
                case SqlDbType.Decimal: return theDefaultDecimal;
                case SqlDbType.Float: return theFloat;
                case SqlDbType.Int: return theInt;
                case SqlDbType.Money: return theMoney;
                case SqlDbType.Real: return theReal;
                case SqlDbType.UniqueIdentifier: return theUniqueIdentifier;
                case SqlDbType.SmallDateTime: return theSmallDateTime;
                case SqlDbType.SmallInt: return theSmallInt;
                case SqlDbType.SmallMoney: return theSmallMoney;
                case SqlDbType.Timestamp: return theTimestamp;
                case SqlDbType.TinyInt: return theTinyInt;
                case SqlDbType.Xml: return theXml;
                case SqlDbType.Text: return theText;
                case SqlDbType.NText: return theNText;
                case SqlDbType.Image: return theImage;
                default:
                    return new SqlType(type);
            }
        }

        [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification="These issues are related to our use of if-then and case statements for node types, which adds to the complexity count however when reviewed they are easy to navigate and understand.")]
        private static Type GetClosestRuntimeType(SqlDbType sqlDbType) {
            switch (sqlDbType) {
                case SqlDbType.Int:
                    return typeof(int);
                case SqlDbType.BigInt:
                    return typeof(long);
                case SqlDbType.Bit:
                    return typeof(bool);
                case SqlDbType.Date:
                case SqlDbType.SmallDateTime:
                case SqlDbType.DateTime:
                case SqlDbType.DateTime2:
                    return typeof(DateTime);
                case SqlDbType.DateTimeOffset:
                    return typeof(DateTimeOffset);
                case SqlDbType.Time:
                    return typeof(TimeSpan);
                case SqlDbType.Float:
                    return typeof(double);
                case SqlDbType.Real:
                    return typeof(float);
                case SqlDbType.Binary:
                case SqlDbType.Image:
                case SqlDbType.Timestamp:
                case SqlDbType.VarBinary:
                    return typeof(byte[]);
                case SqlDbType.Decimal:
                case SqlDbType.Money:
                case SqlDbType.SmallMoney:
                    return typeof(decimal);
                case SqlDbType.Char:
                case SqlDbType.NChar:
                case SqlDbType.NText:
                case SqlDbType.NVarChar:
                case SqlDbType.Xml:
                case SqlDbType.VarChar:
                case SqlDbType.Text:
                    return typeof(string);
                case SqlDbType.UniqueIdentifier:
                    return typeof(Guid);
                case SqlDbType.SmallInt:
                    return typeof(short);
                case SqlDbType.TinyInt:
                    return typeof(byte);
                case SqlDbType.Udt:
                    // Udt type is not handled.
                    throw Error.UnexpectedTypeCode(SqlDbType.Udt);
                case SqlDbType.Variant:
                default:
                    return typeof(object);
            }
        }
        #endregion

        #region Singleton SqlTypes
        /*
         * For simple types with no size, precision or scale, reuse a static set of SqlDataTypes.
         * This has two advantages: Fewer allocations of SqlType and faster comparison of
         * one type to another.
         */
        static private readonly SqlType theBigInt = new SqlType(SqlDbType.BigInt);
        static private readonly SqlType theBit = new SqlType(SqlDbType.Bit);
        static private readonly SqlType theChar = new SqlType(SqlDbType.Char);
        static private readonly SqlType theDateTime = new SqlType(SqlDbType.DateTime);
        static private readonly SqlType theDate = new SqlType(SqlDbType.Date);
        static private readonly SqlType theTime = new SqlType(SqlDbType.Time);
        static private readonly SqlType theDateTime2 = new SqlType(SqlDbType.DateTime2);
        static private readonly SqlType theDateTimeOffset = new SqlType(SqlDbType.DateTimeOffset);
        const int defaultDecimalPrecision = 29;
        const int defaultDecimalScale = 4;
        static private readonly SqlType theDefaultDecimal = new SqlType(SqlDbType.Decimal, defaultDecimalPrecision, defaultDecimalScale);
        static private readonly SqlType theFloat = new SqlType(SqlDbType.Float);
        static private readonly SqlType theInt = new SqlType(SqlDbType.Int);
        static private readonly SqlType theMoney = new SqlType(SqlDbType.Money, 19, 4);
        static private readonly SqlType theReal = new SqlType(SqlDbType.Real);
        static private readonly SqlType theUniqueIdentifier = new SqlType(SqlDbType.UniqueIdentifier);
        static private readonly SqlType theSmallDateTime = new SqlType(SqlDbType.SmallDateTime);
        static private readonly SqlType theSmallInt = new SqlType(SqlDbType.SmallInt);
        static private readonly SqlType theSmallMoney = new SqlType(SqlDbType.SmallMoney, 10, 4);
        static private readonly SqlType theTimestamp = new SqlType(SqlDbType.Timestamp);
        static private readonly SqlType theTinyInt = new SqlType(SqlDbType.TinyInt);
        static private readonly SqlType theXml = new SqlType(SqlDbType.Xml, LargeTypeSizeIndicator);
        static private readonly SqlType theText = new SqlType(SqlDbType.Text, LargeTypeSizeIndicator);
        static private readonly SqlType theNText = new SqlType(SqlDbType.NText, LargeTypeSizeIndicator);
        static private readonly SqlType theImage = new SqlType(SqlDbType.Image, LargeTypeSizeIndicator);
        #endregion

        public class SqlType : ProviderType {
            /// <summary>
            /// Helper method for building ToString functions.
            /// </summary>
            protected static string KeyValue<T>(string key, T value) {
                if (value != null) {
                    return key + "=" + value.ToString() + " ";
                }
                return String.Empty;
            }

            /// <summary>
            /// Helper method for building ToString functions.
           /// </summary>
            protected static string SingleValue<T>(T value) {
                if (value != null) {
                    return value.ToString() + " ";
                }
                return string.Empty;
            }        
            public override string ToString() {
                return SingleValue(GetClosestRuntimeType()) 
                        + SingleValue(ToQueryString())
                        + KeyValue("IsApplicationType", IsApplicationType)
                        + KeyValue("IsUnicodeType", IsUnicodeType)
                        + KeyValue("IsRuntimeOnlyType", IsRuntimeOnlyType)
                        + KeyValue("SupportsComparison", SupportsComparison)
                        + KeyValue("SupportsLength", SupportsLength)
                        + KeyValue("IsLargeType", IsLargeType)
                        + KeyValue("IsFixedSize", IsFixedSize)
                        + KeyValue("IsOrderable", IsOrderable)
                        + KeyValue("IsGroupable", IsGroupable)
                        + KeyValue("IsNumeric", IsNumeric)
                        + KeyValue("IsChar", IsChar)
                        + KeyValue("IsString", IsString);
            }        
            protected SqlDbType sqlDbType;
            protected int? size;
            protected int precision;
            protected int scale;
            Type runtimeOnlyType;
            private int? applicationTypeIndex = null;

            #region Constructors
            internal SqlType(SqlDbType type) {
                this.sqlDbType = type;
            }

            internal SqlType(SqlDbType type, int? size) {
                this.sqlDbType = type;
                this.size = size;
            }

            internal SqlType(SqlDbType type, int precision, int scale) {
                System.Diagnostics.Debug.Assert(scale <= precision);
                this.sqlDbType = type;
                this.precision = precision;
                this.scale = scale;
            }

            internal SqlType(Type type) {
                this.runtimeOnlyType = type;
            }

            internal SqlType(int applicationTypeIndex) {
                this.applicationTypeIndex = applicationTypeIndex;
            }
            #endregion

            #region ProviderType Implementation
            internal override bool IsUnicodeType {
                get {
                    switch (this.SqlDbType) {
                        case SqlDbType.NChar:
                        case SqlDbType.NText:
                        case SqlDbType.NVarChar:
                            return true;
                        default:
                            return false;
                    }
                }
            }
            internal override ProviderType GetNonUnicodeEquivalent() {
                if (IsUnicodeType) {
                    switch (this.SqlDbType) {
                        case SqlDbType.NChar:
                            return new SqlType(SqlDbType.Char, this.Size);
                        case SqlDbType.NText:
                            return new SqlType(SqlDbType.Text);
                        case SqlDbType.NVarChar:
                            return new SqlType(SqlDbType.VarChar, this.Size);
                        default:
                            // can't happen
                            return this;
                    }
                }
                return this;
            }
            internal override bool IsRuntimeOnlyType {
                get { return runtimeOnlyType != null; }
            }

            internal override bool IsApplicationType {
                get { return applicationTypeIndex != null; }
            }

            internal override bool IsApplicationTypeOf(int index) {
                if (IsApplicationType) {
                    return applicationTypeIndex == index;
                }
                return false;
            }

            /// <summary>
            /// Determines if it is safe to suppress size specifications for
            /// the operand of a cast/convert.  For example, when casting to string,
            /// all these types have length less than the default sized used by SqlServer,
            /// so the length specification can be omitted without fear of truncation.
            /// </summary>
            internal override bool CanSuppressSizeForConversionToString{
                get {
                    int defaultSize = 30;

                    if (this.IsLargeType) {
                        return false;
                    }
                    else if (!this.IsChar && !this.IsString && this.IsFixedSize && this.Size > 0 /*&& this.Size != LargeTypeSizeIndicator*/) { // commented out because LargeTypeSizeIndicator == -1
                        return (this.Size < defaultSize);
                    }
                    else {

                        switch (this.SqlDbType) {
                            case SqlDbType.BigInt: // -2^63 (-9,223,372,036,854,775,808) to 2^63-1 (9,223,372,036,854,775,807)
                            case SqlDbType.Bit: // 0 or 1
                            case SqlDbType.Int: // -2^31 (-2,147,483,648) to 2^31-1 (2,147,483,647)
                            case SqlDbType.SmallInt: // -2^15 (-32,768) to 2^15-1 (32,767)
                            case SqlDbType.TinyInt: // 0 to 255
                            case SqlDbType.Money: // -922,337,203,685,477.5808 to 922,337,203,685,477.5807
                            case SqlDbType.SmallMoney: // -214,748.3648 to 214,748.3647
                            case SqlDbType.Float: // -1.79E+308 to -2.23E-308, 0 and 2.23E-308 to 1.79E+308
                            case SqlDbType.Real: // -3.40E+38 to -1.18E-38, 0 and 1.18E-38 to 3.40E+38
                                return true;
                            default:
                                return false;
                        };
                    }

                }
            }

            internal override int ComparePrecedenceTo(ProviderType type) {
                SqlType sqlProviderType = (SqlType)type;
                // Highest precedence is given to server-known types. Converting to a client-only type is
                // impossible when the conversion is present in a SQL query.
                int p0 = IsTypeKnownByProvider ? GetTypeCoercionPrecedence(this.SqlDbType) : Int32.MinValue;
                int p1 = sqlProviderType.IsTypeKnownByProvider ? GetTypeCoercionPrecedence(sqlProviderType.SqlDbType) : Int32.MinValue;
                return p0.CompareTo(p1);
            }

            /// <summary>
            /// Whether this is a legitimate server side type like NVARCHAR.
            /// </summary>
            private bool IsTypeKnownByProvider {
                get { return !IsApplicationType && !IsRuntimeOnlyType; }
            }

            internal override bool IsSameTypeFamily(ProviderType type) {
                SqlType sqlType = (SqlType)type;
                if (IsApplicationType) {
                    return false;
                }
                if (sqlType.IsApplicationType) {
                    return false;
                }
                return this.Category == sqlType.Category;
            }

            internal override bool SupportsComparison {
                get {
                    switch (this.sqlDbType) {
                        case SqlDbType.NText:
                        case SqlDbType.Image:
                        case SqlDbType.Xml:
                        case SqlDbType.Text:
                            return false;
                        default:
                            return true;
                    }
                }
            }

            internal override bool SupportsLength {
                get {
                    switch (this.sqlDbType) {
                        case SqlDbType.NText:
                        case SqlDbType.Image:
                        case SqlDbType.Xml:
                        case SqlDbType.Text:
                            return false;
                        default:
                            return true;
                    }
                }
            }

            /// <summary>
            /// Returns true if the given values will be equal to eachother on the server for this type.
            /// </summary>
            internal override bool AreValuesEqual(object o1, object o2) {
                if (o1 == null || o2 == null) {
                    return false;
                }
                switch (this.sqlDbType) {
                    case SqlDbType.Char:
                    case SqlDbType.NChar:
                    case SqlDbType.NVarChar:
                    case SqlDbType.VarChar:
                    case SqlDbType.Text:
                        string s1 = o1 as string;
                        if (s1 != null) {
                            string s2 = o2 as string;
                            if (s2 != null) {
                                return s1.TrimEnd(' ').Equals(s2.TrimEnd(' '), StringComparison.Ordinal);
                            }
                        }
                        break;
                }
                return o1.Equals(o2);
            }

            internal override bool IsLargeType {
                get {
                    switch (this.sqlDbType) {
                        case SqlDbType.NText:
                        case SqlDbType.Image:
                        case SqlDbType.Xml:
                        case SqlDbType.Text:
                            return true;
                        case SqlDbType.NVarChar:
                        case SqlDbType.VarChar:
                        case SqlDbType.VarBinary:
                            return (this.size == LargeTypeSizeIndicator);
                        default:
                            return false;
                    }
                }
            }

            internal override bool HasPrecisionAndScale {
                get {
                    switch (SqlDbType) {
                        case SqlDbType.Decimal:
                        case SqlDbType.Float:
                        case SqlDbType.Real:
                        case SqlDbType.Money:      // precision and scale are fixed at 19,4
                        case SqlDbType.SmallMoney: // precision and scale are fixed at 10,4
                        case SqlDbType.DateTime2:
                        case SqlDbType.DateTimeOffset:
                        case SqlDbType.Time:
                            return true;
                        default:
                            return false;
                    }
                }
            }

            internal override Type GetClosestRuntimeType() {
                if (runtimeOnlyType != null) {
                    return runtimeOnlyType;
                }
                else {
                    return SqlTypeSystem.GetClosestRuntimeType(this.sqlDbType);
                }
            }

            internal override string ToQueryString() {
                return ToQueryString(QueryFormatOptions.None);
            }

            [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification="These issues are related to our use of if-then and case statements for node types, which adds to the complexity count however when reviewed they are easy to navigate and understand.")]
            internal override string ToQueryString(QueryFormatOptions formatFlags) {
                if (runtimeOnlyType != null) {
                    return runtimeOnlyType.ToString();
                }
                StringBuilder sb = new StringBuilder();

                switch (sqlDbType) {
                    case SqlDbType.BigInt:
                    case SqlDbType.Bit:
                    case SqlDbType.Date:
                    case SqlDbType.Time:
                    case SqlDbType.DateTime:
                    case SqlDbType.DateTime2:
                    case SqlDbType.DateTimeOffset:
                    case SqlDbType.Int:
                    case SqlDbType.Money:
                    case SqlDbType.SmallDateTime:
                    case SqlDbType.SmallInt:
                    case SqlDbType.SmallMoney:
                    case SqlDbType.Timestamp:
                    case SqlDbType.TinyInt:
                    case SqlDbType.UniqueIdentifier:
                    case SqlDbType.Xml:
                    case SqlDbType.Image:
                    case SqlDbType.NText:
                    case SqlDbType.Text:
                    case SqlDbType.Udt:
                        sb.Append(sqlDbType.ToString());
                        break;
                    case SqlDbType.Variant:
                        sb.Append("sql_variant");
                        break;
                    case SqlDbType.Binary:
                    case SqlDbType.Char:
                    case SqlDbType.NChar:
                        sb.Append(sqlDbType);
                        if ((formatFlags & QueryFormatOptions.SuppressSize) == 0) {
                            sb.Append("(");
                            sb.Append(size);
                            sb.Append(")");
                        }
                        break;
                    case SqlDbType.NVarChar:
                    case SqlDbType.VarBinary:
                    case SqlDbType.VarChar:
                        sb.Append(sqlDbType);
                        if ((size.HasValue && size != 0) && (formatFlags & QueryFormatOptions.SuppressSize) == 0) {
                            sb.Append("(");
                            if (size == LargeTypeSizeIndicator) {
                                sb.Append("MAX");
                            }
                            else {
                                sb.Append(size);
                            }
                            sb.Append(")");
                        }
                        break;
                    case SqlDbType.Decimal:
                    case SqlDbType.Float:
                    case SqlDbType.Real:
                        sb.Append(sqlDbType);
                        if (precision != 0) {
                            sb.Append("(");
                            sb.Append(precision);
                            if (scale != 0) {
                                sb.Append(",");
                                sb.Append(scale);
                            }
                            sb.Append(")");
                        }
                        break;
                }
                return sb.ToString();
            }

            internal override bool HasSizeOrIsLarge {
                get {
                    return this.size.HasValue || this.IsLargeType;
                }
            }

            internal override int? Size {
                get {
                    return this.size;
                }
            }

            internal int Precision {
                get { return this.precision; }
            }

            internal int Scale {
                get { return this.scale; }
            }

            internal override bool IsFixedSize {
                get {
                    switch (this.sqlDbType) {
                        case SqlDbType.NText:
                        case SqlDbType.Text:
                        case SqlDbType.NVarChar:
                        case SqlDbType.VarChar:
                        case SqlDbType.Image:
                        case SqlDbType.VarBinary:
                        case SqlDbType.Xml:
                            return false;
                        default:
                            return true;
                    }
                }
            }

            internal SqlDbType SqlDbType {
                get { return sqlDbType; }
            }

            internal override bool IsOrderable {
                get {
                    if (this.IsRuntimeOnlyType) return false; // must be a SQL type

                    switch (this.sqlDbType) {
                        case SqlDbType.Image:
                        case SqlDbType.Text:
                        case SqlDbType.NText:
                        case SqlDbType.Xml:
                            return false;
                        default:
                            return true;
                    }
                }
            }

            internal override bool IsGroupable {
                get {
                    if (this.IsRuntimeOnlyType) return false; // must be a SQL type

                    switch (this.sqlDbType) {
                        case SqlDbType.Image:
                        case SqlDbType.Text:
                        case SqlDbType.NText:
                        case SqlDbType.Xml:
                            return false;
                        default:
                            return true;
                    }
                }
            }

            internal override bool CanBeColumn {
                get { return !this.IsApplicationType && !this.IsRuntimeOnlyType; }
            }

            internal override bool CanBeParameter {
                get { return !this.IsApplicationType && !this.IsRuntimeOnlyType; }
            }

            internal override bool IsNumeric {
                get {
                    if (IsApplicationType || IsRuntimeOnlyType) {
                        return false;
                    }
                    switch (SqlDbType) {
                        case SqlDbType.Bit:
                        case SqlDbType.TinyInt:
                        case SqlDbType.SmallInt:
                        case SqlDbType.Int:
                        case SqlDbType.BigInt:
                        case SqlDbType.Decimal:
                        case SqlDbType.Float:
                        case SqlDbType.Real:
                        case SqlDbType.Money:
                        case SqlDbType.SmallMoney:
                            return true;
                        default:
                            return false;
                    };
                }
            }

            internal override bool IsChar {
                get {
                    if (IsApplicationType || IsRuntimeOnlyType)
                        return false;
                    switch (SqlDbType) {
                        case SqlDbType.Char:
                        case SqlDbType.NChar:
                        case SqlDbType.NVarChar:
                        case SqlDbType.VarChar:
                            return Size == 1;
                        default:
                            return false;
                    }
                }
            }

            internal override bool IsString {
                get {
                    if (IsApplicationType || IsRuntimeOnlyType)
                        return false;
                    switch (SqlDbType) {
                        case SqlDbType.Char:
                        case SqlDbType.NChar:
                        case SqlDbType.NVarChar:
                        case SqlDbType.VarChar:
                            // -1 is used for large types to represent MAX
                            return Size == 0 || Size > 1 || Size == LargeTypeSizeIndicator;
                        case SqlDbType.Text:
                        case SqlDbType.NText:
                            return true;
                        default:
                            return false;
                    }
                }
            }
            #endregion

            #region Equals + GetHashCode

            public override bool Equals(object obj) {
                if ((object)this == obj)
                    return true;

                SqlType that = obj as SqlType;
                if (that == null)
                    return false;

                return 
                    this.runtimeOnlyType == that.runtimeOnlyType &&
                    this.applicationTypeIndex == that.applicationTypeIndex &&
                    this.sqlDbType == that.sqlDbType &&
                    this.Size == that.Size &&
                    this.precision == that.precision &&
                    this.scale == that.scale;
            }

            public override int GetHashCode() {
                // Make up a hash function that will atleast not treat precision and scale
                // as interchangeable. This may need a smarter replacement if certain hash
                // buckets get too full.
                int hash = 0;
                if (this.runtimeOnlyType != null) {
                    hash = this.runtimeOnlyType.GetHashCode();
                }
                else if (this.applicationTypeIndex != null) {
                    hash = this.applicationTypeIndex.Value;
                }
                return hash ^ this.sqlDbType.GetHashCode() ^ (this.size ?? 0) ^ (this.precision) ^ (this.scale << 8);
            }
            #endregion

            #region Type Classification
            [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification="These issues are related to our use of if-then and case statements for node types, which adds to the complexity count however when reviewed they are easy to navigate and understand.")]
            private static int GetTypeCoercionPrecedence(SqlDbType type) {
                switch (type) {
                    case SqlDbType.Binary: return 0;
                    case SqlDbType.VarBinary: return 1;
                    case SqlDbType.VarChar: return 2;
                    case SqlDbType.Char: return 3;
                    case SqlDbType.NChar: return 4;
                    case SqlDbType.NVarChar: return 5;
                    case SqlDbType.UniqueIdentifier: return 6;
                    case SqlDbType.Timestamp: return 7;
                    case SqlDbType.Image: return 8;
                    case SqlDbType.Text: return 9;
                    case SqlDbType.NText: return 10;
                    case SqlDbType.Bit: return 11;
                    case SqlDbType.TinyInt: return 12;
                    case SqlDbType.SmallInt: return 13;
                    case SqlDbType.Int: return 14;
                    case SqlDbType.BigInt: return 15;
                    case SqlDbType.SmallMoney: return 16;
                    case SqlDbType.Money: return 17;
                    case SqlDbType.Decimal: return 18;
                    case SqlDbType.Real: return 19;
                    case SqlDbType.Float: return 20;
                    case SqlDbType.Date: return 21;
                    case SqlDbType.Time: return 22;
                    case SqlDbType.SmallDateTime: return 23;
                    case SqlDbType.DateTime: return 24;
                    case SqlDbType.DateTime2: return 25;
                    case SqlDbType.DateTimeOffset: return 26;
                    case SqlDbType.Xml: return 27;
                    case SqlDbType.Variant: return 28;
                    case SqlDbType.Udt: return 29;
                    default:
                        throw Error.UnexpectedTypeCode(type);
                }
            }

            internal enum TypeCategory {
                Numeric,
                Char,
                Text,
                Binary,
                Image,
                Xml,
                DateTime,
                UniqueIdentifier,
                Variant,
                Udt
            }

            [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification="These issues are related to our use of if-then and case statements for node types, which adds to the complexity count however when reviewed they are easy to navigate and understand.")]
            internal TypeCategory Category {
                get {
                    switch (this.SqlDbType) {
                        case SqlDbType.Bit:
                        case SqlDbType.TinyInt:
                        case SqlDbType.SmallInt:
                        case SqlDbType.Int:
                        case SqlDbType.BigInt:
                        case SqlDbType.Decimal:
                        case SqlDbType.Float:
                        case SqlDbType.Real:
                        case SqlDbType.Money:
                        case SqlDbType.SmallMoney:
                            return TypeCategory.Numeric;
                        case SqlDbType.Char:
                        case SqlDbType.NChar:
                        case SqlDbType.VarChar:
                        case SqlDbType.NVarChar:
                            return TypeCategory.Char;
                        case SqlDbType.Text:
                        case SqlDbType.NText:
                            return TypeCategory.Text;
                        case SqlDbType.Binary:
                        case SqlDbType.VarBinary:
                        case SqlDbType.Timestamp:
                            return TypeCategory.Binary;
                        case SqlDbType.Image:
                            return TypeCategory.Image;
                        case SqlDbType.Xml:
                            return TypeCategory.Xml;
                        case SqlDbType.Date:
                        case SqlDbType.Time:
                        case SqlDbType.DateTime:
                        case SqlDbType.DateTime2:
                        case SqlDbType.DateTimeOffset:
                        case SqlDbType.SmallDateTime:
                            return TypeCategory.DateTime;
                        case SqlDbType.UniqueIdentifier:
                            return TypeCategory.UniqueIdentifier;
                        case SqlDbType.Variant:
                            return TypeCategory.Variant;
                        case SqlDbType.Udt:
                            return TypeCategory.Udt;
                        default:
                            throw Error.UnexpectedTypeCode(this);
                    }
                }
            }
            #endregion
        }

        abstract class ProviderBase : TypeSystemProvider {
            protected Dictionary<int, SqlType> applicationTypes = new Dictionary<int, SqlType>();

            #region Factories
            internal override ProviderType GetApplicationType(int index) {
                if (index < 0)
                    throw Error.ArgumentOutOfRange("index");
                SqlType result = null;
                if (!applicationTypes.TryGetValue(index, out result)) {
                    result = new SqlType(index);
                    applicationTypes.Add(index, result);
                }
                return result;
            }

            [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification="These issues are related to our use of if-then and case statements for node types, which adds to the complexity count however when reviewed they are easy to navigate and understand.")]
            internal override ProviderType Parse(string stype) {
                // parse type...
                string typeName = null;
                string param1 = null;
                string param2 = null;
                int paren = stype.IndexOf('(');
                int space = stype.IndexOf(' ');
                int end = (paren != -1 && space != -1) ? Math.Min(space, paren)
                        : (paren != -1) ? paren
                        : (space != -1) ? space
                        : -1;
                if (end == -1) {
                    typeName = stype;
                    end = stype.Length;
                }
                else {
                    typeName = stype.Substring(0, end);
                }
                int start = end;
                if (start < stype.Length && stype[start] == '(') {
                    start++;
                    end = stype.IndexOf(',', start);
                    if (end > 0) {
                        param1 = stype.Substring(start, end - start);
                        start = end + 1;
                        end = stype.IndexOf(')', start);
                        param2 = stype.Substring(start, end - start);
                    }
                    else {
                        end = stype.IndexOf(')', start);
                        param1 = stype.Substring(start, end - start);
                    }
                    start = end++;
                }

                #region Special case type mappings
                if (String.Compare(typeName, "rowversion", StringComparison.OrdinalIgnoreCase) == 0) {
                    typeName = "Timestamp";
                }

                if (String.Compare(typeName, "numeric", StringComparison.OrdinalIgnoreCase) == 0) {
                    typeName = "Decimal";
                }

                if (String.Compare(typeName, "sql_variant", StringComparison.OrdinalIgnoreCase) == 0) {
                    typeName = "Variant";
                }

                if (String.Compare(typeName, "filestream", StringComparison.OrdinalIgnoreCase) == 0) {
                    typeName = "Binary";
                }
                #endregion

                // since we're going to parse the enum value below, we verify
                // here that it is defined.  For example, types like table, cursor
                // are not valid.
                if (!Enum.GetNames(typeof(SqlDbType)).Select(n => n.ToUpperInvariant()).Contains(typeName.ToUpperInvariant()))
                {
                    throw Error.InvalidProviderType(typeName);
                }

                int p1 = 0;
                int p2 = 0;
                SqlDbType dbType = (SqlDbType)Enum.Parse(typeof(SqlDbType), typeName, true);
                if (param1 != null) {
                    if (String.Compare(param1.Trim(), "max", StringComparison.OrdinalIgnoreCase) == 0) {
                        p1 = LargeTypeSizeIndicator;
                    }
                    else {
                        p1 = Int32.Parse(param1, Globalization.CultureInfo.InvariantCulture);
                        if (p1 == int.MaxValue)
                            p1 = LargeTypeSizeIndicator;
                    }
                }

                if (param2 != null) {
                    if (String.Compare(param2.Trim(), "max", StringComparison.OrdinalIgnoreCase) == 0) {
                        p2 = LargeTypeSizeIndicator;
                    }
                    else {
                        p2 = Int32.Parse(param2, Globalization.CultureInfo.InvariantCulture);
                        if (p2 == int.MaxValue)
                            p2 = LargeTypeSizeIndicator;

                    }
                }

                switch (dbType) {
                    case SqlDbType.Binary:
                    case SqlDbType.Char:
                    case SqlDbType.NChar:
                    case SqlDbType.NVarChar:
                    case SqlDbType.VarBinary:
                    case SqlDbType.VarChar:
                        return Create(dbType, p1);
                    case SqlDbType.Decimal:
                    case SqlDbType.Real:
                    case SqlDbType.Float:
                        return Create(dbType, p1, p2);
                    case SqlDbType.Timestamp:
                    default:
                        return Create(dbType);
                }
            }
            #endregion

            #region Implementation
            // Returns true if the type provider supports MAX types (e.g NVarChar(MAX))
            protected abstract bool SupportsMaxSize { get; }

            /// <summary>
            /// For types with size, determine the closest matching type for the information
            /// specified, promoting to the appropriate large type as needed.  If no size is
            /// specified, we use the max.
            /// </summary>
            protected ProviderType GetBestType(SqlDbType targetType, int? size)
            {
                // determine max size for the specified db type
                int maxSize = 0;
                switch (targetType)
                {
                    case SqlDbType.NChar:
                    case SqlDbType.NVarChar:
                        maxSize = 4000;
                        break;
                    case SqlDbType.Char:
                    case SqlDbType.VarChar:
                    case SqlDbType.Binary:
                    case SqlDbType.VarBinary:
                        maxSize = 8000;
                        break;
                };

                if (size.HasValue)
                {
                    if (size.Value <= maxSize)
                    {
                        return Create(targetType, size.Value);
                    }
                    else
                    {
                        return GetBestLargeType(Create(targetType));
                    }
                }

                // if the type provider supports MAX types, return one, otherwise use
                // the maximum size determined above
                return Create(targetType, SupportsMaxSize ? SqlTypeSystem.LargeTypeSizeIndicator : maxSize);
            }

            internal override void InitializeParameter(ProviderType type, DbParameter parameter, object value) {
                SqlType sqlType = (SqlType)type;
                if (sqlType.IsRuntimeOnlyType) {
                    throw Error.BadParameterType(sqlType.GetClosestRuntimeType());
                }
                System.Data.SqlClient.SqlParameter sParameter = parameter as System.Data.SqlClient.SqlParameter;
                if (sParameter != null) {
                    sParameter.SqlDbType = sqlType.SqlDbType;
                    if (sqlType.HasPrecisionAndScale) {
                        sParameter.Precision = (byte)sqlType.Precision;
                        sParameter.Scale = (byte)sqlType.Scale;
                    }
                }
                else {
                    PropertyInfo piSqlDbType = parameter.GetType().GetProperty("SqlDbType");
                    if (piSqlDbType != null) {
                        piSqlDbType.SetValue(parameter, sqlType.SqlDbType, null);
                    }
                    if (sqlType.HasPrecisionAndScale) {
                        PropertyInfo piPrecision = parameter.GetType().GetProperty("Precision");
                        if (piPrecision != null) {
                            piPrecision.SetValue(parameter, Convert.ChangeType(sqlType.Precision, piPrecision.PropertyType, CultureInfo.InvariantCulture), null);
                        }
                        PropertyInfo piScale = parameter.GetType().GetProperty("Scale");
                        if (piScale != null) {
                            piScale.SetValue(parameter, Convert.ChangeType(sqlType.Scale, piScale.PropertyType, CultureInfo.InvariantCulture), null);
                        }
                    }
                }
                parameter.Value = GetParameterValue(sqlType, value);

                int? determinedSize = DetermineParameterSize(sqlType, parameter);
                if (determinedSize.HasValue) {
                    parameter.Size = determinedSize.Value;
                }
            }

            internal virtual int? DetermineParameterSize(SqlType declaredType, DbParameter parameter) {
                // Output parameters and input-parameters of a fixed-size should be specifically set if value fits.
                bool isInputParameter = parameter.Direction == ParameterDirection.Input;
                if (!isInputParameter || declaredType.IsFixedSize) {
                    if (declaredType.Size.HasValue && parameter.Size <= declaredType.Size || declaredType.IsLargeType) {
                        return declaredType.Size.Value;
                    }
                }

                // Preserve existing provider & server-driven behaviour for all other cases.
                return null;
            }

            protected int? GetLargestDeclarableSize(SqlType declaredType) {
                 switch (declaredType.SqlDbType) {
                    case SqlDbType.Image:
                    case SqlDbType.Binary:
                    case SqlDbType.VarChar:
                        return 8000;
                    case SqlDbType.NVarChar:
                        return 4000;
                    default:
                        return null;
                }
            }
           
            [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification="Unknown reason.")]
            protected object GetParameterValue(SqlType type, object value) {
                if (value == null) {
                    return DBNull.Value;
                }
                else {
                    Type vType = value.GetType();
                    Type pType = type.GetClosestRuntimeType();
                    if (pType == vType) {
                        return value;
                    }
                    else {
                        return DBConvert.ChangeType(value, pType);
                    }
                }
            }

            [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification="These issues are related to our use of if-then and case statements for node types, which adds to the complexity count however when reviewed they are easy to navigate and understand.")]
            internal override ProviderType PredictTypeForUnary(SqlNodeType unaryOp, ProviderType operandType) {
                switch (unaryOp) {
                    case SqlNodeType.Not:
                    case SqlNodeType.Not2V:
                    case SqlNodeType.IsNull:
                    case SqlNodeType.IsNotNull:
                        return theBit;
                    case SqlNodeType.Negate:
                    case SqlNodeType.BitNot:
                    case SqlNodeType.ValueOf:
                    case SqlNodeType.Treat:
                    case SqlNodeType.OuterJoinedValue:
                        return operandType;
                    case SqlNodeType.Count:
                        return this.From(typeof(int));
                    case SqlNodeType.LongCount:
                        return this.From(typeof(long));
                    case SqlNodeType.Min:
                    case SqlNodeType.Max:
                        return operandType;
                    case SqlNodeType.Sum:
                    case SqlNodeType.Avg:
                    case SqlNodeType.Stddev:
                        return this.MostPreciseTypeInFamily(operandType);
                    case SqlNodeType.ClrLength:
                        if (operandType.IsLargeType) {
                            return this.From(typeof(long)); // SqlDbType.BigInt
                        }
                        else {
                            return this.From(typeof(int)); // SqlDbType.Int
                        }
                    default:
                        throw Error.UnexpectedNode(unaryOp);
                }
            }

            [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification="These issues are related to our use of if-then and case statements for node types, which adds to the complexity count however when reviewed they are easy to navigate and understand.")]
            internal override ProviderType PredictTypeForBinary(SqlNodeType binaryOp, ProviderType leftType, ProviderType rightType) {
                SqlType highest;

                if (leftType.IsSameTypeFamily(this.From(typeof(string))) && rightType.IsSameTypeFamily(this.From(typeof(string)))) {
                    highest = (SqlType)this.GetBestType(leftType, rightType);
                } else {
                    int coercionPrecedence = leftType.ComparePrecedenceTo(rightType);
                    highest = (SqlType)(coercionPrecedence > 0 ? leftType : rightType);
                }

                switch (binaryOp) {
                    case SqlNodeType.Add:
                    case SqlNodeType.Sub:
                    case SqlNodeType.Mul:
                    case SqlNodeType.Div:
                    case SqlNodeType.BitAnd:
                    case SqlNodeType.BitOr:
                    case SqlNodeType.BitXor:
                    case SqlNodeType.Mod:
                    case SqlNodeType.Coalesce:
                        return highest;
                    case SqlNodeType.Concat:
                        // When concatenating two types with size, the result type after
                        // concatenation must have a size equal to the sum of the two sizes.
                        if (highest.HasSizeOrIsLarge)
                        {
                            // get the best type, specifying null for size so we get
                            // the maximum allowable size
                            ProviderType concatType = this.GetBestType(highest.SqlDbType, null);

                            if ((!leftType.IsLargeType && leftType.Size.HasValue) &&
                                (!rightType.IsLargeType && rightType.Size.HasValue))
                            {
                                // If both types are not large types and have size, and the
                                // size is less than the default size, return the shortened type.
                                int concatSize = leftType.Size.Value + rightType.Size.Value;
                                if ((concatSize < concatType.Size) || concatType.IsLargeType)
                                {
                                    return GetBestType(highest.SqlDbType, concatSize);
                                }
                            }

                            return concatType;
                        }
                        return highest;
                    case SqlNodeType.And:
                    case SqlNodeType.Or:
                    case SqlNodeType.LT:
                    case SqlNodeType.LE:
                    case SqlNodeType.GT:
                    case SqlNodeType.GE:
                    case SqlNodeType.EQ:
                    case SqlNodeType.NE:
                    case SqlNodeType.EQ2V:
                    case SqlNodeType.NE2V:
                        return theInt;
                    default:
                        throw Error.UnexpectedNode(binaryOp);
                }
            }

            internal override ProviderType MostPreciseTypeInFamily(ProviderType type) {
                SqlType sqlType = (SqlType)type;
                switch (sqlType.SqlDbType) {
                    case SqlDbType.TinyInt:
                    case SqlDbType.SmallInt:
                    case SqlDbType.Int:
                        return From(typeof(int));
                    case SqlDbType.SmallMoney:
                    case SqlDbType.Money:
                        return Create(SqlDbType.Money);
                    case SqlDbType.Real:
                    case SqlDbType.Float:
                        return From(typeof(double));
                    case SqlDbType.Date:
                    case SqlDbType.Time:
                    case SqlDbType.SmallDateTime:
                    case SqlDbType.DateTime:
                    case SqlDbType.DateTime2:
                        return From(typeof(DateTime));
                    case SqlDbType.DateTimeOffset:
                        return From(typeof(DateTimeOffset));
                    default:
                        return type;
                }
            }

            [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification="Unknown reason.")]
            private ProviderType[] GetArgumentTypes(SqlFunctionCall fc) {
                ProviderType[] array = new ProviderType[fc.Arguments.Count];
                for (int i = 0, n = array.Length; i < n; i++) {
                    array[i] = fc.Arguments[i].SqlType;
                }
                return array;
            }

            /// <summary>
            /// Gives the return type of a SQL function which has as first argument something of this SqlType.
            /// For some functions (e.g. trigonometric functions) the return type is independent of the argument type;
            /// in those cases this method returns null and the type was already set correctly in the MethodCallConverter.
            /// </summary>
            /// <param name="functionCall">The SqlFunctionCall node.</param>
            /// <returns>null: Don't change type, otherwise: Set return type to this ProviderType</returns>
            [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification="These issues are related to our use of if-then and case statements for node types, which adds to the complexity count however when reviewed they are easy to navigate and understand.")]
            internal override ProviderType ReturnTypeOfFunction(SqlFunctionCall functionCall) {
                var argumentTypes = this.GetArgumentTypes(functionCall);

                SqlType arg0 = (SqlType)argumentTypes[0];
                SqlType arg1 = argumentTypes.Length > 1 ? (SqlType)argumentTypes[1] : (SqlType)null;

                switch (functionCall.Name) {
                    case "LEN":
                    case "DATALENGTH":
                        switch (arg0.SqlDbType) {
                            case SqlDbType.NVarChar:
                            case SqlDbType.VarChar:
                            case SqlDbType.VarBinary:
                                if (arg0.IsLargeType) {
                                    return Create(SqlDbType.BigInt);
                                }
                                else {
                                    return Create(SqlDbType.Int);
                                }
                            default:
                                return Create(SqlDbType.Int);
                        }
                    case "ABS":
                    case "SIGN":
                    case "ROUND":
                    case "CEILING":
                    case "FLOOR":
                    case "POWER":
                        switch (arg0.SqlDbType) {
                            case SqlDbType.TinyInt:
                            case SqlDbType.Int:
                            case SqlDbType.SmallInt:
                                return Create(SqlDbType.Int);
                            case SqlDbType.Float:
                            case SqlDbType.Real:
                                return Create(SqlDbType.Float);
                            default:
                                return arg0;
                        }
                    case "PATINDEX":
                    case "CHARINDEX":
                        if (arg1.IsLargeType)
                            return Create(SqlDbType.BigInt);
                        return Create(SqlDbType.Int);
                    case "SUBSTRING":
                        if (functionCall.Arguments[2].NodeType == SqlNodeType.Value) {
                            SqlValue val = (SqlValue)functionCall.Arguments[2];

                            if (val.Value is int) {
                                switch (arg0.SqlDbType) {
                                    case SqlDbType.NVarChar:
                                    case SqlDbType.NChar:
                                    case SqlDbType.VarChar:
                                    case SqlDbType.Char:
                                        return Create(arg0.SqlDbType, (int)val.Value);
                                    default:
                                        return null;
                                }
                            }
                        }
                        switch (arg0.SqlDbType) {
                            case SqlDbType.NVarChar:
                            case SqlDbType.NChar:
                                return Create(SqlDbType.NVarChar);
                            case SqlDbType.VarChar:
                            case SqlDbType.Char:
                                return Create(SqlDbType.VarChar);
                            default:
                                return null;
                        }
                    case "STUFF":
                        // if the stuff call is an insertion  and is strictly additive
                        // (no deletion of characters) the return type is the same as 
                        // a concatenation
                        if (functionCall.Arguments.Count == 4)
                        {
                            SqlValue delLength = functionCall.Arguments[2] as SqlValue;
                            if (delLength != null && (int)delLength.Value == 0)
                            {
                                return PredictTypeForBinary(SqlNodeType.Concat,
                                    functionCall.Arguments[0].SqlType, functionCall.Arguments[3].SqlType);
                            }
                        }
                        return null;
                    case "LOWER":
                    case "UPPER":
                    case "RTRIM":
                    case "LTRIM":
                    case "INSERT":
                    case "REPLACE":
                    case "LEFT":
                    case "RIGHT":
                    case "REVERSE":
                        return arg0;
                    default:
                        return null;
                }
            }

            internal override ProviderType ChangeTypeFamilyTo(ProviderType type, ProviderType toType) {
                // if this is already the same family, do nothing
                if (type.IsSameTypeFamily(toType))
                    return type;

                // otherwise as a default return toType
                // but look for special cases we have to convert carefully
                if (type.IsApplicationType || toType.IsApplicationType)
                    return toType;
                SqlType sqlToType = (SqlType)toType;
                SqlType sqlThisType = (SqlType)type;

                if (sqlToType.Category == SqlType.TypeCategory.Numeric && sqlThisType.Category == SqlType.TypeCategory.Char) {
                    switch (sqlThisType.SqlDbType) {
                        case SqlDbType.NChar:
                            return Create(SqlDbType.Int);
                        case SqlDbType.Char:
                            return Create(SqlDbType.SmallInt);
                        default:
                            return toType;
                    }
                }
                else {
                    return toType;
                }
            }

            [SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Justification = "[....]: Cast is dependent on node type and casts do not happen unecessarily in a single code path.")]
            internal override ProviderType GetBestType(ProviderType typeA, ProviderType typeB) {
                // first determine the type precedence
                SqlType bestType = (SqlType)(typeA.ComparePrecedenceTo(typeB) > 0 ? typeA : typeB);

                // if one of the types is a not a server type, return
                // that type
                if (typeA.IsApplicationType || typeA.IsRuntimeOnlyType) {
                    return typeA;
                }
                if (typeB.IsApplicationType || typeB.IsRuntimeOnlyType) {
                    return typeB;
                }

                SqlType sqlTypeA = (SqlType)typeA;
                SqlType sqlTypeB = (SqlType)typeB;
                if (sqlTypeA.HasPrecisionAndScale && sqlTypeB.HasPrecisionAndScale && bestType.SqlDbType == SqlDbType.Decimal) {
                    int p0 = sqlTypeA.Precision;
                    int s0 = sqlTypeA.Scale;
                    int p1 = sqlTypeB.Precision;
                    int s1 = sqlTypeB.Scale;
                    // precision and scale may be zero if this is an unsized type.
                    if (p0 == 0 && s0 == 0 && p1 == 0 && s1 == 0) {
                        return Create(bestType.SqlDbType);
                    } else if (p0 == 0 && s0 == 0) {
                        return Create(bestType.SqlDbType, p1, s1);
                    } else if (p1 == 0 && s1 == 0) {
                        return Create(bestType.SqlDbType, p0, s0);
                    }
                    // determine best scale/precision
                    int bestLeft = Math.Max(p0 - s0, p1 - s1);
                    int bestRight = Math.Max(s0, s1);
                    int precision = Math.Min(bestLeft + bestRight, 38);
                    return Create(bestType.SqlDbType, precision, /*scale*/bestRight);
                }
                else {
                    // determine the best size
                    int? bestSize = null;

                    if (sqlTypeA.Size.HasValue && sqlTypeB.Size.HasValue) {
                        bestSize = (sqlTypeB.Size > sqlTypeA.Size) ? sqlTypeB.Size : sqlTypeA.Size;
                    }

                    if (sqlTypeB.Size.HasValue && sqlTypeB.Size.Value == LargeTypeSizeIndicator
                        || sqlTypeA.Size.HasValue && sqlTypeA.Size.Value == LargeTypeSizeIndicator) {
                        // the large type size trumps all
                        bestSize = LargeTypeSizeIndicator;
                    }

                    bestType = new SqlType(bestType.SqlDbType, bestSize);
                }

                return bestType;
            }

            internal override ProviderType From(object o) {
                Type clrType = (o != null) ? o.GetType() : typeof(object);
                if (clrType == typeof(string)) {
                    string str = (string)o;
                    return From(clrType, str.Length);
                }
                else if (clrType == typeof(bool)) {
                    return From(typeof(int));
                }
                else if (clrType.IsArray) {
                    Array arr = (Array)o;
                    return From(clrType, arr.Length);
                }
                else if (clrType == typeof(decimal)) {
                    decimal d = (decimal)o;
                    // The CLR stores the scale of a decimal value in bits
                    // 16 to 23 (i.e., mask 0x00FF0000) of the fourth int. 
                    int scale = (Decimal.GetBits(d)[3] & 0x00FF0000) >> 16;
                    return From(clrType, scale);
                }
                else {
                    return From(clrType);
                }
            }

            internal override ProviderType From(Type type)
            {
                return From(type, null);
            }

            internal override ProviderType From(Type type, int? size) {
                return From(type, size);
            }

            #endregion
        }

        class Sql2005Provider : ProviderBase {
            [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification="These issues are related to our use of if-then and case statements for node types, which adds to the complexity count however when reviewed they are easy to navigate and understand.")]
            internal override ProviderType From(Type type, int? size) {
                if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
                    type = type.GetGenericArguments()[0];
                TypeCode tc = System.Type.GetTypeCode(type);
                switch (tc) {
                    case TypeCode.Boolean:
                        return Create(SqlDbType.Bit);
                    case TypeCode.Byte:
                        return Create(SqlDbType.TinyInt);
                    case TypeCode.SByte:
                    case TypeCode.Int16:
                        return Create(SqlDbType.SmallInt);
                    case TypeCode.Int32:
                    case TypeCode.UInt16:
                        return Create(SqlDbType.Int);
                    case TypeCode.Int64:
                    case TypeCode.UInt32:
                        return Create(SqlDbType.BigInt);
                    case TypeCode.UInt64:
                        return Create(SqlDbType.Decimal, 20, 0);
                    case TypeCode.Decimal:
                        return Create(SqlDbType.Decimal, 29, size ?? 4);
                    case TypeCode.Double:
                        return Create(SqlDbType.Float);
                    case TypeCode.Single:
                        return Create(SqlDbType.Real);
                    case TypeCode.Char:
                        return Create(SqlDbType.NChar, 1);
                    case TypeCode.String:
                        return GetBestType(SqlDbType.NVarChar, size);
                    case TypeCode.DateTime:
                        return Create(SqlDbType.DateTime);
                    case TypeCode.Object: {
                            if (type == typeof(Guid))
                                return Create(SqlDbType.UniqueIdentifier);
                            if (type == typeof(byte[]) || type == typeof(Binary))
                                return GetBestType(SqlDbType.VarBinary, size);
                            if (type == typeof(char[]))
                                return GetBestType(SqlDbType.NVarChar, size);
                            if (type == typeof(TimeSpan))
                                return Create(SqlDbType.BigInt);
                            if (type == typeof(System.Xml.Linq.XDocument) ||
                                type == typeof(System.Xml.Linq.XElement))
                                return theXml;
                            // else UDT?
                            return new SqlType(type);
                        }
                    default:
                        throw Error.UnexpectedTypeCode(tc);
                }
            }

            internal override ProviderType GetBestLargeType(ProviderType type) {
                SqlType sqlType = (SqlType)type;
                switch (sqlType.SqlDbType) {
                    case SqlDbType.NText:
                    case SqlDbType.NChar:
                    case SqlDbType.NVarChar:
                        return Create(SqlDbType.NVarChar, LargeTypeSizeIndicator);
                    case SqlDbType.Text:
                    case SqlDbType.Char:
                    case SqlDbType.VarChar:
                        return Create(SqlDbType.VarChar, LargeTypeSizeIndicator);
                    case SqlDbType.Image:
                    case SqlDbType.Binary:
                    case SqlDbType.VarBinary:
                        return Create(SqlDbType.VarBinary, LargeTypeSizeIndicator);
                }
                return type;
            }

            internal override int? DetermineParameterSize(SqlType declaredType, DbParameter parameter)
            {
                int? parameterSize = base.DetermineParameterSize(declaredType, parameter);
                if (parameterSize.HasValue) {
                    return parameterSize;
                }

                // Engine team have recommended we use 4k/8k block sizes rather than -1 providing the
                // data fits within that size.
                int? largestDeclarableSize = GetLargestDeclarableSize(declaredType);
                if (largestDeclarableSize.HasValue && largestDeclarableSize.Value >= parameter.Size)
                {
                    return largestDeclarableSize.Value;
                }

                // Providers that support the maximum size large type indicator should fall to that.
                return SqlTypeSystem.LargeTypeSizeIndicator;
            }


            protected override bool SupportsMaxSize
            {
                get { return true; }
            }

        }

        class Sql2008Provider : Sql2005Provider
        {
            [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "These issues are related to our use of if-then and case statements for node types, which adds to the complexity count however when reviewed they are easy to navigate and understand.")]
            internal override ProviderType From(Type type, int? size)
            {
                if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
                    type = type.GetGenericArguments()[0];

                // Retain mappings for DateTime and TimeSpan; add one for the new DateTimeOffset type.
                //
                if (System.Type.GetTypeCode(type) == TypeCode.Object &&
                    type == typeof(DateTimeOffset))
                {
                    return Create(SqlDbType.DateTimeOffset);
                }

                return base.From(type, size);
            }
        }

        class Sql2000Provider : ProviderBase {
            [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification="These issues are related to our use of if-then and case statements for node types, which adds to the complexity count however when reviewed they are easy to navigate and understand.")]
            internal override ProviderType From(Type type, int? size) {
                if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
                    type = type.GetGenericArguments()[0];
                TypeCode tc = System.Type.GetTypeCode(type);
                switch (tc) {
                    case TypeCode.Boolean:
                        return Create(SqlDbType.Bit);
                    case TypeCode.Byte:
                        return Create(SqlDbType.TinyInt);
                    case TypeCode.SByte:
                    case TypeCode.Int16:
                        return Create(SqlDbType.SmallInt);
                    case TypeCode.Int32:
                    case TypeCode.UInt16:
                        return Create(SqlDbType.Int);
                    case TypeCode.Int64:
                    case TypeCode.UInt32:
                        return Create(SqlDbType.BigInt);
                    case TypeCode.UInt64:
                        return Create(SqlDbType.Decimal, 20, 0);
                    case TypeCode.Decimal:
                        return Create(SqlDbType.Decimal, 29, size ?? 4);
                    case TypeCode.Double:
                        return Create(SqlDbType.Float);
                    case TypeCode.Single:
                        return Create(SqlDbType.Real);
                    case TypeCode.Char:
                        return Create(SqlDbType.NChar, 1);
                    case TypeCode.String:
                        return GetBestType(SqlDbType.NVarChar, size);               
                    case TypeCode.DateTime:
                        return Create(SqlDbType.DateTime);
                    case TypeCode.Object: {
                            if (type == typeof(Guid))
                                return Create(SqlDbType.UniqueIdentifier);
                            if (type == typeof(byte[]) || type == typeof(Binary))
                                return GetBestType(SqlDbType.VarBinary, size); 
                            if (type == typeof(char[]))
                                return GetBestType(SqlDbType.NVarChar, size);
                            if (type == typeof(TimeSpan))
                                return Create(SqlDbType.BigInt);
                            if (type == typeof(System.Xml.Linq.XDocument) ||
                                type == typeof(System.Xml.Linq.XElement))
                                return theNText;
                            // else UDT?
                            return new SqlType(type);
                        }
                    default:
                        throw Error.UnexpectedTypeCode(tc);
                }
            }

            internal override ProviderType GetBestLargeType(ProviderType type) {
                SqlType sqlType = (SqlType)type;
                switch (sqlType.SqlDbType) {
                    case SqlDbType.NChar:
                    case SqlDbType.NVarChar:
                        return Create(SqlDbType.NText);
                    case SqlDbType.Char:
                    case SqlDbType.VarChar:
                        return Create(SqlDbType.Text);
                    case SqlDbType.Binary:
                    case SqlDbType.VarBinary:
                        return Create(SqlDbType.Image);
                }
                return type;
            }

            protected override bool SupportsMaxSize
            {
                get { return false; }
            }

        }

        class SqlCEProvider : Sql2000Provider {
        }
    }
}
