using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Media;
using Machinations.FromFlash.graph;
using Machinations.UserComponents;

namespace Machinations.FromFlash.machinations.model
{
    /// <summary>
    /// connection class for resource flow
    /// </summary>
    public class ResourceConnection: MachinationsConnection
    {
        private List<int> m_requestQueue;
        public int JustPulled;
		private List<Resource> m_resources;
		public double Speed;
		public int Delivered;
        public bool Instantaneous;

        public ResourceConnection()
        {
            m_resources = new List<Resource>();
        }

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

        public List<int> RequestQueue
        {
            get { return m_requestQueue; }
            set { m_requestQueue = value; }
        }

        public void Produce(Color sourceColor, Color sourceResourceColor)
        {
			int amount = Convert.ToInt32(Label.Value);
			Label.GenerateNewValue();
			RequestQueue.Add(Math.Max(amount, 0));
			if (amount <= 0) return;
			//double period = (m_graph as MachinationsGraph).FireInterval;
			//period = (period / amount) * -Speed;
			Color c;
			if (Color == sourceColor) c = sourceResourceColor;
			else c = Color;
			for (int i = 0; i < amount; i++) {
				//Resources.Add(new Resource(c, i* period));
                Resources.Add(new Resource(c, 0.5));
			}
        }

        public override ConnectionClass ConnectionClass => ConnectionClass.ResourceConnection;

        public override void Prepare(bool doEvents) 
		{
			base.Prepare(doEvents);
            //if ((m_graph as MachinationsGraph).ResourceSpeed > 0)
            //{
            //    Speed = (m_graph as MachinationsGraph).ResourceSpeed / TotalLength;
            //}
            //else
            //{
                Speed = 1;
            //}
            if (Label.Text == "") Label.Text = "1";
            Delivered = 0;
			JustPulled = 0;
			RequestQueue = new List<int>();
			Instantaneous = ((m_graph as MachinationsGraph).DistributionMode == DistributionMode.Instantaneous);

            CheckInhibition();

		}

        public override void Stop() 
		{
			Resources.Clear();
			base.Stop();
		}

        public override void Update(double time) 
		{
			base.Update(time);
		    int resourcesCount = Resources.Count;
			if (Instantaneous)
			{
			    var endNode = (MachinationsNode)End;
				for (int i = Resources.Count -1; i >= 0; i--)
                {
					//if (Resources[i].Position <= 0)
     //               {
					//	Resources[i].Position += 1;
					//} else

                    //TO DO: posilat resourcz
                    {
						Delivered++;
                        endNode?.ReceiveResource(Resources[i].Color, this);
                        Resources.RemoveAt(i);
                        CheckInhibition();
					}
				}
                if(resourcesCount > 0 && endNode != null && endNode.DoEvents)
                    endNode.OnChanged(GraphEvent.ElementChange);
			} else
            {
				for (int i = Resources.Count-1; i >= 0; i--) {
					Resources[i].Position += time* Speed;
					if (Resources[i].Position >= 1) {
						Delivered++;
					    (End as MachinationsNode)?.ReceiveResource(Resources[i].Color, this);
					    Resources.RemoveAt(i);

                        CheckInhibition();
					}
				}
				if (resourcesCount > 0 && DoEvents)
                    OnChanged(GraphEvent.ElementChange);
			}
		}

        public bool Pull(int discount = 0)
        {
            if (Inhibited)
                return false;
			if (discount< 0) discount = 0;
			Pool pool = Start as Pool;
			if (pool == null && Start is Source && ((Source) Start).ActivationMode == ActivationMode.Passive)
			{
			    var source = (Source) Start;
                Produce(source.Color, source.ResourceColor);
				return true;
			}
			if (pool == null) return false;
			int amount = Convert.ToInt32(Label.Value);
			for (int i = 0; i < Label.GeneratedValues.Count; i++) {
				RequestQueue.Add(Math.Max(Convert.ToInt32(Label.GeneratedValues[i]), 0));
			}
			Label.GenerateNewValue();
			if (amount <= 0) return true;
			
			//keep track of the number of demands on a single pool
			pool.Pulls++;
			
			double period = (m_graph as MachinationsGraph).FireInterval;
			period /= amount;
			double spawnPos = period* -Speed;
			
			Color c;
			if (Color == pool.Color || !(m_graph as MachinationsGraph).ColorCoding ) c = ColorsUtils.TransparentColor;
			else c = Color;
			
			JustPulled = 0;
			
			for (int i = 0; i < amount; i++) {
			    double t = 0;
				if (!Instantaneous) t = i * period;
				var color = Label.DrawRandom ? pool.RemoveRandomResource(t) : pool.RemoveResource(c, t);
				if (color != ColorsUtils.TransparentColor) {
					Resources.Add(new Resource(color, i* spawnPos));
					JustPulled++;
				}
				if (color == ColorsUtils.TransparentColor)
                {
					if (i == 0 && End is MachinationsNode && (End as MachinationsNode).ActivationMode != ActivationMode.Automatic)
                    {
						Blocked = Math.Min((m_graph as MachinationsGraph).FireInterval* 0.75, 0.5);
						if (DoEvents) OnChanged(GraphEvent.ElementChange);
					}
					if ((m_graph as MachinationsGraph).TimeMode == TimeMode.Synchronous)
                    {
						if (i==0) Blocked = Math.Min((m_graph as MachinationsGraph).FireInterval* 0.75, 0.5);						
						pool.OverPulled = true;
					}
					if (JustPulled == 0)
					{
					   //nothing is pulled cancel the pull request in queue
					   RequestQueue.RemoveAt(RequestQueue.Count - 1);
					}
					return false;
				}
				pool.CheckInhibition();
			}
			return true;
		}

        public bool CanPull(int discount = 0)
		{
			Pool pool = Start as Pool;
			if (pool != null)
            {
				int amount = Convert.ToInt32(Label.Value);
				amount -= discount;
				if (amount <= 0) return true;
				Color c;
				if (Color == pool.Color) c = ColorsUtils.TransparentColor;
				else c = Color;
				if (pool.CanRemoveResource(c, amount)) {
					return true;
				}
				
				if ((m_graph as MachinationsGraph).TimeMode == TimeMode.Synchronous) {
					pool.OverPulled = true;
					Blocked = Math.Min((m_graph as MachinationsGraph).FireInterval* 0.75, 0.5);
				}			
			}
			
			if (End is MachinationsNode && ((MachinationsNode) End).ActivationMode != ActivationMode.Automatic) {
				Blocked = Math.Min((m_graph as MachinationsGraph).FireInterval* 0.75, 0.5);
				if (DoEvents) OnChanged(GraphEvent.ElementChange);
			}
			
			
			return false;
			
		}

        public void CheckInhibition(bool communicate = true)
        {
			m_communicateInhibition = communicate;
			
			bool inh = false;
			for (int i = 0; i < m_inputs.Count; i++)
			{
			    var connection = m_inputs[i] as StateConnection;
			    if (connection != null && connection.IsActivator() && connection.Inhibited) {
					inh = true;
					break;
				}
			}
		    //if ((Start is Pool) && (Start as Pool).Caption == "keys") Trace(inh);
			if (!inh)
            {
				if (Start is MachinationsNode && (Start as MachinationsNode).Inhibited)
					inh = true;
				//Disabled these checks to prevent unwanted (and seemingly random) inhibiting of resource connections at the start of a session
				/*
				if (end is Drain && !(end as Drain).canDrain()) {
					inh = true;
				}
				if (end is Converter && !(end as Converter).canDrain()) {
					inh = true;
				}*/
				if (Resources.Count > 0) inh = false;
			}
			Inhibited = inh;
		}

        public void Fire()
        {
			Firing = Math.Min((m_graph as MachinationsGraph).FireInterval* 0.75, 0.5);
			if (Start is Pool)
                Pull(0);
			else if (Start is Source && !(Start is Converter))
			{
			    var source = (Source) Start;
                Produce(source.Color, source.ResourceColor);
            }
                
		}

        public void UndoPull(bool undoAllOfEndNode = true)
        {
			int u = 0;
			int i = Resources.Count;
			while (i > 0 && u<JustPulled)
            {
				i--;
				if (Resources[i].Position <= 0)
                {
					if (!undoAllOfEndNode && Start is Pool)
                    {
						(Start as Pool).ReturnResource(Resources[i].Color);
					}
					u++;
					Resources.RemoveAt(i);
				}
			}
			if (u > 0)
            {
				Blocked = Math.Min((m_graph as MachinationsGraph).FireInterval* 0.75, 0.5);
				RequestQueue.RemoveAt(RequestQueue.Count - 1);
				if (undoAllOfEndNode && (End as MachinationsNode).PullMode == PullMode.PullAll)
                {
					(End as MachinationsNode).UndoPull();
				}
			}
			JustPulled = 0;
		}

        public override string ToMMString() 
		{
			StringBuilder str = new StringBuilder(GetMMName() + ": ");
            str.Append(m_start?.GetMMName());
            str.Append(" -" + Label.GetMMText() + "-> ");
            str.Append(m_end?.GetMMName());
			
			return str.ToString();
        }
    }

    public class MergedResourceConnection : ResourceConnection, IEquatable<MergedResourceConnection>, IMergedConnection
    {
        private readonly MergedConnectionKey m_mergedConnectionKey;

        public MergedResourceConnection(MergedConnectionKey mcc)
        {
            m_mergedConnectionKey = mcc;
        }

        public MergedConnectionKey MergedConnectionKey
        {
            get { return m_mergedConnectionKey; }
        }

        protected override void InhibitedChangeForReal()
        {
            base.InhibitedChangeForReal();
            m_mergedConnectionKey.InnerConnection.ForceChangeInhibition(Inhibited);
            m_mergedConnectionKey.OuterConnection.ForceChangeInhibition(Inhibited);
        }

        public bool Equals(MergedResourceConnection other)
        {
            if (ReferenceEquals(null, other)) return false;
            if (ReferenceEquals(this, other)) return true;
            return base.Equals(other) && m_mergedConnectionKey.Equals(other.m_mergedConnectionKey);
        }

        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj)) return false;
            if (ReferenceEquals(this, obj)) return true;
            if (obj.GetType() != this.GetType()) return false;
            return Equals((MergedResourceConnection) obj);
        }

        public override int GetHashCode()
        {
            unchecked
            {
                return (base.GetHashCode() * 397) ^ m_mergedConnectionKey.GetHashCode();
            }
        }
    }
}
