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

namespace DMX2
{
	public class SequenceurLineaire : Sequenceur
	{
		public class Effet
		{

			string _nom;

			public Effet (string nom, Dictionary<Circuit,int> valeurs, TimeSpan duree, TimeSpan transition)
			{
				_nom = nom;
				_valeurs = new Dictionary<Circuit, int> (valeurs);
				_duree = duree;
				_transition = transition;
			}

			public string Nom {
				get {
					return _nom;
				}
				set {
					_nom = value;
				}
			}

			Dictionary<Circuit,int> _valeurs;

			public int this [Circuit index] {
				get {
					if (!_valeurs.ContainsKey (index))
						_valeurs.Add (index, 0);
					return _valeurs [index];
				}
			}

			public Dictionary<Circuit, int> Valeurs {
				get {
					return _valeurs;
				}
			}
			public void RetireCircuit (Circuit c)
			{
				_valeurs.Remove (c);
			}

			TimeSpan _duree = TimeSpan.Zero ;
			TimeSpan _transition = TimeSpan.Zero;

			public TimeSpan Duree {
				get {
					return _duree;
				}
				set {
					_duree = value;
				}
			}

			public TimeSpan Transition {
				get {
					return _transition;
				}
				set {
					_transition = value;
				}
			}
			public void Save (System.Xml.XmlElement parent)
			{
				System.Xml.XmlElement el = parent.OwnerDocument.CreateElement ("Effet");
				System.Xml.XmlElement xmlVal;
				parent.AppendChild (el);
				el.SetAttribute("nom",_nom);
				el.SetAttribute ("duree", _duree.ToString ());
				el.SetAttribute ("transition", _transition.ToString ());
				foreach (var valeur in _valeurs) {
					xmlVal = parent.OwnerDocument.CreateElement("Valeur");
					el.AppendChild(xmlVal);
					xmlVal.SetAttribute("circuit",valeur.Key.ID.ToString());
					xmlVal.SetAttribute("valeur",valeur.Value.ToString());
				}
			}
			public static Effet Load (Conduite conduite, System.Xml.XmlElement el)
			{
				Dictionary<Circuit, int> valeurs = new Dictionary<Circuit, int> ();
				foreach (var xv in el.GetElementsByTagName("Valeur")) {
					System.Xml.XmlElement xval = xv as System.Xml.XmlElement;
					valeurs.Add(
						conduite.GetCircuitByID(int.Parse(xval.GetAttribute("circuit"))),
						int.Parse(xval.GetAttribute("valeur"))
						);
				}
				return new Effet( el.GetAttribute("nom"),valeurs,
				                 TimeSpan.Parse(el.GetAttribute("duree")),
				                 TimeSpan.Parse(el.GetAttribute("transition"))
				                 );
			}

		}

		#region EventTargets
		class circuitEventTarget : IEventTarget  {
			Circuit circuit;
			Dictionary<string,int> valeursrecues= new Dictionary<string, int>();
			SequenceurLineaire seq;
			int max=0, signe=-2;

			bool attache;

			public bool Attache {
				get {
					return attache;
				}
				set {
					signe=-2;
					attache = value;
				}
			}

			public circuitEventTarget(SequenceurLineaire _seq, Circuit _c){
				seq=_seq;
				circuit=_c;
			}

			bool IEventTarget.FireEvent (EventData data)
			{
				valeursrecues[data.id]=data.value;
				max = valeursrecues.Values.Max();
				if (!Attache) {
					int val = seq.ValeurBruteCircuit (circuit);
					int cs = Math.Sign (val - max);
					if (signe == -2) signe=cs;
					if (cs==0 || cs!=signe) Attache=true;
					else return true ;
				}
				seq.ChangeValeur(circuit,max);
				return true;
			}


			void IEventTarget.Bind (string id)
			{
				valeursrecues[id] = 0;
			}

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

			IEnumerable<string> IEventTarget.IDs {
				get {
					return valeursrecues.Keys;
				}
			}

		}

		#endregion

		TimeSpan timeStamp = TimeSpan.Zero;
		TimeSpan tempsTransition = TimeSpan.Zero;
		List<Effet> effets = new List<Effet> ();
		Effet effetcourrant = null;
		bool enTransition = false;
		bool paused=false;


		List<Circuit> circuitsSeq = new List<Circuit> ();
		Dictionary<Circuit,int> valeurscourantes = new Dictionary<Circuit, int> ();
		Dictionary<Circuit,int> valeursinitiales = new Dictionary<Circuit, int> ();
		Dictionary<Circuit,bool> valeurschangees = new Dictionary<Circuit, bool> ();
		Dictionary<Circuit,circuitEventTarget> targets = new Dictionary<Circuit, circuitEventTarget>();

		actionEventTarget masterEventTarget=null;
		actionEventTarget goNextEventTarget=null;
		actionEventTarget goBackEventTarget=null;

		SeqLinUI ui = null;

		int master = 100;

			public int Master {
				get {
					return master;
				}
				set {
					master = value;
				}
			}

		public SequenceurLineaire ()
		{
			effetcourrant = new Effet ("",valeurscourantes  , TimeSpan.Zero, TimeSpan.Zero);

			masterEventTarget = new actionEventTarget (
				delegate(EventData data) {
					Master = 100 * data.value / 255;
					return true;
				}
			);

			goNextEventTarget = new actionEventTarget (
				delegate(EventData data) {
					if(data.value>0) IndexEffetCourrant++;
					return true;
				}
			);

			goBackEventTarget = new actionEventTarget (
				delegate(EventData data) {
					if(data.value>0) IndexEffetCourrant--;
					return true;
				}
			);

		}

		public TimeSpan TimeStamp {
			get {
				return timeStamp;
			}
		}

		public ReadOnlyCollection<Circuit> Circuits {
			get {
				return circuitsSeq.AsReadOnly ();
			}
		}

		public ReadOnlyCollection<Effet> Effets {
			get {
				return effets.AsReadOnly ();
			}
		}

		public void ChangeCircuits (System.Collections.Generic.List<Circuit> list)
		{
			foreach (var c in circuitsSeq.ToArray()) {
				if (!list.Contains (c))
					lock(this) RetireCircuit (c);
			}
			foreach (var c in list)
				if (!circuitsSeq.Contains (c))
					lock(this) AjouteCircuit (c);
			circuitsSeq = list;
		}

		void AjouteCircuit (Circuit c)
		{
			valeurscourantes [c] = 0;
			valeursinitiales [c] = 0;
		}

		void RetireCircuit (Circuit c)
		{
			foreach (var ef in effets) {
				ef.RetireCircuit (c);
			}
			circuitsSeq.Remove (c);
			valeurscourantes.Remove (c);
			valeursinitiales.Remove (c);
			targets.Remove(c);
		}

		public override int ValeurCircuit (Circuit c)
		{
			lock(this) {
				if (!circuitsSeq.Contains (c)) 
					return 0;
				if (master != 100)
					return valeurscourantes [c] * master / 100;
				return valeurscourantes [c];
			}
		}

		public int ValeurBruteCircuit (Circuit c)
		{
			if (!circuitsSeq.Contains (c)) 
				return 0;
			return valeurscourantes [c];
		}

		public void ChangeValeur (Circuit c, int value)
		{
			valeurschangees [c] = true;
			valeurscourantes [c] = value;
		}

		public bool EstChange (Circuit c)
		{
			return valeurschangees.ContainsKey (c);
		}

		public bool EnTransition (Circuit c)
		{
			if(!enTransition) return false ;
			return valeurscourantes [c] != effetcourrant [c];
		}

		public bool EstCapture (Circuit c)
		{
			if(!targets.ContainsKey(c)) return false;
			return targets[c].Attache;
		}

		public bool Paused {
			get {
				return paused;
			}
			set {
				paused = value;
			}
		}

		public override void Tick (TimeSpan time)
		{
			if (paused) return;
			timeStamp += time;
			lock(this) {
				if (enTransition) {
					if (timeStamp < tempsTransition) {
						double progression = timeStamp.TotalMilliseconds / tempsTransition.TotalMilliseconds;
						foreach (Circuit c in circuitsSeq) {
							if (valeurscourantes [c] != effetcourrant [c] && !valeurschangees.ContainsKey (c)) {
								valeurscourantes [c] = (int)(progression * (effetcourrant [c] - valeursinitiales [c]) + valeursinitiales [c]);
							}
						}
					} else {
						FinDeTransition ();
					}
				}
				if (effetcourrant.Duree != TimeSpan.Zero && timeStamp >= effetcourrant.Duree) {
					int index = effets.IndexOf (effetcourrant) + 1;
					if (index < effets.Count)
						ChangeEffetCourrant (index);
				}
			}
		}



		public void FinDeTransition ()
		{
			lock(this) {
				enTransition = false;
				foreach (Circuit c in circuitsSeq)
					if (!valeurschangees.ContainsKey (c))
						valeurscourantes [c] = effetcourrant [c];
			}
		}


		public SequenceurLineaire.Effet EffetCourrant {
			get {
				return effetcourrant;
			}
		}

		public int IndexEffetCourrant {
			get {
				return effets.IndexOf(effetcourrant);
			}
			set {
				if(value>=0 && value < effets.Count)
					ChangeEffetCourrant(value);
			}
		}

		void ChangeEffetCourrant (int index)
		{
			lock (this) {
				effetcourrant = effets [index];
				valeurschangees.Clear ();
				valeursinitiales = new Dictionary<Circuit, int> (valeurscourantes);
				tempsTransition = effetcourrant.Transition;
				enTransition = true;
				timeStamp = TimeSpan.Zero;
				if (ui != null)
					ui.EffetChange ();
				foreach(var t in targets.Values) t.Attache = false;
			}
		}

		public int SauveEffet (string nom, TimeSpan duree, TimeSpan transition)
		{
			lock (this) {
				effets.Add (effetcourrant = new Effet (nom, valeurscourantes, duree, transition));
				valeurschangees.Clear ();
				return effets.Count-1;
			}
		}

		public int InsereEffetApres (int index, string nom, TimeSpan duree, TimeSpan transition)
		{
			lock (this) {
				int pos = index+1;
				if (pos >= effets.Count) return SauveEffet(nom,duree,transition);
				effets.Insert (pos,effetcourrant = new Effet (nom, valeurscourantes, duree, transition));
				valeurschangees.Clear ();
				CommandAdd(index);
				return pos;
			}
		}

		public void RemplaceEffet (int index)
		{
			lock (this) {
				Effet ef = effets[index];
				effets[index] = new Effet (ef.Nom, valeurscourantes, ef.Duree, ef.Transition);
				effetcourrant = effets[index];
				valeurschangees.Clear ();
			}
		}

		public void SupprimeEffet (int index)
		{
			lock (this) {
				effets.RemoveAt (index);
				CommandRemove(index);
			}
		}

		public int MonteEffet (int index)
		{
			lock (this) {
				if (index >= effets.Count || index < 1)
					return index;
				Effet ef = effets [index];
				effets.RemoveAt (index);
				effets.Insert (index - 1, ef);
				CommandSwap (index - 1);
				return index - 1;
			}
		}

		public int BaisseEffet (int index)
		{
			lock (this) {
				if (index > effets.Count - 2 || index < 0)
					return index;
				Effet ef = effets [index];
				effets.RemoveAt (index);
				effets.Insert (index + 1, ef);
				CommandSwap(index);
				return index + 1;
			}
		}

		/*
		private class circEvTarget : IEventTarget{
			Circuit c; SequenceurLineaire seq; int s=-2; bool ok=false;
			public circEvTarget(SequenceurLineaire _seq, Circuit _c){
				c=_c;
				seq=_seq;
			}
			#region IEventTarget implementation
			bool IEventTarget.FireEvent (EventData data)
			{
				if (!ok) {
					int val = seq.ValeurBruteCircuit (c);
					int cs = Math.Sign (val - data.value);
					if (s == -2) s =cs;
					if (cs==0 || cs!=s) ok=true;
					else return true ;
				}
				seq.ChangeValeur(c,data.value);
				return true;
			}
			#endregion

		}
		*/

		public void BindCircuitEvent (Circuit c, string eventId)
		{

			if (eventId.Length == 0) {
				if(!targets.ContainsKey(c)) return;
				Conduite.Courante.EventManager.Unbind(targets[c]);
				targets.Remove(c);
				return;
			} 

			if(!targets.ContainsKey(c)) targets[c]=new circuitEventTarget(this,c);
			Conduite.Courante.EventManager.Bind(eventId,targets[c]);
		}


		public void BindMasterEvent (string eventId)
		{
			if(eventId.Length==0)
			{
				Conduite.Courante.EventManager.Unbind(masterEventTarget);
				return;
			}
			Conduite.Courante.EventManager.Bind(eventId,masterEventTarget);
		}

		public void BindEffetSuivantEvent (string eventId)
		{
			if (eventId.Length == 0) {
				Conduite.Courante.EventManager.Unbind (goNextEventTarget);
				return;
			}
			Conduite.Courante.EventManager.Bind(eventId,goNextEventTarget);
		}

		public void BindEffetPrecedentEvent (string eventId)
		{
			if (eventId.Length == 0) {
				Conduite.Courante.EventManager.Unbind (goBackEventTarget);
				return;
			}
			Conduite.Courante.EventManager.Bind(eventId,goBackEventTarget);
		}
		                                                
		public override void Save (System.Xml.XmlElement parent)
		{
			System.Xml.XmlElement el = parent.OwnerDocument.CreateElement ("SequenceurLineaire");
			System.Xml.XmlElement xmlEl;

			parent.AppendChild (el);
			el.SetAttribute ("id", ID.ToString ());
			el.SetAttribute ("name", Name);
			//el.SetAttribute ("master", master.ToString ());

			el.AppendChild(xmlEl = parent.OwnerDocument.CreateElement ("Master"));
			xmlEl.SetAttribute("value",master.ToString());
			EventManager.SaveBindings(xmlEl,masterEventTarget);

			xmlEl = parent.OwnerDocument.CreateElement ("EffetSuivant");
			if(EventManager.SaveBindings(xmlEl,goNextEventTarget ))	el.AppendChild(xmlEl);

			xmlEl = parent.OwnerDocument.CreateElement ("EffetPrecedent");
			if(EventManager.SaveBindings(xmlEl,goBackEventTarget ))	el.AppendChild(xmlEl);

			foreach (Circuit c in circuitsSeq) {
				el.AppendChild(xmlEl = parent.OwnerDocument.CreateElement ("CircuitSeq"));
				xmlEl.SetAttribute("id",c.ID.ToString());

				if(targets.ContainsKey(c))
					EventManager.SaveBindings(xmlEl,targets[c]);

			}

			foreach (Effet ef in effets) {
				ef.Save(el);
			}

		}


		public override SequenceurUI GetUI ()
		{

			if (ui == null) {
				ui = new SeqLinUI (this);
				ui.Destroyed += UiDestroyed;
			}

			return ui;
		}

		void UiDestroyed (object sender, EventArgs e)
		{
			ui = null;
		}

		public static new SequenceurLineaire Load (Conduite conduite, System.Xml.XmlElement el)
		{
			SequenceurLineaire seq = new SequenceurLineaire();
			seq.LoadSeq(conduite,el);
			return seq;
		}

		private void LoadSeq (Conduite conduite, System.Xml.XmlElement el)
		{
			System.Xml.XmlElement xmlE;

			ID = int.Parse (el.GetAttribute ("id"));
			Name = el.GetAttribute ("name");

			if ((xmlE = el["Master"]) != null) {
				master =  int.Parse (xmlE.TryGetAttribute("value","100"));
				foreach(string id in EventManager.LoadBindings(xmlE))
					BindMasterEvent(id);
			}
			else master = int.Parse (el.TryGetAttribute("master","100"));

			if ((xmlE = el["EffetSuivant"])!= null)
				foreach(string id in EventManager.LoadBindings(xmlE))
					BindEffetSuivantEvent(id);

			if ((xmlE = el["EffetPrecedent"])!= null)
				foreach(string id in EventManager.LoadBindings(xmlE))
					BindEffetPrecedentEvent(id);


			foreach (var xc in el.GetElementsByTagName("CircuitSeq")) {
				System.Xml.XmlElement xcir = xc as System.Xml.XmlElement;
				Circuit c = conduite.GetCircuitByID (int.Parse (xcir.GetAttribute ("id")));
				circuitsSeq.Add (c);
				AjouteCircuit (c);
				foreach(string id in EventManager.LoadBindings (xcir)) 
					BindCircuitEvent(c,id);
			}

			foreach (var xe in el.GetElementsByTagName("Effet")) 
				effets.Add(Effet.Load(conduite,xe as System.Xml.XmlElement)); 
		}

		static System.Text.RegularExpressions.Regex regexCommandExec = new System.Text.RegularExpressions.Regex(
			@"(?<effet>\d+)(t(?<transition>\d+))?",
			System.Text.RegularExpressions.RegexOptions.Compiled);

		static System.Text.RegularExpressions.Regex regexCommandProcess = new System.Text.RegularExpressions.Regex(
			@"(?<effet>\d+)(?<params>(t\d+)?)?",
			System.Text.RegularExpressions.RegexOptions.Compiled);

		public override void Command (string command)
		{
			lock (this) {
				var cmd = regexCommandExec.Match(command);

				if (cmd.Success) {
					if (cmd.Groups ["effet"].Success) {
						int effet = int.Parse (cmd.Groups ["effet"].Value) - 1;
						if (effet < effets.Count)
							ChangeEffetCourrant (effet); 
					}

					if (cmd.Groups ["transition"].Success) {
						int transition = int.Parse (cmd.Groups ["transition"].Value);
						tempsTransition = TimeSpan.FromMilliseconds( transition *100);
					}
				}
			}
		}

		void CommandAdd (int index)
		{
			lock (Conduite.Courante.SequenceurMaitre) {
				string[] commands = Conduite.Courante.SequenceurMaitre.GetCommands (this);

				for (int i = 0; i < commands.Length; i++) {
					var cmd = regexCommandProcess.Match(commands[i]);
					if(cmd.Success){
						int ef = int.Parse(cmd.Groups["effet"].Value);
						if (ef-1>index) { 
							ef++;
							commands[i] = ef.ToString() + cmd.Groups["params"].Value;
						}
					}
				}
				Conduite.Courante.SequenceurMaitre.SetCommands(this,commands);
			}
		}

		void CommandRemove (int index)
		{
			lock (Conduite.Courante.SequenceurMaitre) {
				string[] commands = Conduite.Courante.SequenceurMaitre.GetCommands (this);

				for (int i = 0; i < commands.Length; i++) {
					var cmd = regexCommandProcess.Match(commands[i]);
					if(cmd.Success){
						int ef = int.Parse(cmd.Groups["effet"].Value);
						if (ef-1 == index) 
							commands[i] = string.Empty;
						else if (ef-1>index) { 
							ef--;
							commands[i] = ef.ToString() + cmd.Groups["params"].Value;
						}
					}
				}
				Conduite.Courante.SequenceurMaitre.SetCommands(this,commands);
			}
		}

		void CommandSwap (int index)
		{
			lock (Conduite.Courante.SequenceurMaitre) {
				string[] commands = Conduite.Courante.SequenceurMaitre.GetCommands (this);

				// numeros a swapper
				int a = index+1;
				int b = index+2;

				for (int i = 0; i < commands.Length; i++) {
					var cmd = regexCommandProcess.Match(commands[i]);
					if(cmd.Success){
						int ef = int.Parse(cmd.Groups["effet"].Value);
						if (ef == a) 
							commands[i] = b.ToString() + cmd.Groups["params"].Value;
						if (ef == b) 
							commands[i] = a.ToString() + cmd.Groups["params"].Value;
					}
				}
				Conduite.Courante.SequenceurMaitre.SetCommands(this,commands);
			}
		}

	}
}
