//
// OracleDataReader.cs 
//
// Part of the Mono class libraries at
// mcs/class/System.Data.OracleClient/System.Data.OracleClient
//
// Assembly: System.Data.OracleClient.dll
// Namespace: System.Data.OracleClient
//
// Authors: Tim Coleman <tim@timcoleman.com>
//          Daniel Morgan <danmorg@sc.rr.com>
//
// Copyright (C) Tim Coleman, 2003
// Copyright (C) Daniel Morgan, 2003
//
// Licensed under the MIT/X11 License.
//

using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Data.Common;
using System.Data.OracleClient.Oci;
using System.Runtime.InteropServices;

namespace System.Data.OracleClient {
	public sealed class OracleDataReader : MarshalByRefObject, IDataReader, IDisposable, IDataRecord, IEnumerable
	{
		#region Fields

		OracleCommand command;
		ArrayList dataTypeNames;
		bool disposed = false;
		bool isClosed;
		bool hasRows;
		DataTable schemaTable;

		int recordsAffected = -1;
		OciStatementType statementType;
		OciStatementHandle statement;

		#endregion // Fields

		#region Constructors

		internal OracleDataReader (OracleCommand command, OciStatementHandle statement)
		{
			this.command = command;
			this.hasRows = false;
			this.isClosed = false;
			this.schemaTable = ConstructSchemaTable ();
			this.statement = statement;
			this.statementType = statement.GetStatementType ();
		}

		~OracleDataReader ()
		{
			Dispose (false);
		}

		#endregion // Constructors

		#region Properties

		public int Depth {
			get { return 0; }
		}

		public int FieldCount {
			get { return statement.ColumnCount; }
		}

		public bool HasRows {
			get { return hasRows; }
		}

		public bool IsClosed {
			get { return isClosed; }
		}

		public object this [string name] {
			get { return GetValue (GetOrdinal (name)); }
		}

		public object this [int i] {
			get { return GetValue (i); }
		}

		public int RecordsAffected {
			get { 
				if (statementType == OciStatementType.Select)
					return -1;
				else
					return GetRecordsAffected ();
			}
		}

		#endregion // Properties

		#region Methods

		public void Close ()
		{
			statement.Dispose();
			if (!isClosed) 
				command.CloseDataReader ();
			isClosed = true;
		}

		private static DataTable ConstructSchemaTable ()
		{
			Type booleanType = Type.GetType ("System.Boolean");
			Type stringType = Type.GetType ("System.String");
			Type intType = Type.GetType ("System.Int32");
			Type typeType = Type.GetType ("System.Type");
			Type shortType = Type.GetType ("System.Int16");

			DataTable schemaTable = new DataTable ("SchemaTable");
			schemaTable.Columns.Add ("ColumnName", stringType);
			schemaTable.Columns.Add ("ColumnOrdinal", intType);
			schemaTable.Columns.Add ("ColumnSize", intType);
			schemaTable.Columns.Add ("NumericPrecision", shortType);
			schemaTable.Columns.Add ("NumericScale", shortType);
			schemaTable.Columns.Add ("DataType", typeType);
			schemaTable.Columns.Add ("IsLong", booleanType);
			schemaTable.Columns.Add ("AllowDBNull", booleanType);
			schemaTable.Columns.Add ("IsUnique", booleanType);
			schemaTable.Columns.Add ("IsKey", booleanType);
			schemaTable.Columns.Add ("IsReadOnly", booleanType);
			schemaTable.Columns.Add ("BaseSchemaTable", stringType);
			schemaTable.Columns.Add ("BaseCatalogName", stringType);
			schemaTable.Columns.Add ("BaseTableName", stringType);
			schemaTable.Columns.Add ("BaseColumnName", stringType);
			schemaTable.Columns.Add ("BaseSchemaName", stringType);

			return schemaTable;
		}

		private void Dispose (bool disposing)
		{
			if (!disposed) {
				if (disposing) {
					schemaTable.Dispose ();
					Close ();
				}
				disposed = true;
			}
		}

		public void Dispose ()
		{
			Dispose (true);
			GC.SuppressFinalize (this);
		}

		public bool GetBoolean (int i)
		{
			throw new NotSupportedException ();
		}

		public byte GetByte (int i)
		{
			throw new NotSupportedException ();
		}

		public long GetBytes (int i, long fieldOffset, byte[] buffer2, int bufferoffset, int length)
		{
			object value = GetValue (i);
			if (!(value is byte[]))
				throw new InvalidCastException ();
			Array.Copy ((byte[]) value, (int) fieldOffset, buffer2, bufferoffset, length);
			return ((byte[]) value).Length - fieldOffset;
		}

		public char GetChar (int i)
		{
			throw new NotSupportedException ();
		}

		public long GetChars (int i, long fieldOffset, char[] buffer2, int bufferoffset, int length)
		{
			object value = GetValue (i);
			if (!(value is char[]))
				throw new InvalidCastException ();
			Array.Copy ((char[]) value, (int) fieldOffset, buffer2, bufferoffset, length);
			return ((char[]) value).Length - fieldOffset;
		}

		[MonoTODO]
		public IDataReader GetData (int i)
		{
			throw new NotImplementedException ();
		}

		public string GetDataTypeName (int i)
		{
			return (string) dataTypeNames [i];
		}

		public DateTime GetDateTime (int i)
		{
			object value = GetValue (i);
			if (!(value is DateTime))
				throw new InvalidCastException ();
			return (DateTime) value;
		}

		public decimal GetDecimal (int i)
		{
			object value = GetValue (i);
			if (!(value is decimal))
				throw new InvalidCastException ();
			return (decimal) value;
		}

		public double GetDouble (int i)
		{
			object value = GetValue (i);
			if (!(value is double))
				throw new InvalidCastException ();
			return (double) value;
		}

		public Type GetFieldType (int i)
		{
			// FIXME: "DataType" need to implement
			//OciColumnInfo columnInfo = command.StatementHandle.DescribeColumn (i);
			//Type fieldType = OciGlue.OciDataTypeToDbType (columnInfo.DataType);
			//return fieldType;
			return typeof(string);
		}

		public float GetFloat (int i)
		{
			object value = GetValue (i);
			if (!(value is float))
				throw new InvalidCastException ();
			return (float) value;
		}

		public Guid GetGuid (int i)
		{
			throw new NotSupportedException ();
		}

		public short GetInt16 (int i)
		{
			throw new NotSupportedException ();
		}

		public int GetInt32 (int i)
		{
			object value = GetValue (i);
			if (!(value is int))
				throw new InvalidCastException ();
			return (int) value;
		}

		public long GetInt64 (int i)
		{
			object value = GetValue (i);
			if (!(value is long))
				throw new InvalidCastException ();
			return (long) value;
		}

		public string GetName (int i)
		{
			return statement.GetParameter (i).GetName ();
		}

		[MonoTODO]
		public OracleBFile GetOracleBFile (int i)
		{
			throw new NotImplementedException ();
		}

		[MonoTODO]
		public OracleBinary GetOracleBinary (int i)
		{
			throw new NotImplementedException ();
		}

		public OracleLob GetOracleLob (int i)
		{
			OracleLob output = ((OciDefineHandle) statement.Values [i]).GetOracleLob ();
			output.connection = command.Connection;
			return output;
		}

		public OracleNumber GetOracleNumber (int i)
		{
			return new OracleNumber ((decimal) GetValue (i));
		}

		public int GetOrdinal (string name)
		{
			int i;
			
			for (i = 0; i < statement.ColumnCount; i += 1) {
				if (String.Compare (statement.GetParameter(i).GetName(), name, false) == 0)
					return i;
			}

			for (i = 0; i < statement.ColumnCount; i += 1) {
				if (String.Compare (statement.GetParameter(i).GetName(), name, true) == 0)
					return i;
			}

			throw new IndexOutOfRangeException ();
		}

		private int GetRecordsAffected ()
		{
			if (recordsAffected == -1) 
				recordsAffected = statement.GetAttributeInt32 (OciAttributeType.RowCount, command.ErrorHandle);
			return recordsAffected;
		}

		public DataTable GetSchemaTable ()
		{
			if (schemaTable.Rows != null && schemaTable.Rows.Count > 0)
				return schemaTable;

			dataTypeNames = new ArrayList ();

			for (int i = 0; i < statement.ColumnCount; i += 1) {
				DataRow row = schemaTable.NewRow ();

				OciParameterDescriptor parameter = statement.GetParameter (i);

				dataTypeNames.Add(parameter.GetDataTypeName());

				row ["ColumnName"]		= parameter.GetName ();
				row ["ColumnOrdinal"]		= i + 1;
				row ["ColumnSize"]		= parameter.GetDataSize ();
				row ["NumericPrecision"]	= parameter.GetPrecision ();
				row ["NumericScale"]		= parameter.GetScale ();
				// FIXME: "DataType" need to implement
				//row ["DataType"] = OciGlue.OciDataTypeToDbType (columnInfo.DataType);
				row ["DataType"]		= typeof(string);
				row ["AllowDBNull"]		= parameter.GetIsNull ();
				row ["BaseColumnName"]		= parameter.GetName ();
				row ["IsReadOnly"] 		= true;

				schemaTable.Rows.Add (row);
			}

			return schemaTable;
		}

		public string GetString (int i)
		{
			object value = GetValue (i);
			if (!(value is string))
				throw new InvalidCastException ();
			return (string) value;
		}

		public TimeSpan GetTimeSpan (int i)
		{
			object value = GetValue (i);
			if (!(value is TimeSpan))
				throw new InvalidCastException ();
			return (TimeSpan) value;
		}

		public object GetValue (int i)
		{
			OciDefineHandle defineHandle = (OciDefineHandle) statement.Values [i];

			if (IsDBNull (i))
				return DBNull.Value;

			return defineHandle.GetValue ();
		}

		public int GetValues (object[] values)
		{
			int len = values.Length;
			int count = statement.ColumnCount;
			int retval = 0;

			if (len > count)
				retval = count;
			else
				retval = len;

			for (int i = 0; i < retval; i += 1) 
				values [i] = GetValue (i);

			return retval;
		}

		IEnumerator IEnumerable.GetEnumerator ()
		{
			return new DbEnumerator (this);
		}

		public bool IsDBNull (int i)
		{
			return ((OciDefineHandle) statement.Values [i]).IsNull;
		}

		[MonoTODO]
		public bool NextResult ()
		{
			// FIXME: get next result
			//throw new NotImplementedException ();
			return false; 
		}

		public bool Read ()
		{
			bool retval = statement.Fetch ();
			return retval;
			//return command.StatementHandle.Fetch ();
		}

		#endregion // Methods
	}
}
