using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Media;
using System.Xml;
using Machinations.Description;
using Machinations.FromFlash.graph;

namespace Machinations.FromFlash.machinations.model
{
    /// <summary>
    /// most common node in machinations framework, can store resources. can also create them,when told so by state connection 
    /// </summary>
    public class Pool : Source
    {
        private int m_startingResources;
        public int Capacity;
        private int m_startingCapacity;
        private List<Resource> m_resources;
		public bool OverPulled;
		public int Pulls;
		private int m_shotage;
        private TurnPoolType m_turnPoolType;
        private bool m_fireTwice;
        private bool m_firedTwice;
        //public const int TokenLimit = 25;
		//public int DisplayCapacity = TokenLimit;


        public Pool()
        {
            m_resources = new List<Resource>();
            m_startingResources = 0;
            Capacity = -1;
            m_activationMode = ActivationMode.Passive;
        }

        public int StartingResources
        {
            get { return m_startingResources; }
            set {
                m_startingResources = value;
                Resources.Clear();
                for (int i = 0; i < value; i++) Resources.Add(new Resource(ResourceColor, 0));
            }
        }

        public List<Resource> Resources
        {
            get { return m_resources; }
            set { m_resources = value; }
        }

        public int ResourceCount
        {
            get
            {
                int c = Resources.Count(t => t.Position == 0);
                return c - m_shotage;
            }
		}



        public override NodeClass NodeClass => NodeClass.Pool;

        public new Color ResourceColor
        {
            get { return m_resourceColor; }
            set {
                m_resourceColor = value;
                Resources.Clear();
                for (int i = 0; i < m_startingResources; i++) Resources.Add(new Resource(value, 0));
            }
        }

        public int Shotage
        {
            get { return m_shotage; }
            set { m_shotage = value; }
        }

        public bool IsTurnPool
        {
            get { return m_turnPoolType != TurnPoolType.None; }
            set
            {
                if (value && m_turnPoolType == TurnPoolType.None)
                {
                    m_turnPoolType = TurnPoolType.Normal;
                    //OnPropertyChanged(nameof(IsTurnPool));
                    //OnPropertyChanged(nameof(TurnPoolIsNormal));
                    ((MachinationsGraph) m_graph).TurnPools.Add(this);
                    UpdateDescription();
                }
                else if (!value && m_turnPoolType != TurnPoolType.None)
                {
                    m_turnPoolType = TurnPoolType.None;
                    //OnPropertyChanged(nameof(IsTurnPool));
                    ((MachinationsGraph)m_graph).TurnPools.Remove(this);
                    foreach (var graphElement in m_graph.Elements)
                    {
                        if (graphElement.TurnPools.Contains(this))
                        {
                            graphElement.TurnPools.Remove(this);
                            graphElement.UpdateDescription();
                        }
                    }
                   UpdateDescription();
                }
            }
        }

        public int TurnPoolIsNormal
        {
            get { return ( m_turnPoolType == TurnPoolType.Normal)? 0 : 1 ; }
            set
            {
                var tptValue = (value == 0) ? TurnPoolType.Normal : TurnPoolType.Inverse;
                if (m_turnPoolType != tptValue)
                {
                    m_turnPoolType = tptValue;
                    UpdateDescription();
                }
            }
        }

        public bool FireTwice
        {
            get { return m_fireTwice && (m_pullMode == PullMode.PushAll || m_pullMode == PullMode.PushAny); }
            set
            {
                if (m_fireTwice != value)
                {
                    m_fireTwice = value;
                    UpdateDescription();
                }
                
            }
        }

        public bool FiredTwice
        {
            get { return m_firedTwice; }
            set { m_firedTwice = value; }
        }

        public override void UpdateDescription()
        {
            DescriptionChange(NodesDescription.IsPoolTurnOrder, NodesDescription.TurnPoolDescription[m_turnPoolType]);
            DescriptionChange(NodesDescription.CanFireTwice, FireTwice? NodesDescription.FireTwice : "");
            base.UpdateDescription();
        }


        public override XmlElement GenerateXml(XmlDocument document, XmlElement element = null)
		{
			element = base.GenerateXml(document, element);
			element.SetAttribute("startingResources", m_startingResources + "");
            element.SetAttribute("capacity", Capacity + "");
            //if (DisplayCapacity != TokenLimit) element.SetAttribute("DisplayCapacity", DisplayCapacity + "");
            element.SetAttribute("turnPool", m_turnPoolType.ToString());
		    element.SetAttribute("fireTwice", FireTwice.ToString());
            return element;
		}

        public override void ReadXml(XmlElement element) 
		{
			base.ReadXml(element);
			StartingResources = int.Parse(element.GetAttribute("startingResources"));
			if (element.HasAttribute("maxResources")) Capacity = int.Parse(element.GetAttribute("maxResources"));
			else Capacity = int.Parse(element.GetAttribute("capacity"));
            if (element.HasAttribute("turnPool"))
                m_turnPoolType = (TurnPoolType) Enum.Parse(typeof (TurnPoolType), element.GetAttribute("turnPool"));
            if (element.HasAttribute("sendImid"))
                FireTwice = bool.Parse(element.GetAttribute("sendImid"));
            if (element.HasAttribute("fireTwice"))
                FireTwice = bool.Parse(element.GetAttribute("fireTwice"));
            // if (element.HasAttribute("tokenLimit")) DisplayCapacity = int.Parse(element.GetAttribute("tokenLimit"));
            // if (element.HasAttribute("displayCapacity"))
            //     DisplayCapacity = int.Parse(element.GetAttribute("displayCapacity"));
        }

        public override void Prepare(bool doEvents)
		{
			base.Prepare(doEvents);
			
			//distributed = m_startingResources;
			m_shotage = 0;
			OverPulled = false;

            //setState();
            CheckInhibition();
			//recalculate();
			
			if (ResourceInputCount == 0) {
				if (PullMode == PullMode.PullAny) PullMode = PullMode.PushAny;
				if (PullMode == PullMode.PullAll) PullMode = PullMode.PushAll;
			}
			
			m_startingCapacity = Capacity;
		}

        public void Modify(int delta)
        {
			if (delta > 0) {
				for (int i = 0; i<delta; i++)
                    ReceiveResource(m_resourceColor, null);
            }
			if (delta< 0) {
				for (int i = 0; i < -delta; i++) {
					if (ResourceCount <= 0) {
						m_shotage++;

                        ChangeState(ColorsUtils.InitColor, -1);
					} else {
                        RemoveResource(ColorsUtils.TransparentColor, 0);
					}
				}
			}
			if (m_doEvents) OnChanged(GraphEvent.ElementChange);
		}

        //replace by modify

        /*public function recalculate():void
		{
			calculated = 0;
			var setting:Boolean = false;
			var l:int = inputs.length;
			for (var i:int = 0; i < l; i++) {
				if (inputs[i] is StateConnection && (inputs[i] as StateConnection).isSetter()) {
					calculated += (inputs[i] as StateConnection).state;
					setting = true;
				}
			}
			if (setting) {
				//Resources.splice(0, Resources.length);
				var r:int = distributed + Math.floor(calculated);
				if (r > Resources.length) {
					for (i = Resources.length; i < r; i++) Resources.push(new Resource(resourceColor, 0));
				}
				if (Math.max(0, r) < Resources.length) {
					Resources.splice(Math.max(0, r), Resources.length - Math.max(0, r));
				}
				
				//Resources.splice(0, Resources.length);
				if (doEvents) OnChanged(GraphEvent.ELEMENT_CHANGE, this));
				setState();
			}
		}*/

        public override void Stop() 
		{
			base.Stop();
			Capacity = m_startingCapacity;
			Resources.Clear();
			for (int i = 0; i < m_startingResources; i++) Resources.Add(new Resource(m_resourceColor, 0));
			m_shotage = 0;
			if (m_doEvents) OnChanged(GraphEvent.ElementChange);
		}

        public override void Update(double time) 
		{
			Pulls = 0;
			int removedCount = 0;
			for (int i = Resources.Count - 1 ; i >= 0; i--) {
				if (Resources[i].Position > 0) {
					//Resources[i].Position -= time;
					//if (Resources[i].Position <= 0) {
						Resources.RemoveAt(i);
						removedCount++;
						//setState();
					//}
				}
			}
			if (Firing > 0) {
				Firing -= time;
				if (Firing <= 0) {
					Firing = 0;
					if (m_doEvents) OnChanged(GraphEvent.ElementChange);
				}
			}				
			
			if (removedCount > 0 && m_doEvents) OnChanged(GraphEvent.ElementChange);
		}

        public override void Fire() 
		{
            SetFiring();
			switch (PullMode) {
				case PullMode.PushAny:
					for (int i = m_outputs.Count - 1; i >= 0; i--)
                    {
						if (m_outputs[i] is ResourceConnection) (m_outputs[i] as ResourceConnection).Pull();
					}
					break;
				case PullMode.PushAll:
					
					bool undo  = false;
                    for (int i = m_outputs.Count - 1; i >= 0; i--)
                    {
                        if (m_outputs[i] is ResourceConnection && !(m_outputs[i] as ResourceConnection).Pull())
                        {
							undo = true;
						}
					}
					if (undo) {
						for (int i = m_outputs.Count -1 ; i >= 0; i--) {
							if (m_outputs[i] is ResourceConnection) {
								(m_outputs[i] as ResourceConnection).UndoPull(false);
							}
						}
					}
					break;
                default:

                    Pull();
                    break;
            }
			if (ResourceInputCount == 0)
            {
                Satisfy();
			}
            LogSelf();
		}

        public override void Satisfy()
		{
			for (int i = 0; i < m_outputs.Count; i++)
            {
				if (m_outputs[i] is StateConnection) (m_outputs[i] as StateConnection).Fire();
			}
		}

        public override void ReceiveResource(Color color, ResourceConnection flow) 
		{
			if (Capacity >= 0 && Resources.Count - m_shotage >= Capacity ) return;
			if (m_shotage > 0) {
				m_shotage--;
			} else {
				Resources.Add(new Resource(color, 0));
			}
			//if (m_doEvents) OnChanged(GraphEvent.ElementChange);
            //recalculate();
            //setState();
            ChangeState(color, 1);
			if (CheckInputs()) Satisfy();
		}

        public void ReturnResource(Color color) 
		{
			if (CheckInputs()) Satisfy();
            //if (maxResources >= 0 && Resources.length >= maxResources) return;
            ChangeState(color, 1, true);
            //try remove one of the removed Resources
            for (int i = Resources.Count - 1; i >= 0; i--) {
				if ((color == ColorsUtils.TransparentColor || Resources[i].Color == color) && Resources[i].Position >= 0.01) {
					Resources[i].Position = 0;
					return;
				}
			}
			if (m_shotage > 0) {
				m_shotage--;
				//changeState(color, 1);
			} else {
				Resources.Add(new Resource(color, 0));
				//changeState(color, 1);
			}
			//if (doEvents) OnChanged(GraphEvent.ELEMENT_CHANGE, this));
			//recalculate();
			//setState();
		}

        public Color RemoveResource(Color color, double time)
        {
			for (int i = Resources.Count -1 ; i >= 0; i--)
            {
				if ((color == ColorsUtils.TransparentColor || Resources[i].Color == color) && Resources[i].Position == 0)
                {
					Color c = Resources[i].Color;
					if (time > 0) {
						Resources[i].Position = time + 0.01;
					} else {
						Resources[i].Position = 0.01;
						//Resources.splice(i, 1);
						//if (doEvents) OnChanged(GraphEvent.ELEMENT_CHANGE));
						//setState();
					}

                    ChangeState(c, -1);
					return c;
				}
			}
            //changeState(ColorsUtils.TransparentColor, -1);
            return ColorsUtils.TransparentColor;
		}

        public Color RemoveRandomResource(double time)
        {
			int c = 0;
			for (int i = Resources.Count - 1; i >= 0; i--) {
				if (Resources[i].Position == 0)
                {
					c++;
				}
			}
			int r = Convert.ToInt32(RandomUtils.NextDouble() * c);
			for (int i = Resources.Count - 1; i >= 0; i--) {
				if (Resources[i].Position == 0) {
					r--;
					if (r< 0) {
						if (time > 0) {
							Resources[i].Position = time+0.01;
						} else {
							Resources[i].Position = 0.01;
						}

                        ChangeState(Resources[i].Color, -1);
						return Resources[i].Color;
					}
				}
			}
			return ColorsUtils.TransparentColor;
		}


        public bool CanRemoveResource(Color color, int amount)
        {
			for (int i = Resources.Count - 1; i >= 0; i--) {
				if ((color == ColorsUtils.TransparentColor || Resources[i].Color == color) && Resources[i].Position == 0) {
					amount--;
					if (amount <= 0) return  true;
				}
			}
			return false;
		}

        public void ChangeState(Color resourceColor, int delta, bool returnResource = false)
        {
			for (int i = 0; i < m_outputs.Count; i++)
            {
                StateConnection stateConnection = m_outputs[i] as StateConnection;
				if (stateConnection != null)
                {
					if (stateConnection.Color == Color 
                        || resourceColor == ColorsUtils.TransparentColor 
                        ||stateConnection.Color == resourceColor
                        ||! (m_graph as MachinationsGraph).ColorCoding)
                    {
                        if(!stateConnection.ForwardOnly || !returnResource)
                            (m_outputs[i] as StateConnection).ChangeState(delta);
                    }
                }
                ResourceConnection resourceConnection = m_outputs[i] as ResourceConnection;
                resourceConnection?.CheckInhibition();
            }
			if (delta != 0)
            { 
                CheckInhibition();
			}
		}


        public int ResourceColorCount(Color c)
        {
			int s = 0;
			for (int i = 0; i < Resources.Count; i++) {
				if (Resources[i].Color == c) s++;
			}
			return s;
			
		}
		
		public void ResolveOverPull()
        {
			if (OverPulled) {
				OverPulled = false;
				
				if (Pulls > 1) {
					//if there was more than one pull, ignore the overpull
					for (int i = 0; i < Resources.Count; i++) {
						Resources[i].Position = 0;
						//distributed++;
					}
					for (int i = 0; i < m_outputs.Count; i++)
                    {
						if (m_outputs[i] is ResourceConnection) {
							(m_outputs[i] as ResourceConnection).UndoPull();
						}
					}
				}
			}
		}
		
		public void ModifyCapacity(int delta)
        {
			Capacity += delta;
			if (Capacity<Resources.Count) {
				int m = Math.Max(Capacity, 0);
				int r = Resources.Count - m;
				for (int i = m; i < m + r; i++)
				{
                    ChangeState(Resources[i].Color, -1);				
				}
				Resources.RemoveRange(m, r);
				if (m_doEvents) OnChanged(GraphEvent.ElementChange);
			}
		}
		
		public override String ToMMString()
		{
		    StringBuilder str =
		        new StringBuilder(GetMMWhen() + " " + GetMMAct() + " " + GetMMHow() + " pool " + GetMMName() + " at " +
		                          StartingResources);

            var addExp = GetMMExp();			
			if (addExp != "")
            {
                str.Append(" add 0" + addExp);
            }
			if (m_startingCapacity != 0)
            {
                str .Append(" max " + m_startingCapacity);
            }
            return str.ToString();
        }

        public bool TurnPoolEnabled()
        {
            switch (m_turnPoolType)
            {
                    case TurnPoolType.None:
                        return true;

                case TurnPoolType.Normal:
                    return Resources.Any();
                case TurnPoolType.Inverse:
                    return !Resources.Any();
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }
    }

    /// <summary>
    /// None je nulová hodnota, Normal- navázane fungují, jen pokud má resourcy, Inverse - navazane fungují,pokud nemá resourcy
    /// </summary>
    public enum TurnPoolType
    {
        None,
        Normal,
        Inverse
    }
}
