using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Input;
using System.Xml;
using Machinations.FromFlash.graph;
using Machinations.FromFlash.machinations.model;
using Machinations.GUI;
using Machinations.UserComponents;
using Machinations.WPF;

namespace Machinations
{
    /// <summary>
    /// class for loading graphs and filling painting canvases by them
    /// </summary>
    public static class ProgramControlUtils
    {
        public static void OpenFile(bool overwrite, string fileName, Graph graph, ProgramControl control,
            PaintingCanvas paintingCanvas = null, bool withModels = true)
        {
            XmlDocument document = new XmlDocument();
            try
            {
                document.Load(fileName);
                LoadGraph(overwrite, graph, document.DocumentElement, paintingCanvas, withModels);
                control.UserComponents.TryAddComponents(graph.UserComponents);
            }
            catch (Exception e)
            {
                paintingCanvas?.Reset();
                MessageBox.Show("Saved file cannot be read. Exception thrown :" + Environment.NewLine + e.Message);
                graph.Clear();
            }
           
        }

        public static void LoadGraph(bool overwrite, Graph graph, XmlElement documentElement
            , PaintingCanvas paintingCanvas = null, bool withModels = true)
        {
            var machinationsGraph = graph as MachinationsGraph;
            if (overwrite)
            {
                paintingCanvas?.Reset();
                graph.Clear();
                machinationsGraph?.LoadGraphSettings(documentElement);
            }
            List<GraphElement> elements;
            List<IUserComponentSchema> components;
            LoadGraphElements(documentElement, graph, out elements, out components);
            foreach (var outerInOut in elements.OfType<OuterInOut>())
            {
                components[outerInOut.UcNumber].GetInstance(outerInOut.InstanceNumber).AddLoadedOuterInOut(outerInOut);
            }
            LoadWithViews(graph, paintingCanvas, withModels, elements, components);
            paintingCanvas?.CreatedCardsChanged();
        }

        public static IEnumerable<IObjectView> LoadWithViews(Graph graph, PaintingCanvas paintingCanvas, bool withModels, List<GraphElement> elements,
            List<IUserComponentSchema> components)
        {
            var views = CreateConnectedViews(paintingCanvas, withModels, elements);
            FinishLoading(graph, elements, components);
            return views;
        }

        private static void FinishLoading(Graph graph, List<GraphElement> elements, List<IUserComponentSchema> components)
        {
            var badConnections =
                elements.OfType<MachinationsConnection>()
                    .Where(c => c.StartId >= 0 && elements[c.StartId] is CompatibilityNode ||
                    c.EndId >= 0 && elements[c.EndId] is CompatibilityNode).ToList();
            badConnections.ForEach(bc =>
            {
                bc.Graph?.RemoveElement(bc);
                bc.Dispose();
                elements.Remove(bc);
            });
            
            elements.ForEach(el => el.FinishLoading(elements));
            elements.ForEach(graph.AddElement);
            components.ForEach(graph.UserComponents.Add);
            //graph.UserComponents.ForEach(uc => uc.UnZip());
            var machinationsGraph = graph as MachinationsGraph;
            if (machinationsGraph != null)
            {
                foreach (Pool pool in elements.OfType<Pool>())
                {
                    if (pool.IsTurnPool)
                        machinationsGraph.TurnPools.Add(pool);
                }
            }
            //foreach (var card in components.OfType<Cards>())
            //{
            //    card.GenerateCards();
            //}
            foreach (var compatibilityNode in elements.OfType<CompatibilityNode>())
            {
                compatibilityNode.Dispose();
                graph.RemoveElement(compatibilityNode);
            }
        }

        public static IEnumerable<IObjectView> CreateConnectedViews(PaintingCanvas paintingCanvas, bool withModels, List<GraphElement> elements)
        {
            Dictionary<GraphElement, IObjectView> createdViews = new Dictionary<GraphElement, IObjectView>();
            if (paintingCanvas != null)
            {
                elements.ForEach(e => CreateViews(e, paintingCanvas, createdViews, elements));
            }
            var failedConnections = new List<GraphConnection>();
            elements.ForEach(e =>
            {
                MachinationsConnection c = e as MachinationsConnection;
                if (c != null)
                {
                    try
                    {
                        if ((c.StartId < 0 || !(elements[c.StartId] is CompatibilityNode)) && (c.EndId < 0 || !(elements[c.EndId] is CompatibilityNode)))
                            ConnectConnections(c, elements, createdViews, withModels);  
                    }
                    catch (ArgumentOutOfRangeException)
                    {
                        failedConnections.Add(c);
                        ((IConnectionView) createdViews[c]).Model.Points.Add(new Point());
                        ((IConnectionView) createdViews[c]).Model.Points.Add(new Point(20, 20));
                    }
                }
            });
            if (failedConnections.Any())
            {
                StringBuilder sb = new StringBuilder("FailedConnections:");
                failedConnections.ForEach(fc => sb.AppendLine(fc.ToMMString()));
                MessageBox.Show(sb.ToString());
            }
            return createdViews.Values;
        }

        public static void CreateViews(GraphElement element, PaintingCanvas paintingCanvas, 
            Dictionary<GraphElement, IObjectView> createdViews, List<GraphElement> elements, bool innerGraph = false)
        {
            dynamic node = element as MachinationsNode;
            var connection = element as MachinationsConnection;
            var component = element as IUserComponentInstance;
            //var inOut = element as UserComponentInOut;
            var inOutv2 = element as InOutBase;
            if (node != null)
            {
                INodeView<MachinationsNode> view = GraphSymbol.GetViewFromClass(node);
                createdViews.Add(node, view);
                //gNode.Symbol = symbol;
                paintingCanvas.AddNodeView(view);
                view.Position = node.Position;
                ((MachinationsNode)node).Simlinks.ForEach(simlink =>
                {
                    simlink.FinishLoading(elements);                        //asi se bude chovat divne pri Addu?
                    //paintingCanvas.AddObjectView(view.CreateSimlinkView(simlink));
                });
            }
            else if (connection != null)
            {
                IConnectionView view = GraphConnectionType.GetViewFromClass(connection);
                createdViews.Add(connection, view);
                paintingCanvas.AddConnectionView(view);
            }
            else if (component != null)
            {
                UserComponentView view = new UserComponentView(component);
                createdViews.Add((GraphElement)component, view);
                //gNode.Symbol = symbol;
                paintingCanvas.AddComponentView(view);
                view.Position = component.Position;
            }
            else if (inOutv2 != null)
            {
                InOutView view;
                if (inOutv2 is InnerInOut)
                {
                    view = new InOutInnerView((InnerInOut)inOutv2);
                    paintingCanvas.AddInOutView(view);
                }
                else
                {
                    OuterInOut outer = (OuterInOut)inOutv2;
                    view =
                        ((UserComponentView)createdViews[(GraphElement)outer.UserComponent.GetInstance(outer.InstanceNumber)]).InOutOuterViews.Single(
                            v => v.Model == outer);
                }
                createdViews.Add(inOutv2, view);
            }
        }


        public static void ConnectConnections(MachinationsConnection connection, List<GraphElement> elements,
            Dictionary<GraphElement, IObjectView> createdViews = null, bool withModels = true, bool addNewPoints = true)
        {
            bool withViews = createdViews != null && createdViews.Any();
            IConnectionView view = withViews ? (IConnectionView)createdViews[connection] : null;
            if (connection.EndId != -1 && connection.End == null) connection.End = elements[connection.EndId];           //kvuli userComponente je to potreba pred pripojenim startViewu
            if (connection.StartId != -1 || connection.Start != null)
            {
                var start = connection.Start ?? elements[connection.StartId];
                if (withViews)
                {
                    //v grafu nejsou ulozeny koncove body hran, to jsou nody. Pri nacitani tedy pridam nove. 
                    //Pokud ale kreslim existujici koponentu, pridat je uz nechci
                    if(addNewPoints)
                        view.Model.Points.Insert(0, new Point());
                    createdViews[start].AddConnection(view, true, withModels);
                }
                else
                    connection.ConnectToElement(start, true);
            }
            if (connection.EndId != -1 || connection.End != null)
            {
                var end = connection.End ?? elements[connection.EndId];
                if (withViews)
                {
                    if (addNewPoints)
                        view.Model.Points.Add(new Point());
                    createdViews[end].AddConnection(view, false, withModels);
                    //view.RecalculateAfterLoaded();
                    view.Loaded();          //je to tu nutne?
                }
                else
                    connection.ConnectToElement(end, false);
            }
            if (withViews)
            {
                view.RecalculateAfterLoaded();
                var points = view.Model.Points;
                if (points.Last().Equals(points[points.Count - 2]))
                    points.Remove(points.Last());
                if(points[0].Equals(points[1]))
                    points.RemoveAt(0);
            }
                
        }

        private static void LoadGraphElements(XmlElement baseElement, Graph graph, out List<GraphElement> elements, out List<IUserComponentSchema> components)
        {
            elements = new List<GraphElement>();
            components = new List<IUserComponentSchema>();
            XmlNode node = baseElement.FirstChild;
            Dictionary<string, XmlElement> loadedComponents = new Dictionary<string, XmlElement>();
            while (node != null)
            {
                var element = node as XmlElement;
                if (element != null)
                {
                    if (node.Name == "node")
                    {
                        try
                        {
                            AddNodeXml(element, elements, graph);
                        }
                        catch (UnsupportedElementException ex)
                        { 
                            //neimplementovany node, je potreba neco vyplnit kvuli idckum
                            elements.Add(new CompatibilityNode());
                        }
                    }
                    else if (node.Name == "connection")
                    {
                        AddConnectionXml(element, elements, graph);
                    }
                    else if (node.Name == "ComponentsTemplates")
                    {
                        foreach (XmlElement componentElement in element.ChildNodes)
                        {
                            string name = componentElement.GetAttribute("name");
                            if (componentElement.HasAttribute("created"))
                            {
                                DateTime dateTime = DateTime.Parse(componentElement.GetAttribute("created"));
                                loadedComponents.Add(name+dateTime, componentElement);
                            }
                            else
                                loadedComponents.Add(name, componentElement);
                        }

                    }
                    else if (node.Name == "UserComponent")
                    {
                        string name = element.GetAttribute("name");
                        XmlElement ucElement;
                        if (element.HasAttribute("created"))
                        {
                            DateTime dateTime = DateTime.Parse(element.GetAttribute("created"));
                            ucElement = loadedComponents[name + dateTime];
                        }
                        else
                            ucElement = loadedComponents[name];
                        UserComponent uc = new UserComponent(ucElement) {Graph = graph};
                        uc.ReadXml(element);
                        components.Add(uc);
                        elements.Add(uc);
                    }
                    else if (node.Name.Equals("cardinstance", StringComparison.CurrentCultureIgnoreCase))
                    {
                        string name = element.GetAttribute("name");
                        int id = int.Parse(element.GetAttribute("CardsId"));
                        var cards = components.SingleOrDefault(c => c.Name == name && c.UcId == id);
                        if (cards == null)
                        {
                            if (element.HasAttribute("created"))
                            {
                                DateTime dateTime = DateTime.Parse(element.GetAttribute("created"));
                                cards = new Cards(loadedComponents[name+dateTime]);
                            }
                            else
                                cards = new Cards(loadedComponents[name]);
                            cards.Graph = graph;
                            cards.UcId = id;
                            components.Add(cards);
                        }
                        CardInstance ci = new CardInstance((Cards)cards);
                        ci.ReadXml(element);
                        elements.Add(ci);
                    }
                    //else if (node.Name.ToLower() == "ucinout")
                    //{
                    //    // outer in out
                    //    if (element.HasAttribute("UcNumber"))
                    //    {
                    //        int componentId = int.Parse(element.GetAttribute("UcNumber"));
                    //        if (graph is UserComponentGraph)
                    //            AddNodeXml(element, elements, graph);
                    //        else
                    //        {
                    //            int componentPosition = int.Parse(element.GetAttribute("UcPosition"));
                    //            elements.Add(components[componentId].InAndOuts[componentPosition]);
                    //        }
                    //    }
                    //    else
                    //    {
                    //        AddNodeXml(element, elements, graph);
                    //    }
                    //}
                    else if (node.Name.ToLower() == "innerinout")
                    {
                        AddNodeXml(element, elements, graph);
                    }
                    else if (node.Name.ToLower() == "outerinout")
                    {
                        AddNodeXml(element, elements, graph);
                    }
                    else throw new NotImplementedException("neznamy node" + node.Name);
                }

                node = node.NextSibling;
            }
        }

        private static void AddNodeXml(XmlElement element, List<GraphElement> elements, Graph graph)
        {
            var gNode = ReadNodeXml(element);
            gNode.Graph = graph;
            elements.Add(gNode);
        }

        private static void AddConnectionXml(XmlElement element, List<GraphElement> elements, Graph graph)
        {
            var connection = ReadConnectionXml(element);
            connection.Graph = graph;
            //nenapojene connectiony dam na kraj
            if (connection.StartId == -1 && connection.EndId == -1 && connection.Points.Count < 2)
            {
                connection.Points.Add(new Point());
                if(connection.Points.Count == 1)
                    connection.Points.Add(new Point(20, 20));
            }
            elements.Add(connection);
        }

        public static GraphNode ReadNodeXml(XmlElement element)
        {
            GraphSymbol symbol = MachinationsGrammar.GetSymbol(element.GetAttribute("symbol"));
            GraphNode gNode = GraphSymbol.GetNodeFromClass(symbol.NodeClass);
            gNode.ReadXml(element);
            return gNode;
        }

        private static MachinationsConnection ReadConnectionXml(XmlElement element)
        {
            GraphConnectionType type = MachinationsGrammar.GetConnectionType(element.GetAttribute("type")); //child.@type);
            MachinationsConnection connection = GraphConnectionType.GetConnectionFromClass(type.ConnectionClass);
            connection.ReadXml(element);
            return connection;
        }

        public static GraphElement ReadElement(XmlElement element)
        {
            if (element.Name == "node")
            {
                return ReadNodeXml(element);
            }
            if (element.Name == "connection")
            {
                return ReadConnectionXml(element);
            }
            throw new ArgumentException();
        }

        public static bool IsFunctionKey(Key k)
        {
            switch (k)
            {
                case Key.F1:
                case Key.F2:
                case Key.F3:
                case Key.F4:
                case Key.F5:
                case Key.F6:
                case Key.F7:
                case Key.F8:
                case Key.F9:
                case Key.F10:
                case Key.F11:
                case Key.F12:
                    return true;
                default:
                    return false;
            }
        }
    }
}
