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

namespace DMX2
{
	public class DriverBoitierV2 : DriverDMX, IEventProvider 
	{

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

		enum etatAutomate {
			Deconnecte,
			Transmission,
			Erreur,
			Reset,
			Parametrage,
			Fin
		}

		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;

		public UniversDMX patch=null;

		string portname = "";
		SerialPort serial = null;

		public DriverBoitierV2 (string serialport, string id): base(id)
		{
			portname = serialport;
			//patch = Conduite.Courante.Patches[0];
			//Start();
			//serial = serialport;
			outputbuffer[0]=27;
			outputbuffer[1]=68;
			outputbuffer[4]=255;

		}

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

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

		volatile etatAutomate etat = etatAutomate.Deconnecte;
		DateTime finAttente = DateTime.Now;

		void MainLoop()
		{
			while(etat != etatAutomate.Fin)
			{
				switch (etat) {
				case etatAutomate.Deconnecte:
					Connection();
					Attente(DateTime.Now.AddMilliseconds(200));
					break;
				case etatAutomate.Transmission:
					finAttente = DateTime.Now.AddMilliseconds (22);
					EnvoiTrame();
					Reception();
					Attente(finAttente);
					break;
				case etatAutomate.Erreur:
					Deconnecte();
					Attente(DateTime.Now.AddSeconds(2));
					break;
//				case etatAutomate.Parametrage:
//					EnvoiParam();
//					break;
//				case etatAutomate.Reset:
//					EnvoiReset();
//					break;
				}
			}
			Deconnecte();
		}

		void Attente (DateTime date)
		{
			int sleeptime = (int) (date - DateTime.Now).TotalMilliseconds;
			if(sleeptime>2)
				Thread.Sleep(sleeptime);

			while (DateTime.Now<date) Thread.Sleep(1);				       
		}

		void Deconnecte ()
		{
			etat = etatAutomate.Deconnecte;
			if(serial == null)	return;

			serial.Close();
			serial.Dispose();
		}

		void EnvoiTrame ()
		{
			try {
				if(!serial.IsOpen) {
					etat = etatAutomate.Erreur; 
				}

				if(patch!=null)	patch.CalculUnivers(outputbuffer,5,255);

				serial.Write(outputbuffer,0,outputbuffer.Length);

			} catch (TimeoutException ex) {
				etat = etatAutomate.Erreur;
			}
		}

//		void EnvoiParam ()
//		{
//			throw new NotImplementedException ();
//		}
//
//		void EnvoiReset ()
//		{
//			throw new NotImplementedException ();
//		}

		void Reception ()
		{
			try {

				if(!serial.IsOpen || etat == etatAutomate.Erreur) {
					etat = etatAutomate.Erreur;
					return;
				}

				serial.Read(inputbuffer,0,inputbuffer.Length);
				ProcessInput();

			} catch (TimeoutException ex) {
				etat = etatAutomate.Erreur;
			}
		}


		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;
			}
		}

		public override void Dispose ()
		{
			disposed = true;
			etat = etatAutomate.Fin;
			if (loopthread != null) {
				loopthread.Join ();
				loopthread = null;
			}
			//TODO : Close Port
			if(serial != null)
				serial.Dispose();

		}	
		#region implemented abstract members of DMX2.DriverDMX
		public override Gtk.Widget GetUI ()
		{
			return new DriverBoitierV2UI(this);
		}
		#endregion

	

		#region IEventProvider implementation


		static System.Text.RegularExpressions.Regex regexEventID = new System.Text.RegularExpressions.Regex(
			@"BV2-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("BV2-B{0}",i);
				item.Data[EventManager.StateKey] = state;
				item.ButtonPressEvent += handler;
				evmenu.Add (item);
			}
			return retmenu;
		}

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

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


	}
}

