using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Windows.Media;
using System.Xml;
using Machinations.Annotations;
using Machinations.FromFlash.graph;
using Machinations.Log;

namespace Machinations.FromFlash.machinations.model
{
    /// <summary>
    /// Subclass of Graph, holds Machinations elements and controls the simulation
    /// </summary>
    public class MachinationsGraph : Graph, INotifyPropertyChanged
    {
        private bool m_running;
        private int m_width;
        private int m_height;
        public double FireInterval;
        private double m_fireIntervalCounter;
        private readonly ObservableCollection<Pool> m_turnPools;
        public double ResourceSpeed;
        public int OriginalActionsPerTurn;
        public int ActionsPerTurn;
        public int ActionsThisTurn;
        public string Name;
        public string Author;
        public string Dice;
        public string Skill;
        public string Strategy;
        public string Multiplayer;
		public TimeMode TimeMode;
		public DistributionMode DistributionMode;
		public bool ColorCoding;
        private ILog m_log;
        private List<MachinationsNode> m_clickableNodesBase;            //updatne se pri startu simulace, obsahuje klikatelne nody
		
		private List<string> m_dicePostFix;
		private List<string> m_skillPostFix;
		private List<string> m_strategyPostFix;
		private List<string> m_multiplayerPostFix;
		
		private double m_dicePercentage;
		private double m_skillPercentage;
		private double m_strategyPercentage;
		private double m_multiplayerPercentage;

        private bool m_ended;
		public string EndCondition;
		public bool DoEvents;
        private int m_steps;
		private bool m_up;
		private bool m_fireUp ;
        private bool m_endTurn;
        private bool m_inhibitionChange;
        private bool m_canSkipTurn;
        private bool m_canDoEvents;
        private readonly HashSet<GraphElement> m_waitingForEvents;
        //private bool m_pullAllCountsInhibited;

        public event Action SizeChanged;
        public event Action StepChanged;
        public event Action TurnFinished;
        public event Action CardsChange;
        public event Action EndChanged;
        public event Action GraphLoaded;


        public MachinationsGraph()
        {
            m_turnPools = new ObservableCollection<Pool>();
            m_running = false;
            m_canDoEvents = true;
            m_waitingForEvents = new HashSet<GraphElement>();
        }


        public bool InhibitionChange
        {
            get { return m_inhibitionChange; }
            set { m_inhibitionChange = value; }
        }

        public ObservableCollection<Pool> TurnPools
        {
            get { return m_turnPools; }
        }

        public bool Running
        {
            get { return m_running; }
            set {
                m_running = value;
                if (m_running) Prepare();
                else Stop();
            }
        }

        public int Width
        {
            get { return m_width; }
            set
            {
                if (m_width != value)
                {
                    m_width = value;
                    OnPropertyChanged(nameof(Width));
                }
            }
        }

        public int Height
        {
            get { return m_height; }
            set
            {
                if (m_height != value)
                {
                    m_height = value;
                    OnPropertyChanged(nameof(Height));
                }
            }
        }

        public ILog Log
        {
            get { return m_log; }
            set { m_log = value; }
        }

        public int Step
        {
            get { return m_steps; }
            set {
                if (m_steps != value)
                {
                    m_steps = value;
                    if (Log != null)
                        Log.Step = value;
                    StepChanged?.Invoke();
                }
            }
        }

        public bool Ended
        {
            get
            {
                return m_ended;
            }
            set
            {
                if (m_ended != value)
                {
                    m_ended = value;
                    EndChanged?.Invoke();
                }
            }
        }

        public List<MachinationsNode> ClickableNodes
        {
            get
            {
                return m_clickableNodesBase; 
            }
        }

        public bool CanSkipTurn
        {
            get { return m_canSkipTurn; }
            set { m_canSkipTurn = value; }
        }

        public bool CanDoEvents
        {
            get
            {
                return m_canDoEvents;
            }
            set
            {
                if (m_canDoEvents != value)
                {
                    m_canDoEvents = value;
                    if (m_canDoEvents)
                    {
                        foreach (var waitingForEvent in m_waitingForEvents)
                        {
                            waitingForEvent.OnChanged("Events enabled");
                        }
                        m_waitingForEvents.Clear();
                    }
                }
            }
        }

        public HashSet<GraphElement> WaitingForEvents
        {
            get { return m_waitingForEvents; }
        }


        public override void Clear() 
		{
			base.Clear();
			FireInterval = 1;
			m_fireIntervalCounter = 0;
			ResourceSpeed = 100;
			ActionsPerTurn = 1;
			Name = "";
			Author = "";
			Dice = "D6";
			Skill = "";
			Strategy = "";
			Multiplayer = "";
			m_width = 800;
			m_height = 800;
            SizeChanged?.Invoke();
			Ended = false;
			ColorCoding = true;
		    CanSkipTurn = true;
            //PullAllCountsInhibited = false;
			TimeMode = TimeMode.TurnBased;
			//DistributionMode = DistributionModeInstantaneous;
			DistributionMode = DistributionMode.Instantaneous;
            m_turnPools?.Clear();
		}

        public override XmlDocument GenerateXml() 
		{
			XmlDocument document = new XmlDocument();//= base.GenerateXml();
            XmlElement element = document.CreateElement("graph");
			element.SetAttribute("name", Name);
			element.SetAttribute("author", Author);
			element.SetAttribute("interval", FireInterval+"");
			element.SetAttribute("timeMode", TimeMode + "");
            element.SetAttribute("distributionMode", DistributionMode + "");
			element.SetAttribute("speed", ResourceSpeed+"");
			element.SetAttribute("actions", ActionsPerTurn+"");
			element.SetAttribute("dice", Dice);
			element.SetAttribute("skill", Skill);
			element.SetAttribute("strategy", Strategy);
			element.SetAttribute("multiplayer", Multiplayer);
			element.SetAttribute("width", m_width+"");
			element.SetAttribute("height", m_height+"");
			element.SetAttribute("colorCoding", ColorCoding? "1" : "0");
		    element.SetAttribute("canSkipTurn", CanSkipTurn.ToString());
            document.AppendChild(element);
            //UserComponentsSave(document);
            FillXmlElement(element);
            //UserComponents.ForEach(uc => uc.UnZip());
            return document;
		}

        public void LoadGraphSettings(XmlElement graphElement)
        {
            Name = graphElement.GetAttribute("name");
            Author = graphElement.GetAttribute("author");
            FireInterval = double.Parse(graphElement.GetAttribute("interval"), CultureInfo.InvariantCulture);
            TimeMode = TimeMode.TurnBased;
            DistributionMode = DistributionMode.Instantaneous;

            ActionsPerTurn = int.Parse(graphElement.GetAttribute("actions"));
            Dice = graphElement.GetAttribute("dice");
            if (Dice == "")
                Dice = "D6";
            Skill = graphElement.GetAttribute("skill");
            Strategy = graphElement.GetAttribute("strategy");
            Multiplayer = graphElement.GetAttribute("multiplayer");
            Width = int.Parse(graphElement.GetAttribute("width"));
            Height = int.Parse(graphElement.GetAttribute("height"));
            if (graphElement.HasAttribute("colorCoding"))
            {
                int i = int.Parse(graphElement.GetAttribute("colorCoding"));
                ColorCoding = i == 1;
            }
            else ColorCoding = true;
            CanSkipTurn = !graphElement.HasAttribute("canSkipTurn") || bool.Parse(graphElement.GetAttribute("canSkipTurn"));
            GraphLoaded?.Invoke();
            SizeChanged?.Invoke();
        }

        public void End(string condition)
        {
            Ended = true;
			EndCondition = condition;
            //UpdateCharts();
		}
		
		public void Prepare()
        {
            UserComponents.ForEach(uc => uc.UnZip());
            Step = 0;
			ActionsThisTurn = 0;
			OriginalActionsPerTurn = ActionsPerTurn;
			Ended = false;
			if (Dice.IndexOf('%') == Dice.Length - 1)
            {
				m_dicePercentage = double.Parse(Dice.Substring(0, Dice.Length - 1), CultureInfo.InvariantCulture);
				m_dicePostFix = null;
			} else {
				m_dicePostFix = MachinationsExpression.ToPostFix(Dice);
			}
			if (Skill.IndexOf('%') == Skill.Length - 1)
            {
                if (Skill.Length > 1)
    				m_skillPercentage = double.Parse(Skill.Substring(0, Skill.Length - 1), CultureInfo.InvariantCulture);
				m_skillPostFix = null;
			} else {
				m_skillPostFix = MachinationsExpression.ToPostFix(Skill);
			}
			
			if (Strategy.IndexOf('%') == Strategy.Length - 1) {
                if (Strategy.Length > 1)
                    m_strategyPercentage = double.Parse(Strategy.Substring(0, Strategy.Length - 1), CultureInfo.InvariantCulture);
				m_strategyPostFix = null;
			} else {
				m_strategyPostFix = MachinationsExpression.ToPostFix(Strategy);
			}
			
			if (Multiplayer.IndexOf('%') == Multiplayer.Length - 1) {
			    if (Multiplayer.Length > 0)
				    m_multiplayerPercentage = double.Parse(Multiplayer.Substring(0, Multiplayer.Length - 1), CultureInfo.InvariantCulture);
				m_multiplayerPostFix = null;
			} else {
				m_multiplayerPostFix = MachinationsExpression.ToPostFix(Multiplayer);
			}

		    m_inhibitionChange = true;
            Elements.ForEach(element =>
            {
                (element as MachinationsConnection)?.Prepare(DoEvents);
                //v opacnem pripade to pada u registru, pri druhem behu chce tahat surovinu z nepripravene connectiony
                //(element as MachinationsNode)?.Prepare(DoEvents);
            });
            
            Elements.ForEach(element =>
            {
                (element as MachinationsNode)?.Prepare(DoEvents);
            });

            //prepare connections again in reverse order to make sure nothing is inhibited // dirty dirty dirty but efficient
            for (int i = Elements.Count - 1; i >= 0; i--) {
                (Elements[i] as MachinationsConnection)?.Prepare(DoEvents);
            }
			m_fireIntervalCounter = FireInterval;

            Elements.ForEach(element =>
            {
                (element as Gate)?.CheckDynamicProbabilities();
                element.TurnPoolsApply();
            });
		    m_inhibitionChange = false;
			
			m_up = false;
			m_fireUp = false;

            //fire onStart nodes
            Elements.ForEach(element =>
            {
                MachinationsNode node = element as MachinationsNode;
                if (node != null && node.ActivationMode == ActivationMode.OnStart)
                {
                    node.Fire();
                }
            });
            m_clickableNodesBase =
                Elements.OfType<MachinationsNode>().Where(n => n.ActivationMode == ActivationMode.Interactive).ToList();
            FinishTurn();
        }
		
		private void Stop()
		{
            Elements.ForEach(element =>
            {
                (element as MachinationsNode)?.Stop();
                (element as MachinationsConnection)?.Stop();
            });
		    ActionsPerTurn = OriginalActionsPerTurn;
            m_inhibitionChange = true;
            UserComponents.Reverse();
            UserComponents.ForEach(uc => uc.Zip());
		    UserComponents.Reverse();
            foreach (GraphElement t in Elements)
            {
                t.Inhibited = false;
                t.InhibitedChanged?.Invoke();
            }
            m_inhibitionChange = false;
        }
		
		public void Update(double time, bool DoEvents)
        {
			if (Ended) {
                Elements.ForEach(element =>
                {
                    (element as EndCondition)?.Update(time);
                });
				return;
			}
		    bool firing = false;
			
			switch (TimeMode) {
				case TimeMode.Asynchronous:
				case TimeMode.Synchronous:
					//runningTime += time;
					m_fireIntervalCounter += time;
					firing = (m_fireIntervalCounter > FireInterval);
					if (firing) m_fireIntervalCounter -= FireInterval;
					break;
				case TimeMode.TurnBased:
					if (ActionsThisTurn >= ActionsPerTurn)
                    {
						ActionsThisTurn -= ActionsPerTurn;
						firing = true;
					} else if (m_endTurn)
					{
					    m_endTurn = false;
					    ActionsThisTurn = 0;
					    firing = true;
					}else
			        {
						firing = false;
					}
					break;
			}
			
			
			//alternate going Up or down the list of element to avoid unfair advantages to elements that have been created earlier.
			m_up = !m_up;
			if (firing)
            {
				Step++;
				m_fireUp = !m_fireUp;
				if (m_fireUp)
                {
                    Elements.ForEach(element =>
                    {
                        MachinationsNode node = element as MachinationsNode;
                        if(node != null && !(node is Delay))node.AutoFire();
                    });
				}
                else
                {
					for (int i = Elements.Count-1; i >= 0; i--) {
						if (Elements[i] is MachinationsNode && !(Elements[i] is Delay)) ((MachinationsNode) Elements[i]).AutoFire();
					}
				}
				
				//fire delays aleter
				if (m_fireUp)
                {
					for (int i = 0; i < Elements.Count ; i++) {
						if (Elements[i] is Delay) (Elements[i] as MachinationsNode).AutoFire();
					}
				} else {
					for (int i = Elements.Count-1; i >= 0; i--) {
						if (Elements[i] is Delay) (Elements[i] as MachinationsNode).AutoFire();
					}
				}
                //Step++;
            }
            else 
                foreach (var pool in Elements.OfType<Pool>().Where(p => p.FireTwice && p.ResourceCount > 0))
                {
                    pool.AutoFire();
                    pool.FiredTwice = true;
                }
            
			
			//resolve overpull
			if (TimeMode == TimeMode.Synchronous) {
				for (int i = 0; i < Elements.Count ; i++) {
					if (Elements[i] is Pool) (Elements[i] as Pool).ResolveOverPull();
				}
			}
			
			//activate other time based events
			if (m_up) {
				for (int i = 0; i < Elements.Count; i++)
                {
					if (Elements[i] is MachinationsNode) (Elements[i] as MachinationsNode).Update(time);
					if (Elements[i] is MachinationsConnection) (Elements[i] as MachinationsConnection).Update(time);
				}
			} else {
				for (int i = Elements.Count - 1; i >= 0; i--)
                {				
					if (Elements[i] is MachinationsNode) (Elements[i] as MachinationsNode).Update(time);
					if (Elements[i] is MachinationsConnection) (Elements[i] as MachinationsConnection).Update(time);
				}
			}
            m_inhibitionChange = true;
            for (int i = 0; i < Elements.Count ; i++)
		    {
		        Elements[i].InhibitedChangeApply();
                Elements[i].TurnPoolsApply();
                //(Elements[i] as MachinationsNode)?.CheckInhibition();
		    }
            m_inhibitionChange = false;
        }
		
		public double GetDiceValue()
        {
			if (m_dicePercentage > 0) {
				int r = Convert.ToInt32(m_dicePercentage / 100);
				if (RandomUtils.NextDouble() * 100 < (m_dicePercentage % 100)) r++;
				return r;
			}
			if (m_dicePostFix != null) return Math.Max(0, MachinationsExpression.EvaluatePostFix(m_dicePostFix));
			else return 0;
		}
		
		public double GetSkillValue()
        {
			if (m_skillPercentage > 0) {
				int r = Convert.ToInt32(m_skillPercentage / 100);
				if (RandomUtils.NextDouble() * 100 < (m_skillPercentage % 100)) r++;
				return r;
			}
			if (m_skillPostFix != null) return Math.Max(0, MachinationsExpression.EvaluatePostFix(m_skillPostFix));
			else return 0;
		}
		
		public double GetStrategyValue()
        {
			if (m_strategyPercentage > 0) {
				int r = Convert.ToInt32(m_strategyPercentage / 100);
				if (RandomUtils.NextDouble() * 100 < (m_strategyPercentage % 100)) r++;
				return r;
			}
			if (m_strategyPostFix != null) return Math.Max(0, MachinationsExpression.EvaluatePostFix(m_strategyPostFix));
			else return 0;
		}

		public double GetMultiplayerValue()
        {
			if (m_multiplayerPercentage > 0) {
				int r = Convert.ToInt32(m_multiplayerPercentage / 100);
				if (RandomUtils.NextDouble() * 100 < (m_multiplayerPercentage % 100)) r++;
				return r;
			}
			if (m_multiplayerPostFix != null) return Math.Max(0, MachinationsExpression.EvaluatePostFix(m_multiplayerPostFix));
			else return 0;
		}
		
		public List<MachinationsNode> FindAllNodesByCaptionAndColor(string caption, Color color)
        {
			List<MachinationsNode> r = new List<MachinationsNode>();
			if (caption == "") return r;
		    r.AddRange(Elements.Select(element => element as MachinationsNode).Where(node => node != null && node.Caption == caption && node.Color == color));
            return r;
		}	
		
		public MachinationsNode FindNodeByCaptionAndColor(string caption, Color color)
        {
			if (caption == "") return null;
			for (int i = 0; i < Elements.Count; i++) {
				if (Elements[i] is MachinationsNode) {
					if (((Elements[i] as MachinationsNode).Caption).Trim() == caption && (Elements[i] as MachinationsNode).Color == color) return (Elements[i] as MachinationsNode);
				}
			}
			return null;
		}	
		
		public List<MachinationsNode> FindAllNodesByCaption(string caption)
        {
			List<MachinationsNode> list = new List<MachinationsNode>();
		    if (caption == "") return list;
			for (int i = 0; i < Elements.Count; i++) {
				if (Elements[i] is MachinationsNode) {
					if ((Elements[i] as MachinationsNode).Caption.Trim() == caption) {
						list.Add(Elements[i] as MachinationsNode);
					}
				}
			}
			return list;
		}
		
		public MachinationsNode FindNodeByCaption(string caption)
        {
			if (caption == "") return null;
			for (int i = 0; i < Elements.Count; i++) {
				if (Elements[i] is MachinationsNode) {
					if ((Elements[i] as MachinationsNode).Caption.Trim() == caption) return (Elements[i] as MachinationsNode);
				}
			}
			return null;
		}
		
		public void PushToTop(GraphElement element)
		{
		    Elements.Remove(element);
            Elements.Add(element);
		}

        public void MoreTurns(int count)
        {
            for (int i = 0; i < count; i++)
            {
                OneTurnWithoutUserActions();
            }
        }

        public void OneTurn()
        {
            Update(1, true);
            FinishTurn();
        }

        public void OneTurnWithoutUserActions()
        {
            m_endTurn = true;
            OneTurn();
        }

        public override void RemoveElement(GraphElement element)
        {
            base.RemoveElement(element);
            var pool = element as Pool;
            if (pool != null && pool.IsTurnPool)
                m_turnPools.Remove(pool);
        }

        public void FinishTurn()
        {
            bool unresolvedConnections =
                Elements.OfType<ResourceConnection>().Any(c => c.Resources.Count > 0);
            List<Pool> sendImidPools = Elements.OfType<Pool>().Where(p => p.FireTwice).ToList();
            int counter = 0;
            while (unresolvedConnections||sendImidPools.Any(s => s.ResourceCount > 0 && ! s.FiredTwice))
            {
                counter++;
                if (counter > 10000)
                {
                    throw new NotImplementedException("Cycle");
                }
                Update(0, true);
                unresolvedConnections = Elements.OfType<ResourceConnection>().Any(c => c.Resources.Count > 0);
            }
            sendImidPools.ForEach(s => s.FiredTwice = false);
            m_inhibitionChange = true;
            CardsChange?.Invoke();
            m_inhibitionChange = false;
            TurnFinished?.Invoke();
        }

        public event PropertyChangedEventHandler PropertyChanged;

        [NotifyPropertyChangedInvocator]
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}
