using System;
using System.Collections.Generic;
using System.Linq;

namespace DMX2
{

	public struct EventData
	{
		public string id;
		public byte value;
	}


	public interface IEventProvider
	{
		string MenuName{ get; }
		//ICollection<string>  GetEventList();		
		bool Bind (string eventId);
		void Unbind (string eventId);
		Gtk.Menu GetProviderSubMenu( EventManager.EventMenuData state, Gtk.ButtonPressEventHandler handler );
		void ProcessEvents(EventManagerCallback callback);
	}

	public interface IEventTarget
	{
		bool FireEvent(EventData data);
		void Bind(string id);
		void Unbind(string id);
		IEnumerable<string> IDs { get; }
	}

	public delegate void EventManagerCallback(EventData data);
	public delegate void EventManagerMenuCallBack(object state, string eventId);

	public class actionEventTarget : IEventTarget {
		public delegate bool EventAction (EventData data);
		EventAction action; 
		List<string> eventIds = new List<string>();
		public actionEventTarget(EventAction _action)
		{
			action=_action;
		}		
		#region IEventTarget implementation
		bool IEventTarget.FireEvent (EventData data)
		{
			return action(data);
		}

		void IEventTarget.Bind (string id)
		{
			if(!eventIds.Contains(id))
				eventIds.Add(id);
		}

		void IEventTarget.Unbind (string id)
		{
			eventIds.Remove(id);
		}

		IEnumerable<string> IEventTarget.IDs {
			get {
				return eventIds;
			}
		}
		#endregion
	}


	public class EventManager
	{
		static public object StateKey = new object();
		static public object EventIdKey = new object();

		class eventBinding
		{
			List<IEventTarget> targets=new List<IEventTarget>();
			public void AddTarget(IEventTarget target)
			{
				if(!targets.Contains(target))
					targets.Add(target);
			}
			public void RemoveTarget(IEventTarget target)
			{
				targets.Remove(target);
			}
			public ICollection<IEventTarget> Targets {
				get {
					return targets;
				}
			}
		}

		Dictionary<string,eventBinding> bindings = new Dictionary<string,eventBinding>();
		List<IEventProvider> providers = new List<IEventProvider>();

		public EventManager ()
		{

		}

		public void RegisterProvider (IEventProvider prov)
		{
			providers.Add (prov);


		}

		public class EventMenuData {
			public EventManagerMenuCallBack CallBack;
			public object state;
		}

		public Gtk.Menu GetMenu (object state, EventManagerMenuCallBack callback)
		{
			Gtk.Menu menu = new Gtk.Menu ();
			EventMenuData evd = new EventMenuData();
			evd.CallBack = callback;
			evd.state = state;

			Gtk.ButtonPressEventHandler handler = new Gtk.ButtonPressEventHandler (HandleButtonPressEvent);

			foreach (IEventProvider prov in providers) {
				Gtk.MenuItem provitem = new Gtk.MenuItem(prov.MenuName);
				provitem.Submenu = prov.GetProviderSubMenu(evd, handler);
				if(provitem.Submenu!=null)	menu.Add(provitem);
			}

			Gtk.MenuItem itemNone = new Gtk.MenuItem("Aucun");
			itemNone.Data[StateKey] = evd;
			itemNone.Data[EventIdKey] = "";
			itemNone.ButtonPressEvent += handler;
			menu.Add(itemNone);

			return menu;

		}

		void HandleButtonPressEvent (object o, Gtk.ButtonPressEventArgs args)
		{
			Gtk.MenuItem item = o as Gtk.MenuItem;
			EventMenuData evd = item.Data[EventManager.StateKey] as EventMenuData;
			string id = item.Data[EventManager.EventIdKey] as string;
			evd.CallBack(evd.state,id);
		}

		public void ProcessEvents ()
		{
			foreach (IEventProvider prov in providers) {
				prov.ProcessEvents(new EventManagerCallback(EventCallBack));
			}
		}

		public void UnregisterProvider (IEventProvider prov)
		{
			providers.Remove(prov);
		}

		public bool Bind (string eventId, IEventTarget target)
		{
			if(!bindings.ContainsKey(eventId)) 
				bindings.Add (eventId,new eventBinding());
			bindings[eventId].AddTarget(target);
			target.Bind(eventId);
			foreach (IEventProvider prov in providers) {
				if(prov.Bind(eventId)) return true;
			}
			return false;
		}

		public void Unbind (IEventTarget target)
		{
			var q = from bind in bindings where bind.Value.Targets.Contains(target) select bind.Key;
			foreach(string id in q.ToArray()) 
				Unbind(id,target);
		}


		public void Unbind (string eventId, IEventTarget target)
		{
			if (!bindings.ContainsKey (eventId)) return;
			bindings [eventId].RemoveTarget (target);
			if (bindings [eventId].Targets.Count == 0) {
				bindings.Remove(eventId);
				foreach (IEventProvider prov in providers) {
					prov.Unbind(eventId);
				}			
			}

			target.Unbind(eventId);
		}

		public void EventCallBack (EventData data)
		{
			Console.WriteLine("Event {0} => {1}",data.id,data.value);
			if (bindings.ContainsKey (data.id)) {
				foreach (IEventTarget target in bindings[data.id].Targets) {
					target.FireEvent(data);
				}
			}			
		}

		public static bool SaveBindings (System.Xml.XmlElement xmlParent, IEventTarget target)
		{
			bool ret=false;
			System.Xml.XmlElement xmlB;
			foreach (string id in target.IDs) {
				ret=true;
				xmlParent.AppendChild(xmlB = xmlParent.OwnerDocument.CreateElement("EventBinding"));
				xmlB.SetAttribute("id",id);
			}
			return ret;
		}

		public static string[] LoadBindings (System.Xml.XmlElement xmlParent)
		{
			var all=xmlParent.GetElementsByTagName("EventBinding");
			string[] ret = new string[all.Count]; int index=0;
			foreach (var xb in all) {
				ret[index++] = (xb as System.Xml.XmlElement).GetAttribute("id");
			}
			return ret;
		}

	}
}

