using System;
using System.Collections.Generic;
using System.Xml;
using System.Collections.ObjectModel;

namespace DMX2
{
	public class SequenceurMacro : Sequenceur
	{
		public class Ligne {
			public Ligne(){}
			string nom = string.Empty;
			TimeSpan top = TimeSpan.MinValue;
			string circuits = string.Empty;
			int valeur;
			TimeSpan temps = TimeSpan.Zero;

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

			public TimeSpan Top {
				get {
					return top;
				}
				set {
					top = value;
				}
			}

			public string Circuits {
				get {
					return circuits;
				}
				set {
					circuits = value;
				}
			}

			public int Valeur {
				get {
					return valeur;
				}
				set {
					valeur = value;
				}
			}

			public TimeSpan Temps {
				get {
					return temps;
				}
				set {
					temps = value;
				}
			}


			public void Save (XmlElement parent)
			{
				XmlElement el = parent.OwnerDocument.CreateElement ("Ligne");
				parent.AppendChild (el);

				el.SetAttribute ("nom", nom);
				el.SetAttribute ("top", top.ToString ());
				el.SetAttribute ("circuits", circuits);
				el.SetAttribute ("valeur", valeur.ToString ());
				el.SetAttribute ("temps", temps.ToString ());


			}			

			public static Ligne Load (Conduite c, XmlElement el)
			{
				Ligne l = new Ligne();
				l.nom = el.GetAttribute ("nom");
				l.top = TimeSpan.Parse(el.GetAttribute("top"));
				l.circuits = el.GetAttribute ("circuits");
				l.valeur = int.Parse(el.GetAttribute("valeur"));
				l.temps = TimeSpan.Parse(el.GetAttribute("temps"));

				return l;
			}
		}

		public class EffetMacro
		{
			public EffetMacro(  TimeSpan tempsTransition, int valeurInitiale, int valeurFinale) {
				TempsPasse = TimeSpan.Zero;
				TempsTransition = tempsTransition;
				ValeurInitiale= valeurInitiale;
				ValeurFinale = valeurFinale;
			}
			public TimeSpan TempsPasse { get; set; }
			public TimeSpan TempsTransition { get; set; }
			public int ValeurInitiale { get; set; }
			public int ValeurFinale { get; set; }
			public bool Incremente (TimeSpan delta)
			{
				TempsPasse += delta;
				if (TempsPasse > TempsTransition) {
					TempsPasse = TempsTransition;
					return true;
				}
				return false;
			}
			public int ValeurCourante() 
			{
				double progression = TempsPasse.TotalMilliseconds/TempsTransition.TotalMilliseconds;
				return (int)( progression *  (ValeurFinale - ValeurInitiale) + ValeurInitiale );
			}
		}

		List<Ligne> lignes = new List<Ligne>();

		Ligne aSuivre = null;
		Ligne enCours = null;
		Ligne ligneMaitre = null;

		TimeSpan timeStamp = TimeSpan.Zero;
		TimeSpan topSuivant = TimeSpan.Zero;
		bool topPresent = false;

		List<Circuit> circuitsSeq = new List<Circuit> ();


		Dictionary<Circuit,int> valeurscourantes = new Dictionary<Circuit, int> ();
		Dictionary<Circuit,EffetMacro> effetsEnCours = new Dictionary<Circuit, EffetMacro>();

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


		SeqMacroUI ui = null;
		bool change = false;
		int master = 100;

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

		bool paused=false;

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

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

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

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


		public int IndexLigneEnCours
		{
			get {
				if (enCours == null) return -1;
				return lignes.IndexOf(enCours);
			}
		}


		public int IndexLigneaSuivre 
		{
			get {
				if (aSuivre == null)
					return -1;
				return lignes.IndexOf (aSuivre);
			}
			set {
				aSuivre = lignes[value];
			}
		}

		public int AjoutLigne (int pos)
		{
			lock (this) {
				lignes.Insert (pos, new Ligne ());
				CommandAdd(pos);
				return pos;
			}
		}

		public void RetireLigne (int pos)
		{
			lock (this) {
				if (lignes [pos] == enCours) {
					enCours = null;
					if (pos + 1 < lignes.Count)
						aSuivre = lignes [pos + 1];
				} 
				if (lignes [pos] == aSuivre)
					aSuivre = null;
				lignes.RemoveAt (pos);
				CommandRemove(pos);
			}
		}

		public TimeSpan TimeStamp {
			get {
				return timeStamp;
			}
		}


		public ReadOnlyCollection<Ligne> Lignes {
			get {
				return lignes.AsReadOnly();
			}
		}


		public ReadOnlyCollection<Circuit> Circuits {
			get {
				return circuitsSeq.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;
		}

		void RetireCircuit (Circuit c)
		{
			circuitsSeq.Remove (c);
			valeurscourantes.Remove (c);
		}

		/*public override void MajCircuitsSupprimes ()
		{
			lock (this) {
				foreach (var c in circuitsSeq.ToArray()) {
					if (!Conduite.Courante.Circuits.Contains (c))
						RetireCircuit (c);
				}
			}
		}*/

		public override int ValeurCircuit (Circuit c)
		{
			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 override void Tick (TimeSpan time)
		{
			if (paused)
				return;
			timeStamp += time;


			lock (this) {
				List<Circuit> circuits = new List<Circuit> (effetsEnCours.Keys);

				foreach (Circuit c in circuits) {
					if (effetsEnCours [c].Incremente (time)) {
						valeurscourantes [c] = effetsEnCours [c].ValeurFinale;
						effetsEnCours.Remove (c);
					} else {
						valeurscourantes [c] = effetsEnCours [c].ValeurCourante ();
					}
				}
			}

			if (topPresent) {
				if (timeStamp > topSuivant){
					LigneSuivante();
				}
			}
		}

		public void LigneSuivante ()
		{
			lock (this) {
				if(lignes.Count==0) return;
				int index;
				change = true; topPresent = false;
				if(aSuivre==null) // selection souris
				{
				 	index = IndexLigneEnCours +1; // Premier effet si aucun précédement
					if(index>= lignes.Count) index = 0; // Boucle si arrivé à la fin

					enCours = lignes[index];

					// Gestion de la Reprise
					if(enCours.Circuits.Equals("R") && ligneMaitre != null)
						enCours = ligneMaitre;

					if(enCours.Nom.Length!=0)
					{
						ligneMaitre = enCours;
						timeStamp = TimeSpan.Zero;
					}
				}
				else
				{
					enCours = aSuivre;
					ligneMaitre = enCours;
					timeStamp = TimeSpan.Zero;

				}
				index = IndexLigneEnCours+1;
				if(index<lignes.Count)
				{
					// Si top présent - non negatif
					if(lignes[index].Top>= TimeSpan.Zero)
					{
						topPresent = true;
						topSuivant= lignes[index].Top;
					}
				}
				aSuivre = null;
				LanceEffetsMacro(false,TimeSpan.Zero);
				if(ui!=null)
					ui.EffetChange();
			}
		}

		void LanceEffetsMacro (bool ecrase, TimeSpan tempsMatrice)
		{
			lock (this) {
				if (ecrase)
					LanceEffetsMacro (enCours.Circuits, enCours.Valeur, tempsMatrice);
				else
					LanceEffetsMacro (enCours.Circuits, enCours.Valeur, enCours.Temps);
			}

		}

		void LanceEffetsMacro (string circuits, int valeurCible, TimeSpan temps)
		{
			if(circuits==null)return;
			string[] blocs = circuits.Split(',');
			foreach (string bloc in blocs) {
				string[] circ = bloc.Split('-');
				int start, end;
				if(! int.TryParse(circ[0], out start)) 
					continue;
				if(circ.Length > 1) // on a affaire a un bloc (de plusieurs)
				{
					if(! int.TryParse(circ[1], out end))
						continue;
				}
				else 
					end= start;
				for( int i = start; i<=end; i++)
				{
					Circuit c = Conduite.Courante.GetCircuitByID(i);
					if(circuitsSeq.Contains(c))
					{
						effetsEnCours[c] = new EffetMacro(temps,valeurscourantes[c],valeurCible);
					}
				}
			}
		}


		public bool LigneChange ()
		{
			if (change) {
				change = false;
				return true;
			}
			return false;
		}

		public override void Save (System.Xml.XmlElement parent)
		{
			System.Xml.XmlElement el = parent.OwnerDocument.CreateElement ("SequenceurMacro");
			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());
			}

			foreach (Ligne li in lignes) {
				li.Save(el);
			}

		}


		public override SequenceurUI GetUI ()
		{

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

			return ui;
		}

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

		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 static new SequenceurMacro Load (Conduite conduite, System.Xml.XmlElement el)
		{
			SequenceurMacro seq = new SequenceurMacro();
			seq.LoadSeq(conduite,el);
			return seq;
		}

		private void LoadSeq (Conduite conduite, System.Xml.XmlElement el)
		{
			ID = int.Parse (el.GetAttribute ("id"));
			Name = el.GetAttribute ("name");

			XmlElement xmlE;

			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 (var xe in el.GetElementsByTagName("Ligne")) 
				lignes.Add(Ligne.Load(conduite,xe as System.Xml.XmlElement)); 
		}

		static System.Text.RegularExpressions.Regex regexCommandeDirecte = new System.Text.RegularExpressions.Regex(
			@"(?<circuits>[\d,-]+) (?<valeur>\d+) (?<temps>\d+)",
			System.Text.RegularExpressions.RegexOptions.Compiled);


		public void CommandDirecte (string text)
		{
			var cmd = regexCommandeDirecte.Match (text);
			if (cmd.Success) {
				string circuits = cmd.Groups["circuits"].Value;

				int valeur = int.Parse(cmd.Groups["valeur"].Value);
				TimeSpan temps = TimeSpan.FromMilliseconds(100* int.Parse(cmd.Groups["temps"].Value));

				LanceEffetsMacro(circuits,valeur,temps);
			}
		}

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

		static System.Text.RegularExpressions.Regex regexCommand2 = 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 = regexCommand1.Match(command);

				if (cmd.Success) {
					if (cmd.Groups ["effet"].Success) {
						int effet = int.Parse (cmd.Groups ["effet"].Value) - 1;
						int transition=-1;

						if(effet>=lignes.Count) return;
						enCours = lignes[effet];
						ligneMaitre = enCours;
						timeStamp = TimeSpan.Zero;
					
						topPresent = false;
						int index = IndexLigneEnCours+1;
						if(index<lignes.Count)
						{
							// Si top présent - non negatif
							if(lignes[index].Top>= TimeSpan.Zero)
							{
								topPresent = true;
								topSuivant= lignes[index].Top;
							}
						}
						aSuivre = null;

						if (cmd.Groups ["transition"].Success) {
							transition = int.Parse (cmd.Groups ["transition"].Value);
							LanceEffetsMacro(true, TimeSpan.FromMilliseconds(transition *100));
						}
						else
							LanceEffetsMacro(false,TimeSpan.Zero);

						if(ui!=null) ui.EffetChange();

					}

				}
			}
		}

		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 = regexCommand2.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 = regexCommand2.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 = regexCommand2.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);
			}
		}

	}
}
