using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Ports;
using System.Threading;

namespace LinkSupport
{

	#region Enums

	public enum LinkType
	{
		ComPort,
		LogFile
	}

	public class LinkEvent
	{
		public virtual bool Parse(BinaryReader Data)
		{
			try
			{
				Data.BaseStream.Position = 0;
				return true;
			}
			catch
			{
				return false;
			}
		}
	}

	#endregion

	public delegate void ReceiveEventDelegate(LinkEvent Event);

	#region Linker

	public class Linker
	{
		private const StreamWriter LogFile = null;
		private readonly LinkType fLinkType;
		private readonly int fPortNumber;
		protected int fErrorCount;
		protected string fFatalError = "";
		protected int fLastDataTime;
		protected string fParseError = "";

		public ReceiveEventDelegate OnReceiveEvent;
		protected SerialDevice P;

		public Linker()
		{
			fLinkType = LinkType.LogFile;
		}
		public Linker(int PortNumber)
		{
#if _LOGFILE_
			try
			{
				string ProgramDir = System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase;
				string[] strs = ProgramDir.Split('\\');
				ProgramDir = "";
				for (int i = 0; i < strs.GetLength(0) - 1; i++)
					ProgramDir += strs[i] + "\\";
				if (!Directory.Exists(ProgramDir+"WorkDir\\Log\\"))
					Directory.CreateDirectory(ProgramDir + "WorkDir\\Log\\");

				ProgramDir += "WorkDir\\Log\\" + DateTime.Now.ToString("dd-MM-yyyy_HH-mm-ss") + ".txt";

				LogFile = new StreamWriter(ProgramDir);
			}
			catch { }
#endif
			fLinkType = LinkType.ComPort;
			fPortNumber = PortNumber;

			P = new WinSerialDevice(PortNumber);

			try
			{
				P.Open();
			}
			catch
			{
				Dispose();
#if _ENGLISH_
				fFatalError = "ERROR OPENING PORT COM" + PortNumber.ToString();
#else
				fFatalError = "ÎØÈÁÊÀ ÎÒÊÐÛÒÈß ÏÎÐÒÀ COM" + PortNumber;
#endif
			}

			fLastDataTime = Environment.TickCount;
		}

		public LinkType LinkType
		{
			get { return fLinkType; }
		}

		public bool Connected
		{
			get
			{
				bool res = false;
				try
				{
					if (P != null) res = P.isOpen;
				}
				catch
				{
				}
				return res;
			}
		}

		public string FatalError
		{
			get
			{
				string s = "";
				if (P != null && string.IsNullOrEmpty(fFatalError))
					s = P.fError;
				return fFatalError + s;
			}
			set { fFatalError = ""; }
		}

		public string ParseError
		{
			get { return fParseError; }
			set { fParseError = ""; }
		}

		public int PortNumber
		{
			get { return fPortNumber; }
		}

		public int LastDataTime
		{
			get { return fLastDataTime; }
		}

		public int ErrorCount
		{
			get { return fErrorCount; }
		}

		public void Dispose()
		{
			try
			{
				if (P != null)
				{
					Thread CloseThread = new Thread(new ThreadStart(delegate
					                                                	{
					                                                		try
					                                                		{
					                                                			P.Close();
					                                                			P.Dispose();
					                                                			P = null;
					                                                		}
					                                                		catch
					                                                		{
					                                                		}
					                                                	}));
					CloseThread.IsBackground = true;
					CloseThread.Start();
				}
			}
			catch
			{
			}

			if (LogFile != null)
				LogFile.Close();
		}

		protected void SendData(byte[] bb)
		{
			byte[] data = bb;
			if (LogFile != null)
				try
				{
					string s = DateTime.Now.ToString("HH-mm-ss-ff") + ">Down - ";
					for (int i = 0; i < data.Length; i++)
						s += data[i].ToString("X2");
					LogFile.WriteLine(s);
				}
				catch
				{
				}
			try
			{
				P.Write(data);
			}
			catch
			{
#if _ENGLISH_
              fFatalError = "ERROR WRITE TO PORT";
#else
				fFatalError = "ÑÁÎÉ ÇÀÏÈÑÈ Â ÏÎÐÒ";
#endif
			}
		}

		public virtual void Update()
		{
			if (P == null)
			{
#if _ENGLISH_
				  fFatalError = "ACCESS TO CLOSED PORT";
#else
				fFatalError = "ÎÁÐÀÙÅÍÈÅ Ê ÇÀÊÐÛÒÎÌÓ ÏÎÐÒÓ";
#endif
				return;
			}
			if (P.isOpen)
			{
				int BytesCount;
				// ïðèåì äàííûõ èç COM ïîðòà
				try
				{
					BytesCount = P.BytesToRead;
				}
				catch (Exception e)
				{
#if _ENGLISH_
					fFatalError = "ERROR READING FROM PORT";
#else
					fFatalError = "ÑÁÎÉ ×ÒÅÍÈß ÈÇ ÏÎÐÒÀ \r" + e.Message;
#endif
					return;
				}
				if (BytesCount > 0)
				{
					try
					{
						byte[] data = P.Read();
						try
						{
							OnReceiveData(data);
							fLastDataTime = Environment.TickCount;
						}
						catch (Exception E)
						{
#if _ENGLISH_
							fFatalError = "PARSING ERROR \n\"" + E.ToString() + "\"";
#else
							fFatalError = "ÑÁÎÉ ÎÁÐÀÁÎÒÊÈ\n\"" + E + "\"";
#endif
						}
/*
						data = null;
*/
					}
					catch (Exception e)
					{
#if _ENGLISH_
						fFatalError = "ERROR READING FROM PORT";
#else
						fFatalError = "ÑÁÎÉ ×ÒÅÍÈß ÄÀÍÍÛÕ ÈÇ ÏÎÐÒÀ \r" + e.Message;
#endif
					}
				}
			}
		}

		protected virtual void OnReceiveData(byte[] data)
		{
			if (LogFile != null)
				try
				{
					string s = DateTime.Now.ToString("HH-mm-ss-ff") + ">Up - ";
					for (int i = 0; i < data.Length; i++)
						s += data[i].ToString("X2");
					LogFile.WriteLine(s);
				}
				catch
				{
				}
		}
	}

	#endregion

	#region WinSerialDevice

	public class WinSerialDevice : SerialDevice
	{
		private bool Closed = true;
		private SerialPort Port;

		public WinSerialDevice(int name) : base(name)
		{
			Port = new SerialPort
			       	{
			       		PortName = (string.Format("COM{0}", fPortNumber)),
			       		BaudRate = 9600,
			       		DataBits = 8,
			       		Parity = Parity.None,
			       		StopBits = StopBits.One,
			       		ReceivedBytesThreshold = 1,
			       		ReadTimeout = 5000,
			       		WriteTimeout = 5000,
			       		ReadBufferSize = 16384,
			       		WriteBufferSize = 16384,
			       		RtsEnable = true,
			       		DtrEnable = true,
			       		DiscardNull = false,
			       		Handshake = Handshake.None
			       	};
			Port.ErrorReceived += Port_ErrorReceived;
		}

		public override int BytesToRead
		{
			get
			{
				if (Port != null && Port.IsOpen)
					if (Port.BytesToRead > 0)
					{
						return Port.BytesToRead;
					}
				return 0;
			}
		}

		public override bool isOpen
		{
			get
			{
				if (Port != null)
					return Port.IsOpen && !Closed;
				return false;
			}
		}

		public override void Dispose()
		{
			try
			{
				Closed = true;
				if (WriteThread != null)
					WriteThread.Join();
				if (Port != null && Port.IsOpen)
				{
					Port.DiscardInBuffer();
					Port.DiscardOutBuffer();
					Port.Dispose();
				}
				Port = null;
			}
			catch
			{
			}
			base.Dispose();
		}

		private void Port_ErrorReceived(object sender, SerialErrorReceivedEventArgs e)
		{
			//Dispose();
		}

		public override void Open()
		{
			try
			{
				Closed = false;
				Port.Open();
				base.Open();
			}
			catch (Exception e)
			{
				Closed = true;
				throw new Exception(e.Message);
			}
		}

		public override void Close()
		{
			if (Port != null && Port.IsOpen)
			{
				try
				{
					Closed = true;
					WriteThread.Join();
					WriteThread = null;
					Port.Close();
				}
				catch
				{
				}
			}
			base.Close();
		}

		public override byte[] Read()
		{
			byte[] d = new byte[0];
			if (Port != null && Port.IsOpen)
			{
				try
				{
					d = new byte[BytesToRead];
					Port.Read(d, 0, d.Length);
					return d;
				}
				catch (Exception e)
				{
					throw new Exception(e.Message);
				}
			}
			return d;
		}

		public override void Write(byte[] data)
		{
			if (Port != null && Port.IsOpen)
				TxQueue = data;
			//Port.Write(data, 0, data.Length);
		}

		protected override void WriteThreadProc()
		{
			byte[] DataToSend;
			fError = "";
			try
			{
				while (isOpen)
				{
					DataToSend = TxQueue;
					while (DataToSend != null && DataToSend.Length != 0 && isOpen)
					{
						if (isOpen)
						{
							if (Port.IsOpen) Port.Write(DataToSend, 0, DataToSend.Length);
							DataToSend = TxQueue;
						}
					}
					Thread.Sleep(10);
				}
				DataToSend = TxQueue;
				while (DataToSend != null && DataToSend.Length != 0)
				{
					if (Port.IsOpen)
					{
						Port.DtrEnable = false;
						Port.Write(DataToSend, 0, DataToSend.Length);
						Port.DtrEnable = true;
					}
					else
						break;
					DataToSend = TxQueue;
				}
				Thread.Sleep(100);
			}
			catch
			{
#if _ENGLISH_
				fError = "ERROR WRITE TO PORT";
#else
				fError = "ÑÁÎÉ ÇÀÏÈÑÈ Â ÏÎÐÒ";
#endif
			}
		}
	}

	#endregion

	public class SerialDevice
	{
		public string fError = "";
		protected int fPortNumber;
		protected Queue<byte[]> fTxQueue = new Queue<byte[]>();
		protected Mutex WriteMutex = new Mutex();
		protected Thread WriteThread;


		public SerialDevice(int ComNumber)
		{
			fPortNumber = ComNumber;
		}

		protected byte[] TxQueue
		{
			get
			{
				byte[] res = null;
				WriteMutex.WaitOne();
				if (fTxQueue.Count > 0)
					res = fTxQueue.Dequeue();
				WriteMutex.ReleaseMutex();
				return res;
			}
			set
			{
				if (value != null && value.Length > 0)
				{
					WriteMutex.WaitOne();
					fTxQueue.Enqueue(value);
					WriteMutex.ReleaseMutex();
				}
			}
		}

		public virtual int BytesToRead
		{
			get { return 0; }
		}

		public virtual bool isOpen
		{
			get { return false; }
		}

		public virtual void Open()
		{
			fTxQueue.Clear();
			if (WriteThread == null)
			{
				WriteThread = new Thread(WriteThreadProc)
				              	{
				              		Name = "WriteComThread",
				              		Priority = ThreadPriority.Highest,
				              		IsBackground = true
				              	};
				WriteThread.Start();
				WriteThread.Priority = ThreadPriority.Highest;
			}
		}

		public virtual void Write(byte[] data)
		{
		}

		public virtual byte[] Read()
		{
			return new byte[0];
		}

		public virtual void Close()
		{
			if (WriteThread != null)
				WriteThread.Join();
			WriteThread = null;
		}

		public virtual void Dispose()
		{
			if (WriteThread != null)
				WriteThread = null;
		}

		protected virtual void WriteThreadProc()
		{
		}
	}
}