using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Windows;
using System.Xml;
using Machinations.Annotations;
using Machinations.Description;
using Machinations.FromFlash.machinations.model;

namespace Machinations.FromFlash.graph
{
    /// <summary>
    /// ancestor of every node and connection, is a basic member of graphs, contains common things for all elements
    /// </summary>
    public abstract class GraphElement : INotifyPropertyChanged, ICloneable
    {
        //private string m_textLabel;
        /**
		 * Identification when saved to XML format
		 */
        protected int m_id;
        /// <summary>
        /// A graph, inside which this element is. During simulation, it should always be Machinations graph
        /// </summary>
        protected Graph m_graph;
        private readonly SortedDictionary<int, string> m_description;

        protected List<GraphConnection> m_inputs;
        protected List<GraphConnection> m_outputs;

        protected List<Pool> m_turnPools;
        private List<int> m_turnPoolIds;

        public event EventHandler Changed;
        //public Action InhibitedChanged;
        public Action DescriptionChanged;
        public event Action OnSelect;
        public event Action OnUnselect;

        protected bool m_inhibited;
        protected bool m_inhibitedNewState;
        private bool m_descriptionChanged;


        protected GraphElement()
        {
            m_id = -1;
            m_inputs = new List<GraphConnection>();
            m_outputs = new List<GraphConnection>();
            m_turnPools = new List<Pool>();
            m_description = new SortedDictionary<int, string>();
            //m_textLabel = "pokus";
        }

        public virtual IEnumerable<GraphConnection> Inputs
        {
            get { return m_inputs; }
        }

        public List<Pool> TurnPools
        {
            get { return m_turnPools; }
            set
            {
                m_turnPools = value;
                UpdateDescription();
            }
        }

        public int Id
        {
            get { return m_id; }
            set { m_id = value; }
        }

        public string Description
        {
            get
            {
                UpdateDescription();
                var description = m_description.Values.Where(v => !v.Equals("")).ToList();
                if (!description.Any()) return "";
                StringBuilder sb = new StringBuilder();
                var last = description.Last();
                foreach (var desc in description)
                {

                    sb.Append(desc);
                    if (!desc.Equals(last))
                        sb.Append(",");
                }
                return sb.ToString();
            }
        }

        public virtual IEnumerable<GraphConnection> Outputs
        {
            get { return m_outputs; }
        }

        public Graph Graph
        {
            get { return m_graph; }
            set { m_graph = value; }
        }

        public Action InhibitedChanged
        {
            get; set;
        }

        //public IObjectView View
        //{
        //    get { return m_view; }
        //    set { m_view = value; }
        //}

        public virtual bool Inhibited
        {
            get { return m_inhibited; }
            set
            {
                m_inhibitedNewState = value;
                if ((m_graph as MachinationsGraph)?.InhibitionChange == true)
                {
                    InhibitedChangeForReal();
                }
            }
        }


        public virtual void Dispose()
        {
            m_inputs.ForEach(c =>
            {
                if (c.End == this)
                    c.End = null;
            });
            m_inputs.Clear();
            m_outputs.ForEach(c =>
            {
                if (c.Start == this)
                    c.Start = null;
            });
            m_outputs.Clear();
            if (m_graph != null)
            {
                m_graph.RemoveElement(this);
                m_graph = null;
            }
            OnChanged(GraphEvent.ElementDispose);
        }

        protected void DescriptionChange(int order, string text = "")
        {
            string previous;
            if (m_description.TryGetValue(order, out previous))
            {
                m_description[order] = text;
                m_descriptionChanged = m_descriptionChanged || !text.Equals(previous);
            }
            else
            {
                m_description[order] = text;
                m_descriptionChanged = true;
            }
        }

        public virtual void UpdateDescription()
        {
            if (m_turnPools.Any())
            {
                DescriptionChange(NodesDescription.HasPoolTurnsOrder,
                    string.Join(",", m_turnPools.Select(tp => tp.Caption.Length > 2 ? tp.Caption.Substring(0, 2) : "TP")));
            }
            else DescriptionChange(NodesDescription.HasPoolTurnsOrder);

            if (m_descriptionChanged)
            {
                m_descriptionChanged = false;
                DescriptionChanged?.Invoke();       
            }
                
        }

        public virtual XmlElement GenerateXml(XmlDocument document, XmlElement element = null)
        {
            if (element == null)
            {
                element = document.CreateElement("element");
            }
            if (m_turnPools.Any())
            {
                StringBuilder sb = new StringBuilder();
                var last = m_turnPools.Last();
                m_turnPools.ForEach(pool =>
                {
                    sb.Append(pool.Id);
                    if (pool != last)
                        sb.Append(',');
                });
                element.SetAttribute("turnPools", sb.ToString());
            }
            element.SetAttribute("myId", Id.ToString());
            return element;
        }

        public virtual void ReadXml(XmlElement element)
        {
            if (element.HasAttribute("turnPools"))
            {
                m_turnPoolIds = new List<int>();
                var ids = element.GetAttribute("turnPools").Split(',');
                foreach (var id in ids)
                {
                    m_turnPoolIds.Add(int.Parse(id));
                }
            }
        }

        public virtual void FinishLoading(List<GraphElement> elements)
        {
            m_turnPoolIds?.ForEach(id =>
            {
                if (id != -1) m_turnPools.Add((Pool) elements[id]);
            });
            UpdateDescription();
        }


        public virtual void AddInput(MachinationsConnection connection)
        {
            m_inputs.Add(connection);
        }

        public virtual void AddOutput(MachinationsConnection connection)
        {
            m_outputs.Add(connection);
        }

        public virtual void RemoveInput(MachinationsConnection connection)
        {
            m_inputs?.Remove(connection);
        }

        public virtual void RemoveOutput(MachinationsConnection connection)
        {
            m_outputs?.Remove(connection);
        }

        public object Clone()
        {
            XmlDocument document = new XmlDocument();
            var xmlElement = GenerateXml(document);
            var newElement = ProgramControlUtils.ReadElement(xmlElement);
            newElement.Graph = Graph;
            return newElement;
        }

        //public virtual Point Position


        //      {


        //          get { return new Point(); }


        //          set { throw new NotImplementedException();}


        //}


        public virtual void MoveBy(double x, double y, double z = 0)
        {
            throw new NotImplementedException();
        }

        public virtual void MoveTo(double x, double y, double z = 0)
        {
            throw new NotImplementedException();
        }

        public virtual string ToMMString()
        {
            throw new NotImplementedException();
        }

        public virtual string GetMMName()
        {
            return "element" + m_id;
        }

        public string GetMMExp()
        {
            string exp = "";
            m_inputs.ForEach(input =>
            {
                StateConnection connection = input as StateConnection;
                if (connection != null)
                {
                    switch (connection.Label.Type)
                    {
                        case LabelType.ChangeValue:
                            {
                                string value = connection.Start.GetMMName();
                                Pool pool = connection.Start as Pool;
                                if (pool != null && pool.StartingResources > 0)
                                    value = "(" + value + "-" + pool.StartingResources + ")";
                                if (pool != null && pool.StartingResources < 0)
                                    value = "(" + value + "+" + Math.Abs(pool.StartingResources) + ")";
                                exp += connection.Label.Text + "*" + connection.Start.GetMMName();
                            }
                            break;
                    }
                }
            });
            return exp;
        }

        public void OnChanged(string reason, string message = null)
        {
            var machinationsGraph = m_graph as MachinationsGraph;
            if (machinationsGraph != null && !machinationsGraph.CanDoEvents)
            {
                machinationsGraph.WaitingForEvents.Add(this);
                return;
            }
            Changed?.Invoke(this, new GraphEvent { Reason = reason, Message = message });
        }

        public void InhibitedChangeApply()
        {
            if (m_inhibited != m_inhibitedNewState)
            {
                InhibitedChangeForReal();
            }
        }

        public void TurnPoolsApply()
        {
            if (m_turnPools.Any())
            {
                if (!m_turnPools.Any(pool => pool.TurnPoolEnabled()))
                {
                    m_inhibited = true;
                    InhibitedChanged?.Invoke();
                }
            }
        }

        protected virtual void InhibitedChangeForReal()
        {
            if (m_inhibited != m_inhibitedNewState)
            {
                m_inhibited = m_inhibitedNewState;
                InhibitedChanged?.Invoke();
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

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

        public abstract void CopyShift();

        public abstract Point GetPosition();
        public void Selected()
        {
            OnSelect?.Invoke();
        }

        public void UnSelected()
        {
            OnUnselect?.Invoke();
        }
    }
}
