//
// System.Drawing.ComIStreamWrapper.cs
//
// Author:
//   Kornél Pál <http://www.kornelpal.hu/>
//
// Copyright (C) 2005 Kornél Pál
//

//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

using System;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
#if NET_2_0
using System.Runtime.InteropServices.ComTypes;
using STATSTG = System.Runtime.InteropServices.ComTypes.STATSTG;
#else
using IStream = System.Runtime.InteropServices.UCOMIStream;
#endif

namespace System.Drawing
{
	// Stream to IStream wrapper for COM interop
	internal sealed class ComIStreamWrapper : IStream
	{
		private const int STG_E_INVALIDFUNCTION = unchecked((int)0x80030001);

		private readonly Stream baseStream;
		private long position = -1;

		internal ComIStreamWrapper(Stream stream)
		{
			baseStream = stream;
		}

		private void SetSizeToPosition()
		{
			if (position != -1)
			{
				if (position > baseStream.Length)
					baseStream.SetLength(position);
				baseStream.Position = position;
				position = -1;
			}
		}

		public void Read(byte[] pv, int cb, IntPtr pcbRead)
		{
			int read = 0;

			if (cb != 0)
			{
				SetSizeToPosition();

				read = baseStream.Read(pv, 0, cb);
			}

			if (pcbRead != IntPtr.Zero)
				Marshal.WriteInt32(pcbRead, read);
		}

		public void Write(byte[] pv, int cb, IntPtr pcbWritten)
		{
			if (cb != 0)
			{
				SetSizeToPosition();

				baseStream.Write(pv, 0, cb);
			}

			if (pcbWritten != IntPtr.Zero)
				Marshal.WriteInt32(pcbWritten, cb);
		}

		public void Seek(long dlibMove, int dwOrigin, IntPtr plibNewPosition)
		{
			long newPosition;

			if (baseStream.CanWrite)
			{
				switch ((SeekOrigin)dwOrigin)
				{
					case SeekOrigin.Begin:
						newPosition = dlibMove;
						break;
					case SeekOrigin.Current:
						if ((newPosition = position) == -1)
							newPosition = baseStream.Position;
						newPosition += dlibMove;
						break;
					case SeekOrigin.End:
						newPosition = baseStream.Length + dlibMove;
						break;
					default:
						throw new ExternalException(null, STG_E_INVALIDFUNCTION);
				}

				if (newPosition > baseStream.Length)
					position = newPosition;
				else
				{
					baseStream.Position = newPosition;
					position = -1;
				}
			}
			else
			{
				try
				{
					newPosition = baseStream.Seek(dlibMove, (SeekOrigin)dwOrigin);
				}
				catch (ArgumentException)
				{
					throw new ExternalException(null, STG_E_INVALIDFUNCTION);
				}
				position = -1;
			}

			if (plibNewPosition != IntPtr.Zero)
				Marshal.WriteInt64(plibNewPosition, newPosition);
		}

		public void SetSize(long libNewSize)
		{
			baseStream.SetLength(libNewSize);
		}

		public void CopyTo(IStream pstm, long cb, IntPtr pcbRead, IntPtr pcbWritten)
		{
			byte[] buffer = new byte[4096];
			long written = 0;
			int read;

			if (cb != 0)
			{
				SetSizeToPosition();
				do
				{
					int count = 4096;

					if (written + 4096 > cb)
						count = (int)(cb - written);

					if ((read = baseStream.Read(buffer, 0, count)) == 0)
						break;
					pstm.Write(buffer, read, IntPtr.Zero);
					written += read;
				} while (written < cb);
			}

			if (pcbRead != IntPtr.Zero)
				Marshal.WriteInt64(pcbRead, written);
			if (pcbWritten != IntPtr.Zero)
				Marshal.WriteInt64(pcbWritten, written);
		}

		public void Commit(int grfCommitFlags)
		{
			baseStream.Flush();
		}

		public void Revert()
		{
			throw new ExternalException(null, STG_E_INVALIDFUNCTION);
		}

		public void LockRegion(long libOffset, long cb, int dwLockType)
		{
			throw new ExternalException(null, STG_E_INVALIDFUNCTION);
		}

		public void UnlockRegion(long libOffset, long cb, int dwLockType)
		{
			throw new ExternalException(null, STG_E_INVALIDFUNCTION);
		}

		public void Stat(out STATSTG pstatstg, int grfStatFlag)
		{
			pstatstg = new STATSTG();
			pstatstg.cbSize = baseStream.Length;
		}

		public void Clone(out IStream ppstm)
		{
			ppstm = null;
			throw new ExternalException(null, STG_E_INVALIDFUNCTION);
		}
	}
}