using System;
using System.Threading;
using System.IO.Ports;

namespace DMX2
{
	public class DriverBoitierV1 : DriverDMX, IEventProvider 
	{

		struct buttonState {
			public buttonState(byte _button, bool _pressed){
				button=_button; pressed=_pressed;
			}
			public byte button;
			public bool pressed;
		}

		bool[] buttons = new bool[8];
		bool[] watchButtons = new bool[8];


		// tampons Entrée/Sortie
		byte[] inputbuffer = new byte[1]; 
		byte[] outputbuffer = new byte[260];

		//Thread de boucle
		Thread loopthread=null;
		volatile bool running=true;

		UniversDMX patch=null;

		string portname = "/dev/ttyUSB0";
		SerialPort serial = null;

		public DriverBoitierV1 ()
		{
			patch = Conduite.Courante.Patches[0];
			Start();
			outputbuffer[0]=27;
			outputbuffer[1]=68;
			outputbuffer[4]=255;

		}

		void Start ()
		{
			OpenPort();
			if (loopthread == null) {
				loopthread = new Thread(new ThreadStart(MainLoop));
				loopthread.Start();
			}
		}

		void OpenPort ()
		{
			if (serial != null) {
				serial.Close();
			}
			serial = new SerialPort(portname, 460800,Parity.None,8,StopBits.One);
			serial.DtrEnable = false;
			serial.ReadTimeout = 15;
			serial.WriteTimeout = 200;
			serial.Open();
		}

		bool CheckPortStatus ()
		{
			if(serial.IsOpen) return true;

			OpenPort();

			return false;
		}


		void MainLoop ()
		{
			DateTime prochainEnvoi= DateTime.Now;
			TimeSpan sleeptime;
			while (running) {
				lock(Conduite.Courante)
				{
					patch.CalculUnivers(outputbuffer,5,255);
				}

				if(!CheckPortStatus())
				{
					Thread.Sleep(1000);
					continue;
				}

				sleeptime = prochainEnvoi - DateTime.Now;
				if(sleeptime.TotalMilliseconds>1) 
					Thread.Sleep(sleeptime);

				prochainEnvoi = DateTime.Now.AddMilliseconds(22);
				Envoi();
				Reception();

			}
		}

		void Envoi ()
		{
			try {

				if(!serial.IsOpen) return;
				serial.Write(outputbuffer,0,outputbuffer.Length);

			} catch (TimeoutException ex) {
				serial.Close();
			}
		}

		void Reception ()
		{
			try {

				if(!serial.IsOpen) return;

				serial.Read(inputbuffer,0,inputbuffer.Length);
				//Console.WriteLine(inputbuffer[0]);
				ProcessInput();

			} catch (TimeoutException ex) {
				serial.Close();
			}
		}


		void ProcessInput ()
		{
			byte b = 1; bool pressed;
			for (byte i = 0; i<8; i++) {
				if(!watchButtons[i]) continue;
				pressed = (inputbuffer[0] & b) != 0;
				if(buttons[i]^pressed)
				{
					eventsPending.Enqueue(new buttonState(i,pressed));
					buttons[i] = pressed;
				}
				b <<= 1;
			}
		}

		#region implemented abstract members of DMX2.DriverDMX
		public override Gtk.Window GetDialog ()
		{
			return null;
		}
		#endregion

		public override void Dispose ()
		{
			disposed = true;
			running = false;
			loopthread.Join();
			loopthread=null;

			//TODO : Close Port
			if(serial != null)
				serial.Close();

		}		

		#region IEventProvider implementation


		static System.Text.RegularExpressions.Regex regexEventID = new System.Text.RegularExpressions.Regex(
			@"BV1-B(?<button>\d+)?",
			System.Text.RegularExpressions.RegexOptions.Compiled);

		System.Collections.Concurrent.ConcurrentQueue<buttonState> eventsPending = 
			new System.Collections.Concurrent.ConcurrentQueue<buttonState>();

		bool IEventProvider.Bind (string eventId)
		{
			var res = regexEventID.Match (eventId);
			if (res.Success) {
				int bt = int.Parse (res.Groups ["button"].Value);
				if(bt<0||bt>7) return false;
				watchButtons[bt] = true;
				return true;
			}
			return false;
		}

		void IEventProvider.Unbind (string eventId)
		{
			var res = regexEventID.Match (eventId);
			if (res.Success) {
				int bt = int.Parse (res.Groups ["button"].Value);
				if(bt<0||bt>7) return ;
				watchButtons[bt] = false;
			}
			return;
		}

		Gtk.Menu IEventProvider.GetProviderSubMenu (EventManager.EventMenuData state, Gtk.ButtonPressEventHandler handler)
		{
			Gtk.Menu retmenu = new Gtk.Menu ();

			Gtk.MenuItem evmenuitem = new Gtk.MenuItem ("Boutons");
			retmenu.Add (evmenuitem);
			Gtk.Menu evmenu = new Gtk.Menu ();
			evmenuitem.Submenu = evmenu;

			for (int i= 0; i<8;i++ ) {
				Gtk.MenuItem item = new Gtk.MenuItem(string.Format("Bouton {0}",i+1));
				item.Data[EventManager.EventIdKey] = string.Format("BV1-B{0}",i);
				item.Data[EventManager.StateKey] = state;
				item.ButtonPressEvent += handler;
				evmenu.Add (item);
			}
			return retmenu;
		}

		void IEventProvider.ProcessEvents (EventManagerCallback callback)
		{
			buttonState bt; string id; EventData evd;
			while (eventsPending.TryDequeue(out bt)) {
				evd.id= string.Format("BV1-B{0}",bt.button );
				evd.value = bt.pressed?(byte)0xFF:(byte)0x00;
				callback(evd);
			}
		}

		string IEventProvider.MenuName {
			get {
				return "Boitier V1";
			}
		}
		#endregion


	}
}

