using System;
using Medivators.Common;
using Medivators.Common.Enums;
using Medivators.Common.Settings;
using Sax.Communications;

namespace Medivators.Comm
{
	/// <summary>
	/// Summary description for SerialPort.
	/// </summary>
	public class CommPort : ICommDevice
	{
		#region declarations

		private const SupportedCommDevices			_device			= SupportedCommDevices.CommPort;
		private bool								_initializing	= true;
		private Sax.Communications.SerialConnection	_connection		= null;
		private CommPortSettings					_settings;
		private CommPorts							_port = CommPorts.COM1;

		public event DataArrivedEventHandler		DataArrived;
		public event CommErrorEventHandler			CommError;
		public event SettingsChangedEventHandler	SettingsChanged;
		
		#endregion

		#region construct/init/dispose

		public CommPort()
		{
			_initializing = true;
			Initialize();
			_initializing = false;
		}
		public void Dispose()
		{
			if ( this.IsOpen )
			{
				this._connection.Flush(true);
				this._connection.Close();
			}
			if ( this._connection != null )
				this._connection.Dispose();
		}
		private void Initialize()
		{
			this._settings				= new CommPortSettings();
			this._settings.BaudRate		= BaudRates.bps9600;
			this._settings.DataBit		= DataBits.Eight;
			this._settings.Parity		= Parities.None;
			this._settings.StopBit		= StopBits.One;
			this._settings.HandShake	= HandShakes.None;
		}

		#endregion

		#region common Comm device methods - implments ICommDevice

		public SupportedCommDevices	Device
		{
			get { return _device; }
		}
		public CommPortSettings	Settings
		{
			get { return this._settings; }
			set 
			{
				try
				{
					if ( ! value.Equals(this._settings) )
					{
						this._settings = value;
						if ( ! this._initializing )
						{
							if ( this.IsOpen )
								this.CloseCommPort();						
							ApplyCommSettings();
							SettingsChangedEventArgs e = new SettingsChangedEventArgs(value);
							this.OnSettingsChanged(e);
						}
					}
				}
				catch ( Exception ex )
				{
					if ( ex.GetType().Equals(typeof(NullReferenceException)))
						this.OnCommError(CommEvent.Unknown, "Please open the Comm Port before using it!");
					else
						this.OnCommError(CommEvent.Unknown, "CommPortSettings reports exception: " + ex.ToString());
				}
			}
		}
		public CommPorts Port
		{
			get { return this._port; }
			set { this._port = value; }
		}
		public bool IsOpen
		{
			get 
			{
				bool rtnVal = false;
				try
				{
					if ( this._connection != null )						
						rtnVal = this._connection.IsOpen;
				}
				catch ( Exception ex)
				{
					if ( ex.GetType().Equals(typeof(NullReferenceException)))
						this.OnCommError(CommEvent.Unknown, "Please open the Comm Port before using it!");
					else
						this.OnCommError(CommEvent.Unknown, "CommPortSettings reports exception: " + ex.ToString());
				}
				return rtnVal;
			}
		}
		public void	OpenCommPort()
		{
			try
			{
				if (!this.IsOpen)
				{
					//  request the connection from the 
					//	connection dispatcher
					if (this._connection == null)
						SetConnection();
					ApplyCommSettings();
					//	we may have been passed an open connection
					//	from the dispatcher
					if( ! this._connection.IsOpen )
						this._connection.Open();
				}
			}
			catch (Exception ex)
			{
				this.OnCommError(CommEvent.Unknown, "OpenCommPort reports exception: " + ex.ToString());
			}
		}
		public void	CloseCommPort()
		{
			try
			{
				this._connection.Close();
			}
			catch ( Exception closeEx )
			{
				this.OnCommError(CommEvent.Unknown, "CloseCommPort reports exception: " + closeEx.ToString());
			}
		}
		public System.Windows.Forms.DialogResult ShowControlDialog()
		{		
			using ( dlgCommPort dlg = new dlgCommPort() )
			{
				dlg.Device = this;
				dlg.ShowDialog();
				return dlg.DialogResult;
			}
		}

		public void WriteByte(Byte Value) 
		{
			byte[] buffer = new byte[1];
			try																		   
			{
				//   if port is not open, try to open it
				if ( ! this.IsOpen )
					this.OpenCommPort();
				buffer[0] = Value;
				this._connection.Write(buffer, 0, 1);
			}
			catch ( Exception WriteEx )
			{
				this.OnCommError(CommEvent.Unknown, "WriteByte reports exception: " + WriteEx.ToString());
			}
		}
		public void WriteString( string Value )
		{
			if ( ( Value != null ) && ( Value.Length > 0 ) )
			{				
				try
				{
					//   if port is not open, try to open it
					if ( ! this.IsOpen )
						this.OpenCommPort();

					byte[] buffer = Utilities.StringToByteArray(Value);
					this._connection.Write(buffer, 0, buffer.Length);
				}
				catch ( Exception WriteEx )
				{
					this.OnCommError(CommEvent.Unknown, WriteEx.ToString());
				}
			}
		}
		
		
		#endregion

		#region events

		internal virtual void OnDataArrived ( DataArrivedEventArgs e )
		{
			DataArrived(this, e);
		}

		internal virtual void OnCommError ( CommErrorEventArgs e )
		{
			CommError(this, e);
		}
		internal virtual void OnCommError ( Exception ex )
		{
			CommErrorEventArgs e = new CommErrorEventArgs ( CommEvent.Unknown, ex.ToString());
			this.OnCommError ( e );
		}
		internal virtual void OnCommError ( CommEvent commEvent, string Message  )
		{
			CommErrorEventArgs e = new CommErrorEventArgs ( commEvent, Message );
			this.OnCommError ( e );
		}
		internal virtual void OnSettingsChanged ( SettingsChangedEventArgs e )
		{
			try { SettingsChanged(this, e); }
			catch (Exception ex) {ErrorHandler.GetInstance().LogException("CommPort.OnSettingsChanged error: " + ex.ToString());}
		}

		
		//   these methods provide eventhandlers for
		//   the Sax dll and, in turn, raise Medivators
		//   ISerialDevice events

		private void Connection_DataAvailable(object sender, System.EventArgs e)
		{
			try
			{
				//   sax sample code recommends sleeping a moment
				//   to allow communication to complete
				System.Threading.Thread.Sleep(250);

				//   how many bytes are waiting?
				int bytes = this._connection.Available;
				byte[] readBuffer = new byte[(bytes)];
				if ( bytes > 0 )
				{					
					//   get bytes
					this._connection.Read(readBuffer, 0, readBuffer.Length);
				}
				DataArrivedEventArgs args = new DataArrivedEventArgs(readBuffer) ;

				//   make sure we actually received something
				if ( args.Data.Length != 0 )
				{
					//   notify calling code 
					this.OnDataArrived(args);
				}
			}
			catch ( Exception ReadEx )
			{
				this.OnCommError(CommEvent.Unknown, ReadEx.Message);
			}		
		}
		private void Connection_DeviceError(object sender, Sax.Communications.SerialConnection.CommErrorEventArgs e)
		{
			CommErrorEventArgs args = new CommErrorEventArgs(CommEvent.Unknown, e.Message);
			this.OnCommError(args);
		}

		
		#endregion

		#region individual comm port settings
		
		public BaudRates BaudRate
		{
			get { return this.Settings.BaudRate; }
			set 
			{
				if ( value != this.Settings.BaudRate )
					this._settings.BaudRate = value;
			}			
		}
		public DataBits DataBit
		{
			get { return this.Settings.DataBit; }
			set 
			{
				if ( value != this.Settings.DataBit )
					this._settings.DataBit = value;
			}			
		}
		public HandShakes HandShake
		{
			get { return this.Settings.HandShake; }
			set 
			{
				if ( value != this.Settings.HandShake )
					this._settings.HandShake = value;
			}			
		}
		public Parities Parity
		{
			get { return this.Settings.Parity; }
			set 
			{
				if ( value != this.Settings.Parity )
					this._settings.Parity = value;
			}			
		}
		public StopBits StopBit
		{
			get { return this.Settings.StopBit; }
			set 
			{
				if ( value != this.Settings.StopBit )
					this._settings.StopBit = value;
			}			
		}
		
		#endregion

		#region  private worker methods

		private void ApplyCommSettings()
		{
			SerialOptions saxOptions = new SerialOptions();

			//
			//   Lots of Case statements here as we translate
			//   our structures and enums to Sax structures
			//   and enum
			//

			switch ( this.Settings.BaudRate )
			{
				case BaudRates.bps300: 
					saxOptions.BaudRate = 300;
					break;
				case BaudRates.bps1200: 
					saxOptions.BaudRate = 1200;
					break;
				case BaudRates.bps2400: 
					saxOptions.BaudRate = 2400;
					break;
				case BaudRates.bps4800:
					saxOptions.BaudRate = 4800;
					break;
				case BaudRates.bps9600: 
					saxOptions.BaudRate = 9600;
					break;
				case BaudRates.bps19200: 
					saxOptions.BaudRate = 19200;
					break;
				case BaudRates.bps28800: 
					saxOptions.BaudRate = 28800;
					break;
				case BaudRates.bps38400: 
					saxOptions.BaudRate = 38400;
					break;
				case BaudRates.bps57600: 
					saxOptions.BaudRate = 57600;
					break;
				case BaudRates.bps115200: 
					saxOptions.BaudRate = 115200;
					break;
			}

			saxOptions.PortName = System.Enum.GetName(this.Port.GetType(), this.Port);

			switch ( this.Settings.DataBit )
			{
				case DataBits.Seven:
					saxOptions.DataBits = 7;
					break;
				case DataBits.Eight:
					saxOptions.DataBits = 8;
					break;
			}

			saxOptions.FlowControlCtsRts = false;
			saxOptions.FlowControlDsrDtr = false;
			saxOptions.FlowControlRs485 = false;
			saxOptions.FlowControlXonXoff = false;

			switch ( this.Settings.HandShake )
			{
				case HandShakes.CtsRts:
					saxOptions.FlowControlCtsRts = true;
					break;
				case HandShakes.DtrDsr:
					saxOptions.FlowControlDsrDtr = true;
					break;
				case HandShakes.XonXoff:
					saxOptions.FlowControlXonXoff = true;
					break;
			}

			switch ( this.Settings.Parity )
			{
				case Parities.Even: 
					saxOptions.Parity = Sax.Communications.Parity.Even;
					break;
				case Parities.Mark: 
					saxOptions.Parity = Sax.Communications.Parity.Mark;
					break;
				case Parities.None: 
					saxOptions.Parity = Sax.Communications.Parity.None;
					break;
				case Parities.Odd: 
					saxOptions.Parity = Sax.Communications.Parity.Odd;
					break;
				case Parities.Space: 
					saxOptions.Parity = Sax.Communications.Parity.Space;
					break;
			}

			switch ( this.Settings.StopBit )
			{
				case StopBits.One: 
					saxOptions.StopBits = CommStopBits.One;
					break;
				case StopBits.OnePointFive: 
					saxOptions.StopBits = CommStopBits.OnePointFive;
					break;
				case StopBits.Two: 
					saxOptions.StopBits = CommStopBits.Two;
					break;
			}

			if (this._connection == null)
				SetConnection();
			this._connection.SetOptions(saxOptions);
		}

		private void SetConnection()
		{
			if (this._connection == null)
			{
				_connection = CommDispatcher.GetInstance().GetCommConnection(this.Port);
				_connection.DataAvailable += new SerialConnection.DataAvailableEventHandler(Connection_DataAvailable);
				_connection.DeviceError += new SerialConnection.DeviceErrorEventHandler(Connection_DeviceError);
			}
		}
		#endregion
	}
}