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

namespace Machinations.FromFlash.graph
{
    
    /// <summary>
    /// Abstract class, can holds machinations elements
    /// </summary>
    public abstract class Graph : IGraph
    {
        /**
		 * Contains all elements in the graph
		 */
        protected List<GraphElement> m_elements;
        private List<IUserComponentSchema> m_userComponents;

        public event EventHandler Changed;

        public Graph()
        {
            m_elements = new List<GraphElement>();
            m_userComponents = new List<IUserComponentSchema>();
            Clear();
        }


        public List<GraphElement> Elements
        {
            get { return m_elements; }
            set { m_elements = value; }
        }

        public List<IUserComponentSchema> UserComponents
        {
            get { return m_userComponents; }
            set { m_userComponents = value; }
        }

        /**
		 * Add an element to the graph
		 * @param	element
		 */

        public void AddElement(GraphElement element)
        {
            element.Graph = this;
            m_elements.Add(element);
        }

        /**
         * Removes an element from a graph, do not call this function directly call element.dispose() instead
         * @param	element
         */

        public virtual void RemoveElement(GraphElement element)
        {
            m_elements.Remove(element);
        }

        protected void SortAndSetIds()
        {
            int counter = 0;
            SortElements();
            m_elements.ForEach(element =>
            {
                element.Id = counter;
                counter++;
            });
        }

        private void UserComponentsSave(XmlElement parentElement)
        {
            if (!UserComponents.Any())
                return;
            List<string> alreadySaved = new List<string>();
            var componentsTemplatesElement = parentElement.OwnerDocument.CreateElement("ComponentsTemplates");
            parentElement.AppendChild(componentsTemplatesElement);
            //var componentsUsageElement = document.CreateElement("ComponentsUsage");
            //de.AppendChild(componentsUsageElement);
            int id = 0;
            UserComponents.ForEach(uc =>
            {
                uc.UcId = id;
                id++;
                if (!alreadySaved.Contains(uc.Name + uc.Created))
                {
                    componentsTemplatesElement.AppendChild(uc.GenerateXmlTemplate(parentElement.OwnerDocument));
                    alreadySaved.Add(uc.Name + uc.Created);
                }
                //uc.Zip();
                //componentsUsageElement.AppendChild(uc.GenerateXml(document));
            });
        }

        private void SortElements()
        {
            var newElements =
                m_elements.Where(e => e is IUserComponentInstance).Concat(m_elements.Where(e => !(e is IUserComponentInstance))).ToList();
            if (m_elements.Count != newElements.Count)
                throw new ArgumentException();
            m_elements = newElements;
        }

        /**
		 * Generate a XML object that represents the graph
		 * @return an XML object representing the graph
		 */

        public virtual XmlDocument GenerateXml()
        {
            SortAndSetIds();
            XmlDocument document = new XmlDocument();
            //document.AppendChild() <graph/>
            m_elements.ForEach(element => document.AppendChild(element.GenerateXml(document)));
            return document;
        }

        public void FillXmlElement(XmlElement filled)
        {
            UserComponentsSave(filled);
            SortAndSetIds();
            m_elements.ForEach(el =>
            {
                filled.AppendChild(el.GenerateXml(filled.OwnerDocument));
            });
        }

        public virtual void Clear()
        {
            m_elements.Clear();
            m_userComponents.Clear();
        }
        
        public virtual void OnChanged(string reason, string message = null)
        {
            Changed?.Invoke(this, new GraphEvent { Reason = reason, Message = message });
        }

        public ResourceConnection CreateResourceConnection(GraphElement startElement, GraphElement endElement, Color color, string labelText = null)
        {
            return (ResourceConnection) CreateConnection(ConnectionClass.ResourceConnection, startElement, endElement, color, labelText);
        }

        public StateConnection CreateStateConnection(GraphElement startElement, GraphElement endElement, Color color, string labelText = null)
        {
            var stateConnection = (StateConnection)CreateConnection(ConnectionClass.StateConnection, startElement, endElement, color, labelText);
            if(stateConnection.Label.IsCondition())
                stateConnection.State = stateConnection.State;
            return stateConnection;
        }


        public Point GetCenter()
        {
            var nodes = Elements.OfType<GraphNode>().ToArray();
            if (nodes.Length == 0)
                return new Point();
            var minX = nodes.Min(e => e.Position.X);
            var maxX = nodes.Max(e => e.Position.X);
            var minY = nodes.Min(e => e.Position.Y);
            var maxY = nodes.Max(e => e.Position.Y);
            return new Point((minX + maxX) / 2, (minY + maxY) / 2);
        }

        public Int32Rect GetRectangle()
        {
            var nodes = Elements.OfType<GraphNode>().ToArray();
            if (nodes.Length <= 1)
                return new Int32Rect(0,0,16,10);
            var minX = Convert.ToInt32(nodes.Min(e => e.Position.X));
            var maxX = Convert.ToInt32(nodes.Max(e => e.Position.X));
            var minY = Convert.ToInt32(nodes.Min(e => e.Position.Y));
            var maxY = Convert.ToInt32(nodes.Max(e => e.Position.Y));
            var shiftedMinX = Math.Max(0, minX - 20);
            var shiftedMinY = Math.Max(0, minY - 20);
            var lengthX = maxX - minX + 40;
            var lengthY = maxY - minY + 40;
            return new Int32Rect(shiftedMinX, shiftedMinY, Convert.ToInt32(Math.Max(lengthX, lengthY / 3)), Convert.ToInt32(Math.Max(lengthX/3, lengthY)));
        }

        private MachinationsConnection CreateConnection(ConnectionClass cclass, GraphElement startElement, GraphElement endElement, Color color, string labelText = null)
        {
            MachinationsConnection connection;
            switch (cclass)
            {
                case ConnectionClass.ResourceConnection:
                    connection = new ResourceConnection();
                    break;
                case ConnectionClass.StateConnection:
                    connection = new StateConnection();
                    break;

                default:
                    throw new ArgumentOutOfRangeException(nameof(cclass), cclass, null);
            }
            connection.Graph = this;
            AddElement(connection);
            connection.ConnectToElement(startElement, true);
            connection.ConnectToElement(endElement, false);
            connection.Color = color;
            if(labelText != null)
                connection.Label.Text = labelText;
            return connection;
        }
    }
}