//
// OracleLob.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
//
// Author: Tim Coleman <tim@timcoleman.com>
//
// Copyright (C) Tim Coleman, 2003
//
// Licensed under the MIT/X11 License.
//

using System;
using System.Data.OracleClient.Oci;
using System.Data.SqlTypes;
using System.IO;
using System.Text;

namespace System.Data.OracleClient {
	public sealed class OracleLob : Stream, ICloneable, INullable
	{
		#region Fields

		public static readonly new OracleLob Null = new OracleLob ();

		internal OracleConnection connection;
		bool isBatched = false;
		bool isOpen = true;
		bool notNull = false;
		OciLobLocator locator;
		OracleType type;

		long length = -1;
		long position = 1;

		#endregion // Fields

		#region Constructors

		internal OracleLob ()
		{
		}

		internal OracleLob (OciLobLocator locator, OciDataType ociType)
		{
			notNull = true;
			this.locator = locator;

			switch (ociType) {
			case OciDataType.Blob:
				type = OracleType.Blob;
				break;
			case OciDataType.Clob:
				type = OracleType.Clob;
				break;
			}
		}

		#endregion // Constructors

		#region Properties

		public override bool CanRead {
			get { return (IsNull || isOpen); }
		}

		public override bool CanSeek {
			get { return (IsNull || isOpen); }
		}

		public override bool CanWrite {
			get { return isOpen; }
		}

		public int ChunkSize {
			[MonoTODO]
			get { 
				AssertConnectionIsOpen ();
				AssertObjectNotDisposed ();
				return locator.GetChunkSize ();
			}
		}

		public OracleConnection Connection {
			get { return connection; }
		}

		public bool IsBatched {
			get { return isBatched; }
		}

		public bool IsNull {
			get { return !notNull; }
		}

		public bool IsTemporary {
			get { 
				AssertConnectionIsOpen ();
				AssertObjectNotDisposed ();
				throw new NotImplementedException ();
			}
		}

		public override long Length {
			get { 
				AssertConnectionIsOpen ();
				AssertObjectNotDisposed ();
				if (length >= 0)
					return length;
				return locator.GetLength (LobType == OracleType.Blob);
			}
		}

		public OracleType LobType {
			get { return type; }
		}

		internal OciLobLocator Locator {
			get { return locator; }
		}

		public override long Position {
			get { 
				AssertConnectionIsOpen ();
				AssertObjectNotDisposed ();
				return position;
			}
			set {
				AssertConnectionIsOpen ();
				AssertObjectNotDisposed ();
				position = value;
			}
		}

		public object Value {
			get { 
				AssertObjectNotDisposed ();
				if (IsNull)
					return DBNull.Value;
				
				byte[] buffer = new byte [Length];
				Read (buffer, 1, (int) Length);

				if (LobType == OracleType.Clob)
					return (new UnicodeEncoding ()).GetString (buffer);
				return buffer;
			}
		}

		#endregion // Properties

		#region Methods

		[MonoTODO]
		public void Append (OracleLob source) 
		{
			if (source.IsNull)
				throw new ArgumentNullException ();
			if (Connection.State == ConnectionState.Closed)
				throw new InvalidOperationException ();
			throw new NotImplementedException ();
		}

		void AssertAmountIsEven (long amount, string argName)
		{
			if (amount % 2 == 1)
				throw new ArgumentOutOfRangeException ("CLOB and NCLOB parameters require even number of bytes for this argument.");
		}

		void AssertAmountIsValid (long amount, string argName)
		{
			if (amount > UInt32.MaxValue)
				throw new ArgumentOutOfRangeException ("Argument too big.");
			if (LobType == OracleType.Clob || LobType == OracleType.NClob)
				AssertAmountIsEven (amount, argName);
		}

		void AssertConnectionIsOpen ()
		{
			if (connection.State == ConnectionState.Closed)
				throw new InvalidOperationException ("Invalid operation. The connection is closed.");
		}

		void AssertObjectNotDisposed ()
		{
			if (!isOpen)
				throw new ObjectDisposedException ("OracleLob");
		}

		void AssertTransactionExists ()
		{
			if (connection.Transaction == null)
				throw new InvalidOperationException ("Modifying a LOB requires that the connection be transacted.");
		}

		public void BeginBatch ()
		{
			BeginBatch (OracleLobOpenMode.ReadOnly);
		}

		public void BeginBatch (OracleLobOpenMode mode)
		{
			AssertConnectionIsOpen ();
			AssertObjectNotDisposed ();

			locator.BeginBatch (mode);
			isBatched = true;
		}

		[MonoTODO]
		public object Clone ()
		{
			throw new NotImplementedException ();
		}

		[MonoTODO]
		public override void Close ()
		{
			locator.Dispose ();
			isOpen = false;
		}

		public long CopyTo (OracleLob destination)
		{
			return CopyTo (0, destination, 0, Length);
		}

		public long CopyTo (OracleLob destination, long destinationOffset)
		{
			return CopyTo (0, destination, destinationOffset, Length);
		}

		public long CopyTo (long sourceOffset, OracleLob destination, long destinationOffset, long amount)
		{
			if (destination.IsNull)
				throw new ArgumentNullException ();

			AssertAmountIsValid (sourceOffset, "sourceOffset");
			AssertAmountIsValid (destinationOffset, "destinationOffset");
			AssertAmountIsValid (amount, "amount");
			AssertTransactionExists ();
			AssertConnectionIsOpen ();

			return (long) locator.Copy (destination.Locator, (uint) amount, (uint) destinationOffset + 1, (uint) sourceOffset + 1);
		}

		[MonoTODO]
		public void Dispose ()
		{
			throw new NotImplementedException ();
		}

		public void EndBatch ()
		{
			AssertConnectionIsOpen ();
			AssertObjectNotDisposed ();

			locator.EndBatch ();
			isBatched = false;
		}

		public long Erase ()
		{
			return Erase (1, Length);
		}

		public long Erase (long offset, long amount)
		{
			if (offset < 0 || amount < 0)
				throw new ArgumentOutOfRangeException ("Must be a positive value.");
			if (offset + amount > Length)
				throw new ArgumentOutOfRangeException ();

			AssertAmountIsValid (offset, "offset");
			AssertAmountIsValid (amount, "amount");

			return (long) locator.Erase ((uint) offset + 1, (uint) amount);
		}

		public override void Flush ()
		{
			// No-op
		}

		public override int Read (byte[] buffer, int offset, int count)
		{
			if (buffer == null)
				throw new ArgumentNullException ();

			AssertAmountIsValid (offset, "offset");
			AssertAmountIsValid (count, "count");
			AssertConnectionIsOpen ();
			AssertObjectNotDisposed ();

			int bytesRead;
			byte[] output = new byte[count];

			bytesRead = locator.Read (output, (uint) Position, (uint) count, LobType == OracleType.Blob);
			output.CopyTo (buffer, offset);
			position += bytesRead;
			return bytesRead;
		}

		[MonoTODO]
		public override long Seek (long offset, SeekOrigin origin)
		{
			long newPosition = position;

			switch (origin) {
			case SeekOrigin.Begin:
				newPosition = offset;
				break;
			case SeekOrigin.Current:
				newPosition += offset;
				break;
			case SeekOrigin.End:
				newPosition = Length - offset;
				break;
			}

			if (newPosition > Length)
				throw new ArgumentOutOfRangeException ();

			position = newPosition;
			return position;
		}

		[MonoTODO]
		public override void SetLength (long value)
		{
			AssertAmountIsValid (value, "value");
			AssertTransactionExists ();
			AssertConnectionIsOpen ();
			AssertObjectNotDisposed ();

			locator.Trim ((uint) value);
			length = value;
		}

		public override void Write (byte[] buffer, int offset, int count)
		{
			if (buffer == null)
				throw new ArgumentNullException ("Buffer is null.");
			if (offset < 0 || count < 0)
				throw new ArgumentOutOfRangeException ("Must be a positive value.");
			if (offset + count > buffer.Length)
				throw new ArgumentOutOfRangeException ("The offset and count values specified exceed the buffer provided.");
			AssertAmountIsValid (offset, "offset");
			AssertAmountIsValid (count, "count");
			AssertTransactionExists ();
			AssertConnectionIsOpen ();
			AssertObjectNotDisposed ();

			byte[] value = null;
			if (offset + count == buffer.Length && offset == 0)
				value = buffer;
			else {
				value = new byte[count];
				Array.Copy (buffer, offset, value, 0, count);
			}

			position += locator.Write (value, (uint) Position, (uint) value.Length, LobType);
		}

		#endregion // Methods
	}
}
