using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Media;
using System.Xml;
using Machinations.FromFlash.graph;
using Machinations.FromFlash.machinations.model;

namespace Machinations.UserComponents
{
    public abstract class UcInstanceBase : GraphElement, IUserComponentInstance
    {
        protected string m_name;
        protected Point m_position;
        protected int m_radius;
        protected readonly List<OuterInOut> m_outerInOuts;
        protected readonly List<IInOut> m_inOuts;
        private Color m_color;
        private Color m_innerColor;

        /// <summary>
        /// pokud je true, je komponenta zabalena, takze jsou videt outer connections, tj spoje mezi ostatnimi castmi grafu a USInputy
        /// pokud je false, je komponenta rozbalena,takze je soucasti grafu 
        /// </summary>
        protected bool m_zip;

        private List<GraphElement> m_unzippedElements;

        protected UcInstanceBase()
        {
            m_inOuts = new List<IInOut>();
            m_outerInOuts = new List<OuterInOut>();
            m_zip = true;
            m_radius = 80;
            m_color = ColorsUtils.InitColor;
        }
        public abstract UserComponentGraph InternalGraph { get; }
        public string Name
        {
            get { return m_name; }
            set
            {
                m_name = value;
            }
        }

        public Point Position
        {
            get { return m_position; }
            set { m_position = value; }
        }

        public int Radius
        {
            get { return m_radius; }
            set
            {
                m_radius = value;
                ComponentChanged?.Invoke();
            }
        }


        public Color Color
        {
            get { return m_color; }
            set { m_color = value; }
        }

        public Action ComponentChanged { get; set; }

        public IEnumerable<OuterInOut> OuterInOuts
        {
            get { return m_outerInOuts; }
        }

        //public override bool Inhibited
        //{
        //    get { return base.Inhibited; }
        //    set
        //    {
        //        base.Inhibited = value;
        //        foreach (var presentedElement in PresentedElements)
        //        {
        //            presentedElement.Inhibited = value;
        //        }
        //    }
        //}

        public IEnumerable<MachinationsNode> ClickableNodes
        {
            get
            {
                return
                    PresentedElements.OfType<MachinationsNode>()
                        .Where(n => n.ActivationMode == ActivationMode.Interactive);
            }
        }

        bool IUserComponentInstance.IsZipped()
        {
            return m_zip;
        }

        //public override void CheckInhibition(bool comunicate = true)
        //{
        //    if (m_inputs.Cast<StateConnection>().Where(i => i.Label.IsCondition()).Any(sc => sc.Inhibited))
        //    {
        //        Inhibited = true;
        //    }
        //    else
        //    {
        //        Inhibited = false;
        //    }
        //}

        public bool IsZipped { get { return m_zip; } }


        public abstract int CurrentCard { get; set; }
        public abstract IEnumerable<GraphElement> PresentedElements { get; }
        public abstract int InstanceNumber { get; }
        public abstract UserComponentType Type { get; }
        protected abstract IEnumerable<InnerInOut> MyInnerInOuts { get; }
        public abstract void AddLoadedOuterInOut(OuterInOut inOut);
        public abstract IUserComponentSchema GetUcScheme();
        public abstract bool IsSimilar(IUserComponentInstance model);
        public abstract void UpdateOuters();
        public abstract void AppendToGraph(Graph graph);

        public override void AddInput(MachinationsConnection connection)
        {
            if (connection.ConnectionClass == ConnectionClass.StateConnection)
            {
                base.AddInput(connection);
            }
            else
                throw new NotImplementedException("Neni jasny co by měla tato hrana dělat");
        }

       public override Point GetPosition()
        {
            return Position;
        }

        public override void CopyShift()
        {
            m_position = new Point(m_position.X + 20, m_position.Y + 20);
        }

        public override XmlElement GenerateXml(XmlDocument document, XmlElement element = null)
        {
            element = base.GenerateXml(document, element);
            element.SetAttribute("x", Math.Round(Position.X) + "");
            element.SetAttribute("y", Math.Round(Position.Y) + "");
            element.SetAttribute("radius", Radius + "");
            if (m_color != ColorsUtils.InitColor)
                element.SetAttribute("color", m_color.ToNiceString());
            return element;
        }

        public override void ReadXml(XmlElement element)
        {
            base.ReadXml(element);
            Name = element.GetAttribute("name");
            var x = double.Parse(element.GetAttribute("x"));
            var y = double.Parse(element.GetAttribute("y"));
            Position = new Point(x,y);
            Radius = int.Parse(element.GetAttribute("radius"));
            if (element.HasAttribute("color"))
                m_color = ColorsUtils.GetColorFromString(element.GetAttribute("color"));
        }

        protected void CreateMergedInOuts(IEnumerable<InnerInOut> innerInOuts)
        {
            Dictionary<int, OuterInOut> outers = m_outerInOuts.ToDictionary(o => o.InOutNumber);
            foreach (var inner in innerInOuts)
            {
                OuterInOut outer = outers[inner.InOutNumber];
                m_inOuts.Add(new MergedInOut(inner, outer));
            }
        }

        public void Zip()
        {
            if (m_zip) return;
            m_zip = true;
            m_unzippedElements = null;
            ZipOrUnzip();
            //need to zip them in reverse order of unzipping them
            if (InternalGraph.UserComponents != null)
            {
                for (var i = InternalGraph.UserComponents.Count - 1; i >= 0; i--)
                {
                    InternalGraph.UserComponents[i].Zip();
                }
            }
            m_inOuts.Clear();
            InternalGraph.Elements.ForEach(e => e.Inhibited = false);
        }

        public void UnZip()
        {
            if (!m_zip) return;
            m_zip = false;
            m_unzippedElements = new List<GraphElement>(InternalGraph.Elements);
            InternalGraph.UserComponents.ForEach(uc => uc.UnZip());
            CreateMergedInOuts(MyInnerInOuts);
            ZipOrUnzip();
            if (m_color != ColorsUtils.InitColor)
            {
                foreach (var presentedElement in PresentedElements)
                {
                    if(presentedElement is MachinationsNode)
                        ((MachinationsNode) presentedElement).Color = m_color;
                    if (presentedElement is MachinationsConnection)
                        ((MachinationsConnection) presentedElement).Color = m_color;
                    m_inOuts.ForEach(inOut =>
                    {
                        foreach (var inOutMergedConnection in inOut.MergedConnections)
                        {
                            inOutMergedConnection.Color = m_color;
                        }
                    });
                }
            }
        }
        
        protected void ZipOrUnzip()
        {
            //m_inOuts.ForEach(io => io.CorrectMerged());
            ZipUnzipGraphElements();
            if (m_zip)
                m_outerInOuts.ForEach(inOut => m_graph.AddElement(inOut));
            else
                m_outerInOuts.ForEach(inOut => m_graph.RemoveElement(inOut));
            m_inOuts.ForEach(inAndOut =>
            {
                try
                {
                    inAndOut.ProcessConnections(IsZipped, Graph);
                }
                catch (NullReferenceException)
                {
                    var name = this is CardInstance ? ((CardInstance) this).Card.Name : Name;
                    MessageBox.Show(
                        $"při rozbalovani inOutu {inAndOut.Name} v komponentě {name} došlo k chybě. Nesprávně zapojená hrana?");
                }
            });
        }

        public void ZipUnzipGraphElements()
        {
            PresentedElements.ToList().ForEach(e =>
            {
                if (m_zip)
                {
                    m_graph.RemoveElement(e);
                    e.Graph = InternalGraph;
                }

                else
                {
                    m_graph.AddElement(e);

                }
            });
        }

        public List<GraphElement> UnzippedElements => m_unzippedElements ?? InternalGraph.Elements;
    }
}
