//
// System.IO.Ports.WinSerialStream.cs
//
// Authors:
//	Carlos Alberto Cortez (calberto.cortez@gmail.com)
//
// (c) Copyright 2006 Novell, Inc. (http://www.novell.com)
//
using System;
using System.Text;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.ComponentModel;
namespace System.IO.Ports
{
	class WinSerialStream : Stream, ISerialStream, IDisposable
	{
		// Windows API Constants
		const uint GenericRead = 0x80000000;
		const uint GenericWrite = 0x40000000;
		const uint OpenExisting = 3;
		const uint FileFlagOverlapped = 0x40000000;
		const uint PurgeRxClear = 0x0008;
		const uint PurgeTxClear = 0x0004;
		const uint WinInfiniteTimeout = 0xFFFFFFFF;
		const uint FileIOPending = 997;
		// Signal constants
		const uint SetRts = 3;
		const uint ClearRts = 4;
		const uint SetDtr = 5;
		const uint ClearDtr = 6;
		const uint SetBreak = 8;
		const uint ClearBreak = 9;
		const uint CtsOn = 0x0010;
		const uint DsrOn = 0x0020;
		const uint RsldOn = 0x0080;
		// Event constants
		const uint EvRxChar = 0x0001;
		const uint EvCts = 0x0008;
		const uint EvDsr = 0x0010;
		const uint EvRlsd = 0x0020;
		const uint EvBreak = 0x0040;
		const uint EvErr = 0x0080;
		const uint EvRing = 0x0100;
		int handle;
		int read_timeout;
		int write_timeout;
		bool disposed;
		IntPtr write_overlapped;
		IntPtr read_overlapped;
		ManualResetEvent read_event;
		ManualResetEvent write_event;
		Timeouts timeouts;
		[DllImport("kernel32", SetLastError = true)]
		static extern int CreateFile(string port_name, uint desired_access,
				uint share_mode, uint security_attrs, uint creation, uint flags,
				uint template);
		[DllImport("kernel32", SetLastError = true)]
		static extern bool SetupComm(int handle, int read_buffer_size, int write_buffer_size);
		[DllImport("kernel32", SetLastError = true)]
		static extern bool PurgeComm(int handle, uint flags);
		[DllImport("kernel32", SetLastError = true)]
		static extern bool SetCommTimeouts(int handle, Timeouts timeouts);
		public WinSerialStream (string port_name, int baud_rate, int data_bits, Parity parity, StopBits sb,
				bool dtr_enable, bool rts_enable, Handshake hs, int read_timeout, int write_timeout,
				int read_buffer_size, int write_buffer_size)
		{
			handle = CreateFile (port_name != null && !port_name.StartsWith(@"\\.\")
					? @"\\.\" + port_name : port_name,
					GenericRead | GenericWrite, 0, 0, OpenExisting,
					FileFlagOverlapped, 0);
			if (handle == -1)
				ReportIOError (port_name);
			// Set port low level attributes
			SetAttributes (baud_rate, parity, data_bits, sb, hs);
			// Clean buffers and set sizes
			if (!PurgeComm (handle, PurgeRxClear | PurgeTxClear) ||
					!SetupComm (handle, read_buffer_size, write_buffer_size))
				ReportIOError (null);
			// Set timeouts
			this.read_timeout = read_timeout;
			this.write_timeout = write_timeout;
			timeouts = new Timeouts (read_timeout, write_timeout);
			if (!SetCommTimeouts(handle, timeouts))
				ReportIOError (null);
			/// Set DTR and RTS
			SetSignal(SerialSignal.Dtr, dtr_enable);
			if (hs != Handshake.RequestToSend &&
					hs != Handshake.RequestToSendXOnXOff)
				SetSignal(SerialSignal.Rts, rts_enable);
			// Init overlapped structures
			NativeOverlapped wo = new NativeOverlapped ();
			write_event = new ManualResetEvent (false);
			wo.EventHandle = write_event.Handle;
			write_overlapped = Marshal.AllocHGlobal (Marshal.SizeOf (typeof (NativeOverlapped)));
			Marshal.StructureToPtr (wo, write_overlapped, true);
			NativeOverlapped ro = new NativeOverlapped ();
			read_event = new ManualResetEvent (false);
			ro.EventHandle = read_event.Handle;
			read_overlapped = Marshal.AllocHGlobal (Marshal.SizeOf (typeof (NativeOverlapped)));
			Marshal.StructureToPtr (ro, read_overlapped, true);
		}
		public override bool CanRead {
			get {
				return true;
			}
		}
		public override bool CanSeek {
			get {
				return false;
			}
		}
		public override bool CanTimeout {
			get {
				return true;
			}
		}
		public override bool CanWrite {
			get {
				return true;
			}
		}
		public override int ReadTimeout {
			get {
				return read_timeout;
			}
			set {
				if (value < 0 && value != SerialPort.InfiniteTimeout)
					throw new ArgumentOutOfRangeException ("value");
				timeouts.SetValues (value, write_timeout);
				if (!SetCommTimeouts (handle, timeouts))
					ReportIOError (null);
				read_timeout = value;
			}
		}
		public override int WriteTimeout {
			get {
				return write_timeout;
			}
			set
			{
				if (value < 0 && value != SerialPort.InfiniteTimeout)
					throw new ArgumentOutOfRangeException ("value");
				timeouts.SetValues (read_timeout, value);
				if (!SetCommTimeouts (handle, timeouts))
					ReportIOError (null);
				write_timeout = value;
			}
		}
		public override long Length {
			get {
				throw new NotSupportedException ();
			}
		}
		public override long Position {
			get {
				throw new NotSupportedException ();
			}
			set {
				throw new NotSupportedException ();
			}
		}
		[DllImport("kernel32", SetLastError = true)]
		static extern bool CloseHandle (int handle);
		protected override void Dispose (bool disposing)
		{
			if (disposed)
				return;
			disposed = true;
			CloseHandle (handle);
			Marshal.FreeHGlobal (write_overlapped);
			Marshal.FreeHGlobal (read_overlapped);
		}
		void IDisposable.Dispose ()
		{
			Dispose (true);
			GC.SuppressFinalize (this);
		}
		public override void Close ()
		{
			((IDisposable)this).Dispose ();
		}
		~WinSerialStream ()
		{
			Dispose (false);
		}
		public override void Flush ()
		{
			CheckDisposed ();
			// No dothing by now
		}
		public override long Seek (long offset, SeekOrigin origin)
		{
			throw new NotSupportedException();
		}
		public override void SetLength (long value)
		{
			throw new NotSupportedException();
		}
#if !TARGET_JVM
		[DllImport("kernel32", SetLastError = true)]
			static extern unsafe bool ReadFile (int handle, byte* buffer, int bytes_to_read,
					out int bytes_read, IntPtr overlapped);
		[DllImport("kernel32", SetLastError = true)]
			static extern unsafe bool GetOverlappedResult (int handle, IntPtr overlapped,
					ref int bytes_transfered, bool wait);
#endif
		public override int Read ([In, Out] byte [] buffer, int offset, int count)
		{
			CheckDisposed ();
			if (buffer == null)
				throw new ArgumentNullException ("buffer");
			if (offset < 0 || count < 0)
				throw new ArgumentOutOfRangeException ("offset or count less than zero.");
			if (buffer.Length - offset < count )
				throw new ArgumentException ("offset+count",
							      "The size of the buffer is less than offset + count.");
			int bytes_read;
			unsafe {
				fixed (byte* ptr = buffer) {
					if (ReadFile (handle, ptr + offset, count, out bytes_read, read_overlapped))
						return bytes_read;
				
					// Test for overlapped behavior
					if (Marshal.GetLastWin32Error () != FileIOPending)
						ReportIOError (null);
				
					if (!GetOverlappedResult (handle, read_overlapped, ref bytes_read, true))
						ReportIOError (null);
			
				}
			}
			if (bytes_read == 0)
				throw new TimeoutException (); // We didn't get any byte
			return bytes_read;
		}
#if !TARGET_JVM
		[DllImport("kernel32", SetLastError = true)]
		static extern unsafe bool WriteFile (int handle, byte* buffer, int bytes_to_write,
				out int bytes_written, IntPtr overlapped);
#endif
		public override void Write (byte [] buffer, int offset, int count)
		{
			CheckDisposed ();
			if (buffer == null)
				throw new ArgumentNullException ("buffer");
			if (offset < 0 || count < 0)
				throw new ArgumentOutOfRangeException ();
			if (buffer.Length - offset < count)
				throw new ArgumentException ("offset+count",
							     "The size of the buffer is less than offset + count.");
			int bytes_written = 0;
			unsafe {
				fixed (byte* ptr = buffer) {
					if (WriteFile (handle, ptr + offset, count, out bytes_written, write_overlapped))
						return;
					if (Marshal.GetLastWin32Error() != FileIOPending)
						ReportIOError (null);
					
					if (!GetOverlappedResult(handle, write_overlapped, ref bytes_written, true))
						ReportIOError (null);
				}
			}
			// If the operation timed out, then
			// we transfered less bytes than the requested ones
			if (bytes_written < count)
				throw new TimeoutException ();
		}
		[DllImport("kernel32", SetLastError = true)]
		static extern bool GetCommState (int handle, [Out] DCB dcb);
		[DllImport ("kernel32", SetLastError=true)]
		static extern bool SetCommState (int handle, DCB dcb);
		public void SetAttributes (int baud_rate, Parity parity, int data_bits, StopBits bits, Handshake hs)
		{
			DCB dcb = new DCB ();
			if (!GetCommState (handle, dcb))
				ReportIOError (null);


Answer: 			dcb.SetValues (baud_rate, parity, data_bits, bits, hs);