/* 

   Copyright (C) Arnaud Houdelette		2012-2014
   Copyright (C) Emmanuel Langlois		2012-2014

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License.
   
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.
   
   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

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

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

		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 DriverBoitierV1 (string serialport, string id): base(id)
		{
			portname = serialport;
			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 DEBUG
			Console.WriteLine ("DriverV1.Connection()");
#endif
			if (serial != null) {
				serial.Close();
				serial.Dispose();
				serial = null;
			}
			if (!System.IO.File.Exists (portname)) {
				Thread.Sleep (200);
				return;
			}
			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(!disposed && 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 {
				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 {
				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;
			}
			if(serial != null)
				serial.Dispose();

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

	

		#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; 
			EventData evd;
			while (eventsPending.TryDequeue(out bt)) {
				evd.id= string.Format("BV1-B{0}",bt.button );
				evd.value = bt.pressed?(byte)0xFF:(byte)0x00;
				evd.prev_value = (!bt.pressed)?(byte)0xFF:(byte)0x00;
				callback(evd);
			}
		}

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

		IFeedbackInfo IEventProvider.GetFeedbackInfo (string eventId)
		{
			return null;
		}

		#endregion

		#region implemented abstract members of DMX2.DriverDMX
		public override void Save (System.Xml.XmlElement parent)
		{
			System.Xml.XmlElement el = parent.OwnerDocument.CreateElement ("DriverBoitierV1");

			parent.AppendChild (el);

			el.SetAttribute ("portname", portname.ToString ());
			el.SetAttribute ("id", ID);

			if(patch!=null)	el.SetAttribute ("univers", patch.Nom);
		}
		#endregion

		public static new DriverDMX Load(Conduite conduite, XmlElement el) {
			//System.Xml.XmlElement xmlE;

			string port = el.GetAttribute("portname");

			if(! System.IO.File.Exists(port)) return null;

			string id = el.GetAttribute("id");

			DriverBoitierV1 drv = new DriverBoitierV1(port,id);

			if(el.HasAttribute("univers"))
			{
				string univ = el.GetAttribute("univers");
				foreach (UniversDMX u in conduite.Patches){
					if(u.Nom== univ){
						drv.patch = u;
						break;
					}
				}
			}
			return drv;
		}

	}
}

