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

namespace Machinations.FromFlash.machinations.model
{
    /// <summary>
    /// Machinations mode, able to distibute resources or to trigger other elements by probability
    /// </summary>
    public class Gate : MachinationsNode
    {
        public const string OutputProbable = "probable";
		public const string OutputProbablePercentage = "probable_percentage";
		public const string OutputConditional = "conditional";
		public const string OutputColorCoded = "color_coded";
        
        private GateMode m_gateType;
		private string OutputType;
		
		
		public double Value;
		public double DisplayValue;
		public double Counting;
		public double InputStateValue;
		
		
		public Gate()
		{
            Size = 16;
            GateType = GateMode.Deterministic;
        }


        public override NodeClass NodeClass => NodeClass.Gate;

        public GateMode GateType
        {
            get { return m_gateType; }
            set {
                if (m_gateType != value)
                {
                    m_gateType = value;
                    UpdateDescription();
                }
            }
        }

        public override XmlElement GenerateXml(XmlDocument document, XmlElement element = null)
		{
            element = base.GenerateXml(document, element);
            element.SetAttribute("gateType", GateType.ToString());
			return element;
		}

        public override void ReadXml(XmlElement element) 
		{
			base.ReadXml(element);
            try
            {
                GateType = (GateMode)Enum.Parse(typeof(GateMode), element.GetAttribute("gateType"), true);
            }
            catch (ArgumentException)
            {
                GateType = GateMode.Deterministic;
            }
		}

        public override void Fire()
        {
            bool inhibited = false;
            m_inputs.ForEach(input =>
            {
                if (input is StateConnection && (input as StateConnection).Inhibited) inhibited = true;
            });
            if(inhibited)return;
			base.Fire();
			if (ResourceInputCount == 0)
            {
                Satisfy();
                ReceiveResource(Colors.White, null);
			}
            else
            {
                Pull();
			}
            LogSelf();
		}

		public override void Satisfy() 
		{
			base.Satisfy();
            m_outputs.ForEach(output =>
            {
                StateConnection stateConnection = output as StateConnection;
                if (stateConnection != null && stateConnection.Label.Type ==  LabelType.Trigger) stateConnection.Fire();
            });
		}
		
		public override void Prepare(bool doEvents) 
		{
			base.Prepare(doEvents);
			OutputType = OutputProbable;
			//determine output type
			ResourceOutputCount = 0;
			Color color = Color;
            m_outputs.ForEach(output =>
            {
                MachinationsConnection machinationsConnection = output as MachinationsConnection;
                if (machinationsConnection != null)
                {
                    ResourceOutputCount ++;
                    if (machinationsConnection.Label.Type ==  LabelType.Probability)OutputType = OutputProbablePercentage;
                    if (machinationsConnection.Label.IsCondition()) OutputType = OutputConditional;
                    if (ResourceOutputCount == 1) Color = machinationsConnection.Color;
                    else
                    {
                        if (color != machinationsConnection.Color && OutputType == OutputProbable)
                            OutputType = OutputColorCoded;
                    }
                }
            });
			if (OutputType == OutputConditional && GateType == GateMode.Deterministic) Value = 0;
			else Value = -1;
            Counting = 0;
            InputStateValue = 0;
			if (OutputType == OutputProbablePercentage) CheckDynamicProbabilities();
        }

        public override void Stop() 
		{
			DisplayValue = 0;
			if (m_doEvents) OnChanged(GraphEvent.ElementChange);
			base.Stop();
		}
		
		public override void ReceiveResource(Color color, ResourceConnection flow) 
		{
			//if (_inhibited) return;
			base.ReceiveResource(color, flow);
			//determine value
			switch (OutputType) {
				case OutputConditional:
                    DistributeConditional(color);
					break;
				case OutputProbable:
                    DistributeProbable(color);
					break;
				case OutputProbablePercentage:
                    DistributeProbablePercentage(color);
					break;
				case OutputColorCoded:
                    DistributeColorCoded(color);
					break;
			}
			//if (doEvents && flowOutputCount == 1) {
			if (m_doEvents && OutputType == OutputConditional)
            {
				DisplayValue = Math.Min((m_graph as MachinationsGraph).FireInterval* 0.75, 0.5);
                //OnChanged(GraphEvent.ElementChange);
			}
		}
		
		public override void AutoFire() 
		{
			if ((m_graph as MachinationsGraph).ActionsPerTurn > 0 && OutputType == OutputConditional && GateType == GateMode.Deterministic) {
				Value = 0;
			}
			base.AutoFire();
		}
		
		public override void Update(double time)
		{
			base.Update(time);
			if (Counting > 0) {
				Counting -= time;
				if (Counting <= 0) {
					Counting = 0;
					Value = InputStateValue;
				}
			}
			if (DisplayValue > 0) {
				DisplayValue-= time;
				if (DisplayValue <= 0) {
					DisplayValue = 0;

                    OnChanged(GraphEvent.ElementChange);
				}
			}
		}
		
		private void Produce(Color color, MachinationsConnection output)
		{
			ResourceConnection flow = output as ResourceConnection;
			if (flow != null)
            {
				flow.Resources.Add(new Resource(color, 0));
				flow.RequestQueue.Add(1);
            }
			StateConnection state = output as StateConnection;
		    state?.Fire();
		}
		
		private void DistributeColorCoded(Color color)
		{
		    bool ret = false;
            m_outputs.ForEach(output =>
            {
                MachinationsConnection machinationsConnection = output as MachinationsConnection;
                if (machinationsConnection != null && machinationsConnection.Color == color)
                {
                    Produce(color, machinationsConnection);
                    ret = true;
                }
            });
            if(ret) return;
			if ((m_graph as MachinationsGraph).ColorCoding) {
                //still here so distribute probable and change the color
                DistributeProbable(ColorsUtils.TransparentColor);
			/*} else {
				//still here so direct to the first connection with the same color as the gate
				for (i = 0; i < l; i++) {
					output = m_outputs[i] as MachinationsConnection
					if (output && output.color == this.color) {
						produce(color, output);
						return;
					}
				}*/
			}
			
		}
		
		private void DistributeProbablePercentage(Color color)
		{
			if (GateType == GateMode.Deterministic) {
				Value += 11;
				Value %= 100;
			} else
			{
			    Value = RandomUtils.NextDouble()*100;
			}
			double r = Value;
		    foreach (var output in m_outputs)
		    {
		        MachinationsConnection machinationsConnection = output as MachinationsConnection;
                if (machinationsConnection != null)
                {
                    if (r < machinationsConnection.Label.Value)
                    {
                        Produce(color, machinationsConnection);
                        return;
                    }
                    r -= machinationsConnection.Label.Value;
                }
            }
		}
		
		private void DistributeProbable(Color color)
		{
			double t = m_outputs.OfType<MachinationsConnection>().Sum(output => output.Label.Value);
			if (GateType == GateMode.Deterministic)
            {
				Value++;
                if (Math.Abs(t) < 0.01)
                    Value = 0;
                else
                    Value %= t;
			} else {
				Value = RandomUtils.NextDouble() * t;
			}
			double r = Value;
			
            foreach (GraphConnection connection in m_outputs)
            {
                MachinationsConnection output = connection as MachinationsConnection;
                if (output != null)
                {
                    if (r < output.Label.Value) {
                        //if color is undetermined, adapt color from output
                        if (color == ColorsUtils.TransparentColor) {
                            color = output.Color;
                        }

                        Produce(color, output);
                        return;
                    }
                    r -= output.Label.Value;
                }
            }
		}

        private void DistributeConditional(Color color)
        {
            switch (GateType)
            {
                case GateMode.Deterministic:
                    Value++;
                    if (Counting == 0 && ((MachinationsGraph) m_graph).ActionsPerTurn == 0)
                    {
                        Counting = ((MachinationsGraph) m_graph).FireInterval - 0.001;
                    }
                    break;
                //default:
                case GateMode.Dice:
                    Value = ((MachinationsGraph) m_graph).GetDiceValue() + InputStateValue;
                    break;
                case GateMode.Skill:
                    Value = ((MachinationsGraph) m_graph).GetSkillValue() + InputStateValue;
                    break;
                case GateMode.Strategy:
                    Value = ((MachinationsGraph) m_graph).GetStrategyValue() + InputStateValue;
                    break;
                case GateMode.Multiplayer:
                    Value = ((MachinationsGraph) m_graph).GetMultiplayerValue() + InputStateValue;
                    break;
            }

            bool produced = false;
            m_outputs.ForEach(o =>
            {
                MachinationsConnection output = o as MachinationsConnection;
                if (output != null)
                {
                    if (output.Label.CheckCondition(Value))
                    {
                        produced = true;
                        Produce(color, output);
                    }
                }
            });
            if (!produced)
            {
                m_outputs.ForEach(o =>
                {
                    MachinationsConnection output = o as MachinationsConnection;
                    if (output != null && output.Label.Type ==  LabelType.Else)
                    {
                        Produce(color, output);
                    }
                });
            }
        }

        public void Modify(double delta)
        {
			InputStateValue += delta;
		}
		
		/*public function recalculate():void {
			inputStateValue = 0;
			var l:int = inputs.Count;
			for (var i:int = 0; i < l; i++) {
				if (inputs[i] is StateConnection) inputStateValue += (inputs[i] as StateConnection).state;
			}
		}*/	
		
		public void CheckDynamicProbabilities()
        {
			double prob = 0;
			int dynProb = 0;
            m_outputs.ForEach(o =>
            {
                MachinationsConnection output = o as MachinationsConnection;
                if (output.Label.Type ==  LabelType.ProbabilityDynamic) {
					dynProb++;
				}
				if (output.Label.Type ==  LabelType.Probability) {
					prob+=output.Label.Value;
				}
            });
            if (dynProb > 0) {
				prob = Math.Max(0, (100 - prob) / dynProb);
                m_outputs.ForEach(o =>
                {
                    MachinationsConnection output = o as MachinationsConnection;
                    if (output.Label.Type ==  LabelType.ProbabilityDynamic)
                    {
                        output.Label.SetDynamicProbability(prob);
                    }
                });
			}
		}
		
		public override string ToMMString()
        {
			return GetMMWhen() + " " + GetMMAct() + " " + GetMMHow() + " gate " + GetMMName();
        }

        public override void UpdateDescription()
        {
            DescriptionChange(NodesDescription.DiceSymbolOrder, GateType == GateMode.Deterministic ? "": NodesDescription.DiceSymbol);
            base.UpdateDescription();
        }
    }

    public enum GateMode
    {
         None,
         Deterministic,
         Dice,
         Skill, 
         Strategy, 
         Multiplayer 
    }
}
