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

namespace Machinations.FromFlash.machinations.model
{
    /// <summary>
    /// contains all common things for nodes in machinations frameworks
    /// </summary>
    public abstract class MachinationsNode : GraphNode, IPreparable
    {
        protected bool m_doEvents;
        protected string m_caption;
        protected ActivationMode m_activationMode;
        protected PullMode m_pullMode;

        public double Thickness;
        public Color Color;
        public double Size;
        public int Actions;
        public int ResourceInputCount;
        public int ResourceOutputCount;
        public int StateOutputCount;
        public int StateInputCount;
        public bool AiControled;
        public double Firing;
        public bool FireFlag;
        public List<SimlinkModel> m_simlinks; 

        protected MachinationsNode()
        {
            Size = 20;
            Thickness = 2;
            Color = ColorsUtils.InitColor;
            m_caption = "";
            m_activationMode = ActivationMode.Passive;
            Actions = 1;
            FireFlag = false;
            m_pullMode = PullMode.PullAny;
            m_simlinks = new List<SimlinkModel>();
        }
        /// <summary>
        /// enum for every node type, every different type need to have one
        /// </summary>
        public override NodeClass NodeClass => NodeClass.None;

        public string Caption
        {
            get { return m_caption; }
            set
            {
                if (m_caption != value)
                {
                    m_caption = value;
                    OnPropertyChanged(nameof(Caption));
                }
            }
        }

        public ActivationMode ActivationMode
        {
            get { return m_activationMode; }
            set
            {
                if (m_activationMode != value)
                {
                    m_activationMode = value;
                    UpdateDescription();
                }
            }
        }

        public PullMode PullMode
        {
            get { return m_pullMode; }
            set
            {
                if (m_pullMode != value)
                {
                    m_pullMode = value;
                    UpdateDescription();
                }
            }
        }

        public List<SimlinkModel> Simlinks
        {
            get { return m_simlinks; }
        }

        public bool DoEvents
        {
            get { return m_doEvents; }
            set { m_doEvents = value; }
        }

        //public override Point GetConnection(MyVector towards)
        //{
        //    Point cloned = Position.Clone();
        //    Point differenced = towards.Subtract(
        //        Position);
        //    differenced.Normalize();
        //    differenced.ScaleBy(Size + Thickness + 2);
        //    cloned.IncrementBy(differenced);
        //    return cloned;
        //}

        public override void UpdateDescription()
        {
            DescriptionChange(NodesDescription.ActivationModeOrder, NodesDescription.ActivationModeDescription[m_activationMode]);
            DescriptionChange(NodesDescription.PullModeOrder, NodesDescription.PullModeDescription[m_pullMode]);
            base.UpdateDescription();
        }

        public override XmlElement GenerateXml(XmlDocument document, XmlElement element = null)
        {
            element = base.GenerateXml(document, element);
            element.SetAttribute("color", Color.ToNiceString());
            element.SetAttribute("caption", m_caption);
            element.SetAttribute("activationMode", ActivationMode.ToString().ToLower());
            element.SetAttribute("pullMode", PullMode.ToString().ToLower());
            element.SetAttribute("actions", Actions + "");
            m_simlinks.ForEach(simlink => simlink.GenerateXml(document, element));
            return element;
        }



        public override void ReadXml(XmlElement element)
        {
            base.ReadXml(element);
            Color = ColorsUtils.GetColorFromString(element.GetAttribute("color"));
            m_caption = element.GetAttribute("caption");
            Thickness = 2;
            ActivationMode =
                (ActivationMode) Enum.Parse(typeof(ActivationMode), element.GetAttribute("activationMode"), true);
            var dict = new Dictionary<string, PullMode>
            {
                {"pull all", PullMode.PullAll},
                {"pull any", PullMode.PullAny},
                {"push all", PullMode.PushAll},
                {"push any", PullMode.PushAny},
                {"pullall", PullMode.PullAll},
                {"pullany", PullMode.PullAny},
                {"pushall", PullMode.PushAll},
                {"pushany", PullMode.PushAny},

                {"any", PullMode.PullAny}
            };
            PullMode = dict[element.GetAttribute("pullMode")];
            if (element.GetAttribute("interactive") == "1") ActivationMode = ActivationMode.Interactive;
            Actions = int.Parse(element.GetAttribute("actions"));
            foreach (var child in element.ChildNodes)
            {
                XmlElement childNode = child as XmlElement;
                if (childNode?.Name == "simlink")
                {
                    new SimlinkModel(this, childNode);
                }
            }
        }

        public virtual void CheckInhibition()
        {
            bool inhibited = false;
            int livingInputs = 0;
            int inputCount = m_inputs.Count;
            int pullRequests = 0;

            for (int i = 0; i < inputCount; i++)
            {
                StateConnection stateConnection = m_inputs[i] as StateConnection;
                if (stateConnection != null)
                {
                    if (stateConnection.Label.Type != LabelType.Trigger &&
                        !(stateConnection.Start is Gate) && stateConnection.Inhibited)
                    {
                        //inhibited = true;
                        Inhibited = true;
                        return;
                    }
                    if (this is Pool && !stateConnection.Inhibited &&
                        stateConnection.IsSetter())
                    {
                        livingInputs++;
                    }
                    if (stateConnection.Label.Type == LabelType.Trigger && NodeClass == NodeClass.EndCondition && !(m_graph as MachinationsGraph)?.Running == false)
                    {
                        //inhibited = true;
                        Inhibited = true;
                        return;
                    }
                }
                ResourceConnection resourceConnection = m_inputs[i] as ResourceConnection;
                if (resourceConnection != null)
                {
                    if (!resourceConnection.Inhibited)
                    {
                        livingInputs++;
                    }
                    if (resourceConnection.RequestQueue != null
                        && resourceConnection.RequestQueue.Count > 0)
                    {
                        pullRequests++;
                    }
                }
            }
            if (livingInputs == 0)
            {
                if ((this as Pool)?.Resources.Count == 0) inhibited = true;
            }
            if (pullRequests > 0) inhibited = false;

            if (m_inhibited != inhibited)
            {
                Inhibited = inhibited;
            }
        }

        public virtual void Prepare(bool doEvents)
        {
            ResourceOutputCount = 0;
            StateOutputCount = 0;
            m_outputs.ForEach(output =>
            {
                if (output is ResourceConnection)
                {
                    ResourceOutputCount++;
                }
                if (output is StateConnection)
                {
                    StateOutputCount++;
                }
            });

            ResourceInputCount = 0;
            StateInputCount = 0;
            m_inputs.ForEach(input =>
            {
                if (input is ResourceConnection)
                {
                    ResourceInputCount++;
                }
                if (input is StateConnection)
                {
                    StateInputCount++;
                }
            });
            AiControled = false;
            Firing = 0;


            m_doEvents = doEvents;
            if (doEvents) OnChanged(GraphEvent.ElementChange);
        }

        public virtual void Stop()
        {
            Firing = 0;
            m_inhibited = false;
            if (m_doEvents) OnChanged(GraphEvent.ElementChange);
        }

        public virtual void AutoFire()
        {
            if ((ActivationMode == ActivationMode.Automatic || FireFlag) && !Inhibited && !AiControled)
            {
                Fire();
                FireFlag = false;
            }
        }

        public virtual void Update(double time)
        {
            if (Firing > 0)
            {
                Firing -= time;
                if (Firing <= 0)
                {
                    Firing = 0;
                    if (m_doEvents) OnChanged(GraphEvent.ElementChange);
                }
            }
        }

        public void Click()
        {
            MachinationsGraph graph = (MachinationsGraph) m_graph;
            if (graph.TimeMode == TimeMode.Synchronous)
            {
                FireFlag = true;
                if (m_doEvents) OnChanged(GraphEvent.ElementChange);
            }
            else
            {
                Fire();
                if (graph.ActionsPerTurn > 0 && ActivationMode == ActivationMode.Interactive)
                {
                    graph.ActionsThisTurn += Actions;
                }
                graph.Update(Actions, true);
                graph.FinishTurn();
            }
        }

        public virtual void Fire()
        {
            SetFiring();
            LogSelf();
        }

        public void SetFiring()
        {
            if (ActivationMode == ActivationMode.Automatic && !(this is ArtificialPlayer) && !AiControled) return;
            Firing = Math.Min((m_graph as MachinationsGraph).FireInterval*0.75, 0.5);
            if (m_doEvents) OnChanged(GraphEvent.ElementChange);
        }

        public virtual void Satisfy()
        {
            CheckInhibition();
        }

        public virtual void ReceiveResource(Color color, ResourceConnection flow)
        {
            if (CheckInputs()) Satisfy();
        }

        public void Pull()
        {
            int d;
            ResourceConnection flow;
            bool blocked = false;
            m_inputs.ForEach(input =>
            {
                flow = input as ResourceConnection;
                if (flow != null)
                {
                    if (!flow.Inhibited)
                    {
                        d = flow.Delivered;
                        d += flow.Resources.Count;
                        if (!flow.Pull(d)) blocked = true;
                    }
                    else
                    {
                        //if(!((MachinationsGraph)m_graph).PullAllCountsInhibited)
                            blocked = true;
                    }
                }
            });
            if (blocked)
            {
                m_outputs.ForEach(output =>
                {
                    StateConnection outputState = output as StateConnection;
                    if (outputState != null && outputState.Label.Type == LabelType.ReverseTrigger)
                    {
                        outputState.ReverseFire();
                    }
                });
            }

            if (blocked && PullMode == PullMode.PullAll)
            {
                UndoPull();
            }

            if (CheckInputs()) Satisfy();
        }

        public bool CheckInputs()
        {
            bool ok = true;
            m_inputs.ForEach(input =>
            {
                ResourceConnection flow = input as ResourceConnection;
                if (flow != null)
                {
                    if (flow.RequestQueue.Count == 0 || flow.Delivered < flow.RequestQueue[0])
                    {
                        ok = false;
                        return ;
                    }
                }
            });
            if(!ok) return false;

            int total = 0;
            m_inputs.ForEach(input =>
            {
                ResourceConnection flow1 = input as ResourceConnection;
                if (flow1 != null)
                {
                    int q;
                    if (flow1.RequestQueue.Count > 0)
                    {
                        q = flow1.RequestQueue[0];
                        flow1.Delivered = Math.Max(flow1.Delivered - q, 0);
                        flow1.RequestQueue.RemoveAt(0);
                        total += q;
                    }
                }
            });
            return ok && (total > 0);
        }

        public void UndoPull()
        {
            m_inputs.OfType<ResourceConnection>().ToList().ForEach(input => { input.UndoPull(false); });
        }

        public override void RemoveInput(MachinationsConnection connection)
        {
            base.RemoveInput(connection);
            if (connection is ResourceConnection)
            {
                ResourceInputCount--;
                if (m_doEvents) OnChanged(GraphEvent.ElementChange);
            }
        }

        public override string GetMMName()
        {
            if (string.IsNullOrEmpty(m_caption))
                return NodeClass + " "+ Id;
            else
                return m_caption;
        }

        public string GetMMWhen()
        {
            switch (ActivationMode)
            {
                case ActivationMode.Passive:
                    return "";
                case ActivationMode.Automatic:
                    return "auto";
                case ActivationMode.Interactive:
                    return "user";
                case ActivationMode.OnStart:
                    return "start";
            }
            return "";
        }

        public string GetMMAct()
        {
            switch (PullMode)
            {
                case PullMode.PullAll:
                case PullMode.PullAny:
                    return "pull";
                case PullMode.PushAll:
                case PullMode.PushAny:
                    return "push";
            }
            return "";
        }

        public string GetMMHow()
        {
            switch (PullMode)
            {
                case PullMode.PullAll:
                case PullMode.PushAll:
                    return "all";
                case PullMode.PullAny:
                case PullMode.PushAny:
                    return "any";
            }
            return "";
        }

        protected override void InhibitedChangeForReal()
        {
            base.InhibitedChangeForReal();
            if (m_doEvents) OnChanged(GraphEvent.ElementChange);
            m_outputs.ForEach(output =>
            {
                ResourceConnection resCon = output as ResourceConnection;
                resCon?.CheckInhibition();
                StateConnection staCon = output as StateConnection;
                staCon?.CheckInhibition();
            });
        }

        public void AddSimlink(SimlinkModel simlinkModel)
        {
            m_simlinks.Add(simlinkModel);
        }

        public void RemoveSimlink(SimlinkModel simlinkModel)
        {
            foreach (var connection in simlinkModel.Connections)
            {
                m_inputs.Remove(connection);
                m_outputs.Remove(connection);
            }
            m_simlinks.Remove(simlinkModel);
        }

        public override void FinishLoading(List<GraphElement> elements)
        {
            base.FinishLoading(elements);
            m_simlinks.ForEach(sl => sl.FinishLoading(elements));
        }

        protected void LogSelf()
        {
            ((MachinationsGraph) m_graph)?.Log?.Log(this, m_activationMode);
        }
    }

    public enum ActivationMode
    {
        Passive, Interactive, Automatic,  OnStart
    }

    public enum PullMode
    {
        PullAny, PullAll, PushAny, PushAll
    }


}