//
// Mono.Data.SybaseClient.SybaseParameter.cs
//
// Author:
//   Rodrigo Moya (rodrigo@ximian.com)
//   Daniel Morgan (danmorg@sc.rr.com)
//   Tim Coleman (tim@timcoleman.com)
//
// (C) Ximian, Inc. 2002
// Copyright (C) Tim Coleman, 2002
//

using Mono.Data.Tds;
using Mono.Data.Tds.Protocol;
using System;
using System.ComponentModel;
using System.Data;
using System.Data.Common;
using System.Runtime.InteropServices;
using System.Text;

namespace Mono.Data.SybaseClient {
	public sealed class SybaseParameter : MarshalByRefObject, IDbDataParameter, IDataParameter, ICloneable
	{
		#region Fields

		TdsMetaParameter metaParameter;

		SybaseParameterCollection container = null;
		DbType dbType;
		ParameterDirection direction = ParameterDirection.Input;
		bool isNullable;
		bool isSizeSet = false;
		bool isTypeSet = false;
		int offset;
		SybaseType sybaseType;
		string sourceColumn;
		DataRowVersion sourceVersion;

		#endregion // Fields

		#region Constructors

		public SybaseParameter () 
			: this (String.Empty, SybaseType.NVarChar, 0, ParameterDirection.Input, false, 0, 0, String.Empty, DataRowVersion.Current, null)
		{
		}

		public SybaseParameter (string parameterName, object value) 
		{
			metaParameter = new TdsMetaParameter (parameterName, value);
			this.sourceVersion = DataRowVersion.Current;
			InferSybaseType (value);
		}
		
		public SybaseParameter (string parameterName, SybaseType dbType) 
			: this (parameterName, dbType, 0, ParameterDirection.Input, false, 0, 0, String.Empty, DataRowVersion.Current, null)
		{
		}

		public SybaseParameter (string parameterName, SybaseType dbType, int size) 
			: this (parameterName, dbType, size, ParameterDirection.Input, false, 0, 0, String.Empty, DataRowVersion.Current, null)
		{
		}
		
		public SybaseParameter (string parameterName, SybaseType dbType, int size, string sourceColumn) 
			: this (parameterName, dbType, size, ParameterDirection.Input, false, 0, 0, sourceColumn, DataRowVersion.Current, null)
		{
		}
		
		[EditorBrowsable (EditorBrowsableState.Advanced)]	 
		public SybaseParameter (string parameterName, SybaseType dbType, int size, ParameterDirection direction, bool isNullable, byte precision, byte scale, string sourceColumn, DataRowVersion sourceVersion, object value) 
		{
			metaParameter = new TdsMetaParameter (parameterName, size, isNullable, precision, scale, value);

			SybaseType = dbType;
			Direction = direction;
			SourceColumn = sourceColumn;
			SourceVersion = sourceVersion;
		}

		// This constructor is used internally to construct a
		// SybaseParameter.  The value array comes from sp_procedure_params_rowset.
		// This is in SybaseCommand.DeriveParameters.
		internal SybaseParameter (object[] dbValues)
		{
			Precision = 0;
			Scale = 0;
			Direction = ParameterDirection.Input;

			ParameterName = (string) dbValues[3];

			switch ((short) dbValues[5]) {
			case 1:
				Direction = ParameterDirection.Input;
				break;
			case 2:
				Direction = ParameterDirection.Output;
				break;
			case 3:
				Direction = ParameterDirection.InputOutput;
				break;
			case 4:
				Direction = ParameterDirection.ReturnValue;
				break;
			}

			IsNullable = (bool) dbValues[8];

			if (dbValues[12] != null)
				Precision = (byte) ((short) dbValues[12]);
			if (dbValues[13] != null)
				Scale = (byte) ((short) dbValues[13]);

			SetDbTypeName ((string) dbValues[16]);
		}

		#endregion // Constructors

		#region Properties

		// Used to ensure that only one collection can contain this
		// parameter
		internal SybaseParameterCollection Container {
			get { return container; }
			set { container = value; }
		}

		public DbType DbType {
			get { return dbType; }
			set { 
				SetDbType (value); 
				isTypeSet = true;
			}
		}

		public ParameterDirection Direction {
			get { return direction; }
			set { 
				direction = value; 
				if (direction == ParameterDirection.Output)
					MetaParameter.Direction = TdsParameterDirection.Output;
			}
		}

		internal TdsMetaParameter MetaParameter {
			get { return metaParameter; }
		}

		string IDataParameter.ParameterName {
			get { return metaParameter.ParameterName; }
			set { metaParameter.ParameterName = value; }
		}

		public bool IsNullable	{
			get { return metaParameter.IsNullable; }
			set { metaParameter.IsNullable = value; }
		}

		public int Offset {
			get { return offset; }
			set { offset = value; }
		}
		
		public string ParameterName {
			get { return metaParameter.ParameterName; }
			set { metaParameter.ParameterName = value; }
		}

		public byte Precision {
			get { return metaParameter.Precision; }
			set { metaParameter.Precision = value; }
		}

                public byte Scale {
			get { return metaParameter.Scale; }
			set { metaParameter.Scale = value; }
		}

                public int Size {
			get { return metaParameter.Size; }
			set { metaParameter.Size = value; }
		}

		public string SourceColumn {
			get { return sourceColumn; }
			set { sourceColumn = value; }
		}

		public DataRowVersion SourceVersion {
			get { return sourceVersion; }
			set { sourceVersion = value; }
		}
		
		public SybaseType SybaseType {
			get { return sybaseType; }
			set { 
				SetSybaseType (value); 
				isTypeSet = true;
			}
		}

		public object Value {
			get { return metaParameter.Value; }
			set { 
				if (!isTypeSet)
					InferSybaseType (value);
				metaParameter.Value = value; 
			}
		}

		#endregion // Properties

		#region Methods

		object ICloneable.Clone ()
		{
			return new SybaseParameter (ParameterName, SybaseType, Size, Direction, IsNullable, Precision, Scale, SourceColumn, SourceVersion, Value);
		}

		// If the value is set without the DbType/SybaseType being set, then we
		// infer type information.
		private void InferSybaseType (object value)
		{
			Type type = value.GetType ();

			string exception = String.Format ("The parameter data type of {0} is invalid.", type.Name);

			switch (type.FullName) {
			case "System.Int64":
				SetSybaseType (SybaseType.BigInt);
				break;
			case "System.Boolean":
				SetSybaseType (SybaseType.Bit);
				break;
			case "System.String":
				SetSybaseType (SybaseType.NVarChar);
				break;
			case "System.DateTime":
				SetSybaseType (SybaseType.DateTime);
				break;
			case "System.Decimal":
				SetSybaseType (SybaseType.Decimal);
				break;
			case "System.Double":
				SetSybaseType (SybaseType.Float);
				break;
			case "System.Byte[]":
				SetSybaseType (SybaseType.VarBinary);
				break;
			case "System.Byte":
				SetSybaseType (SybaseType.TinyInt);
				break;
			case "System.Int32":
				SetSybaseType (SybaseType.Int);
				break;
			case "System.Single":
				SetSybaseType (SybaseType.Real);
				break;
			case "System.Int16":
				SetSybaseType (SybaseType.SmallInt);
				break;
			case "System.Guid":
				SetSybaseType (SybaseType.UniqueIdentifier);
				break;
			case "System.Object":
				SetSybaseType (SybaseType.Variant);
				break;
			default:
				throw new ArgumentException (exception);				
			}
		}

		// When the DbType is set, we also set the SybaseType, as well as the SQL Server
		// string representation of the type name.  If the DbType is not convertible
		// to an SybaseType, throw an exception.
		private void SetDbType (DbType type)
		{
			string exception = String.Format ("No mapping exists from DbType {0} to a known SybaseType.", type);

			switch (type) {
			case DbType.AnsiString:
				MetaParameter.TypeName = "varchar";
				sybaseType = SybaseType.VarChar;
				break;
			case DbType.AnsiStringFixedLength:
				MetaParameter.TypeName = "char";
				sybaseType = SybaseType.Char;
				break;
			case DbType.Binary:
				MetaParameter.TypeName = "varbinary";
				sybaseType = SybaseType.VarBinary;
				break;
			case DbType.Boolean:
				MetaParameter.TypeName = "bit";
				sybaseType = SybaseType.Bit;
				break;
			case DbType.Byte:
				MetaParameter.TypeName = "tinyint";
				sybaseType = SybaseType.TinyInt;
				break;
			case DbType.Currency:
				sybaseType = SybaseType.Money;
				MetaParameter.TypeName = "money";
				break;
			case DbType.Date:
			case DbType.DateTime:
				MetaParameter.TypeName = "datetime";
				sybaseType = SybaseType.DateTime;
				break;
			case DbType.Decimal:
				MetaParameter.TypeName = "decimal";
				sybaseType = SybaseType.Decimal;
				break;
			case DbType.Double:
				MetaParameter.TypeName = "float";
				sybaseType = SybaseType.Float;
				break;
			case DbType.Guid:
				MetaParameter.TypeName = "uniqueidentifier";
				sybaseType = SybaseType.UniqueIdentifier;
				break;
			case DbType.Int16:
				MetaParameter.TypeName = "smallint";
				sybaseType = SybaseType.SmallInt;
				break;
			case DbType.Int32:
				MetaParameter.TypeName = "int";
				sybaseType = SybaseType.Int;
				break;
			case DbType.Int64:
				MetaParameter.TypeName = "bigint";
				sybaseType = SybaseType.BigInt;
				break;
			case DbType.Object:
				MetaParameter.TypeName = "sql_variant";
				sybaseType = SybaseType.Variant;
				break;
			case DbType.Single:
				MetaParameter.TypeName = "real";
				sybaseType = SybaseType.Real;
				break;
			case DbType.String:
				MetaParameter.TypeName = "nvarchar";
				sybaseType = SybaseType.NVarChar;
				break;
			case DbType.StringFixedLength:
				MetaParameter.TypeName = "nchar";
				sybaseType = SybaseType.NChar;
				break;
			case DbType.Time:
				MetaParameter.TypeName = "datetime";
				sybaseType = SybaseType.DateTime;
				break;
			default:
				throw new ArgumentException (exception);
			}
			dbType = type;
		}

		// Used by internal constructor which has a SQL Server typename
		private void SetDbTypeName (string dbTypeName)
		{
			switch (dbTypeName.ToLower ()) {	
			case "bigint":
				SybaseType = SybaseType.BigInt;
				break;
			case "binary":
				SybaseType = SybaseType.Binary;
				break;
			case "bit":
				SybaseType = SybaseType.Bit;
				break;
			case "char":
				SybaseType = SybaseType.Char;
				break;
			case "datetime":
				SybaseType = SybaseType.DateTime;
				break;
			case "decimal":
				SybaseType = SybaseType.Decimal;
				break;
			case "float":
				SybaseType = SybaseType.Float;
				break;
			case "image":
				SybaseType = SybaseType.Image;
				break;
			case "int":
				SybaseType = SybaseType.Int;
				break;
			case "money":
				SybaseType = SybaseType.Money;
				break;
			case "nchar":
				SybaseType = SybaseType.NChar;
				break;
			case "ntext":
				SybaseType = SybaseType.NText;
				break;
			case "nvarchar":
				SybaseType = SybaseType.NVarChar;
				break;
			case "real":
				SybaseType = SybaseType.Real;
				break;
			case "smalldatetime":
				SybaseType = SybaseType.SmallDateTime;
				break;
			case "smallint":
				SybaseType = SybaseType.SmallInt;
				break;
			case "smallmoney":
				SybaseType = SybaseType.SmallMoney;
				break;
			case "text":
				SybaseType = SybaseType.Text;
				break;
			case "timestamp":
				SybaseType = SybaseType.Timestamp;
				break;
			case "tinyint":
				SybaseType = SybaseType.TinyInt;
				break;
			case "uniqueidentifier":
				SybaseType = SybaseType.UniqueIdentifier;
				break;
			case "varbinary":
				SybaseType = SybaseType.VarBinary;
				break;
			case "varchar":
				SybaseType = SybaseType.VarChar;
				break;
			default:
				SybaseType = SybaseType.Variant;
				break;
			}
		}

		// When the SybaseType is set, we also set the DbType, as well as the SQL Server
		// string representation of the type name.  If the SybaseType is not convertible
		// to a DbType, throw an exception.
		private void SetSybaseType (SybaseType type)
		{
			string exception = String.Format ("No mapping exists from SybaseType {0} to a known DbType.", type);

			switch (type) {
			case SybaseType.BigInt:
				MetaParameter.TypeName = "bigint";
				dbType = DbType.Int64;
				break;
			case SybaseType.Binary:
				MetaParameter.TypeName = "binary";
				dbType = DbType.Binary;
				break;
			case SybaseType.Timestamp:
				MetaParameter.TypeName = "timestamp";
				dbType = DbType.Binary;
				break;
			case SybaseType.VarBinary:
				MetaParameter.TypeName = "varbinary";
				dbType = DbType.Binary;
				break;
			case SybaseType.Bit:
				MetaParameter.TypeName = "bit";
				dbType = DbType.Boolean;
				break;
			case SybaseType.Char:
				MetaParameter.TypeName = "char";
				dbType = DbType.AnsiStringFixedLength;
				break;
			case SybaseType.DateTime:
				MetaParameter.TypeName = "datetime";
				dbType = DbType.DateTime;
				break;
			case SybaseType.SmallDateTime:
				MetaParameter.TypeName = "smalldatetime";
				dbType = DbType.DateTime;
				break;
			case SybaseType.Decimal:
				MetaParameter.TypeName = "decimal";
				dbType = DbType.Decimal;
				break;
			case SybaseType.Float:
				MetaParameter.TypeName = "float";
				dbType = DbType.Double;
				break;
			case SybaseType.Image:
				MetaParameter.TypeName = "image";
				dbType = DbType.Binary;
				break;
			case SybaseType.Int:
				MetaParameter.TypeName = "int";
				dbType = DbType.Int32;
				break;
			case SybaseType.Money:
				MetaParameter.TypeName = "money";
				dbType = DbType.Currency;
				break;
			case SybaseType.SmallMoney:
				MetaParameter.TypeName = "smallmoney";
				dbType = DbType.Currency;
				break;
			case SybaseType.NChar:
				MetaParameter.TypeName = "nchar";
				dbType = DbType.StringFixedLength;
				break;
			case SybaseType.NText:
				MetaParameter.TypeName = "ntext";
				dbType = DbType.String;
				break;
			case SybaseType.NVarChar:
				MetaParameter.TypeName = "nvarchar";
				dbType = DbType.String;
				break;
			case SybaseType.Real:
				MetaParameter.TypeName = "real";
				dbType = DbType.Single;
				break;
			case SybaseType.SmallInt:
				MetaParameter.TypeName = "smallint";
				dbType = DbType.Int16;
				break;
			case SybaseType.Text:
				MetaParameter.TypeName = "text";
				dbType = DbType.AnsiString;
				break;
			case SybaseType.VarChar:
				MetaParameter.TypeName = "varchar";
				dbType = DbType.AnsiString;
				break;
			case SybaseType.TinyInt:
				MetaParameter.TypeName = "tinyint";
				dbType = DbType.Byte;
				break;
			case SybaseType.UniqueIdentifier:
				MetaParameter.TypeName = "uniqueidentifier";
				dbType = DbType.Guid;
				break;
			case SybaseType.Variant:
				MetaParameter.TypeName = "sql_variant";
				dbType = DbType.Object;
				break;
			default:
				throw new ArgumentException (exception);
			}
			sybaseType = type;
		}

		public override string ToString() 
		{
			return ParameterName;
		}

		#endregion // Methods
	}
}
