using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using Machinations.Annotations;
using Machinations.FromFlash.graph;
using Machinations.FromFlash.machinations.model;
using Machinations.GUI;
using Machinations.UserComponents;
using static System.Windows.Controls.Panel;

namespace Machinations.WPF
{
    /// <summary>
    /// Interaction logic for RealCanvas.xaml
    /// class, that handles visual representation of elements by having their Views. also handles user interaction
    /// </summary>
    public partial class PaintingCanvas : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        public event Action<MachinationsNode> ModelClick;
        public Action ComponentsChanged;
        public Action CreatedCardsChanged;

        private Point m_selectRectangleDown;
        private readonly Rectangle m_selectRectangle;
        private List<IObjectView> m_objectsUnderMouse;

        private readonly List<IObjectView> m_selectedObjects;
        /// <summary>
        /// Connects UiElements in Canvas with IObjectViews in RealCanvas
        /// </summary>
        private readonly Dictionary<UIElement, IObjectView> m_views;
        private readonly List<IObjectView> m_draggedElements;
        private Point m_dragLast;
        private Graph m_graph;
        private GraphControl m_graphControl;
        private ComponentsSettings m_componentsSettings;
        private object m_insertedObject;
        private IObjectView m_highlighted;

        private IComponentView m_editedComponent;

        private double m_canvasWidth;
        private double m_canvasHeight;
        private int m_zoomValue;
        private List<IUserComponentSchema> m_schemas;
        private bool m_editDisabled;
        private int m_scrollViewerWidth;
        private int m_scrollViewerHeight;

        private static readonly List<GraphElement> m_copies = new List<GraphElement>();


        public PaintingCanvas()
        {
            m_views = new Dictionary<UIElement, IObjectView>();
            InitializeComponent();
            m_selectRectangle = new Rectangle()
            {
                Stroke = new SolidColorBrush(Colors.Coral),
                StrokeThickness = 2,
                Visibility = Visibility.Collapsed
            };
            m_objectsUnderMouse = new List<IObjectView>();
            m_selectedObjects = new List<IObjectView>();
            RealCanvas.Children.Add(m_selectRectangle);
            m_draggedElements = new List<IObjectView>();
            //m_copies = new List<GraphElement>();
            var a = SystemParameters.PrimaryScreenWidth;
            var b = SystemParameters.PrimaryScreenHeight;
            ScrollViewerWidth =(int) a/10 * 8 ;     //900;
            ScrollViewerHeight = (int)b/10 * 9;      //900;
        }

        public void Initialize(Graph graph, GraphControl graphControl, ComponentsSettings componentSettings)
        {
            m_graph = graph;
            var machinationsGraph = graph as MachinationsGraph;
            if (machinationsGraph != null)
            {
                CanvasWidth = machinationsGraph.Width;
                CanvasHeight = machinationsGraph.Height;
                machinationsGraph.SizeChanged += OnSizeChanged;
                OnPropertyChanged(nameof(ViewBoxWidth));
                OnPropertyChanged(nameof(ViewBoxHeight));
            }
            else
            {
                CanvasWidth = 800;
                CanvasHeight = 600;
            }

            m_graphControl = graphControl;
            m_componentsSettings = componentSettings;
        }


        public double CanvasWidth
        {
            get
            {
                var graph = m_graph as MachinationsGraph;
                return graph?.Width ?? m_canvasWidth;
            }
            set
            {
                var graph = m_graph as MachinationsGraph;
                if (graph != null)
                {
                    if (graph.Width != value)
                    {
                        double d = Math.Max(value, 300);
                        d = Math.Min(d, 3000);
                        graph.Width = Convert.ToInt32(d);
                        OnPropertyChanged(nameof(CanvasWidth));
                        OnPropertyChanged(nameof(ViewBoxWidth));
                    }
                }
                else
                {
                    if (m_canvasWidth != value)
                    {
                        double d = Math.Max(value, 300);
                        d = Math.Min(d, 3000);
                        m_canvasWidth = d;
                        OnPropertyChanged(nameof(CanvasWidth));
                        OnPropertyChanged(nameof(ViewBoxWidth));
                    }
                }
            }
        }

        public double CanvasHeight
        {
            get
            {
                var graph = m_graph as MachinationsGraph;
                return graph?.Height ?? m_canvasHeight;
            }
            set
            {
                var graph = m_graph as MachinationsGraph;
                if (graph != null)
                {
                    double d = Math.Max(value, 300);
                    d = Math.Min(d, 3000);
                    graph.Height = Convert.ToInt32(d);
                    OnPropertyChanged(nameof(CanvasHeight));
                    OnPropertyChanged(nameof(ViewBoxHeight));
                }
                else
                {
                    if (m_canvasHeight != value)
                    {
                        double d = Math.Max(value, 300);
                        d = Math.Min(d, 3000);
                        m_canvasHeight = d;
                        OnPropertyChanged(nameof(CanvasHeight));
                        OnPropertyChanged(nameof(ViewBoxHeight));
                    }
                }
            }
        }

        public List<IObjectView> SelectedObjects
        {
            get { return m_selectedObjects; }
        }

        public object InsertedObject
        {
            get { return m_insertedObject; }
            set { m_insertedObject = value; }
        }

        public int ZoomValue
        {
            get { return m_zoomValue; }
            set
            {
                if (m_zoomValue != value)
                {
                    m_zoomValue = value;
                    OnPropertyChanged(nameof(ViewBoxWidth));
                    OnPropertyChanged(nameof(ViewBoxHeight));
                }
            }
        }

        //public int Zoom
        //{
        //    get { return Convert.ToInt32(CanvasWidth - m_zoomValue * CanvasWidth/15) ; }
        //}

        public int ViewBoxHeight
        {
            get { return Convert.ToInt32(CanvasHeight / (1 + 0.3 * m_zoomValue)); }
        }

        public int ViewBoxWidth
        {
            get { return Convert.ToInt32(CanvasWidth / (1 + 0.3 * m_zoomValue)); }
        }

        public bool EditDisabled
        {
            get { return m_editDisabled; }
            set { m_editDisabled = value; }
        }

        public List<IUserComponentSchema> Schemas
        {
            get { return m_schemas; }
            set { m_schemas = value; }
        }


        //jen pro editor komponent
        public MachinationsGraph BaseGraph { get; set; }

        public MachinationsGraph AlwaysMachinationsGraph
        {
            get { return (m_graph is MachinationsGraph) ? (MachinationsGraph)m_graph : BaseGraph; }
        }

        public int ScrollViewerWidth
        {
            get { return m_scrollViewerWidth; }
            set
            {
                value = Math.Min(value, 1200);
                value = Math.Max(value, 600);
                m_scrollViewerWidth = value;
                OnPropertyChanged(nameof(ScrollViewerWidth));
            }
        }

        public int ScrollViewerHeight
        {
            get { return m_scrollViewerHeight; }
            set
            {
                value = Math.Min(value, 1200);
                value = Math.Max(value, 600);
                m_scrollViewerHeight = value;
                OnPropertyChanged(nameof(ScrollViewerHeight));
                OnPropertyChanged();
            }
        }

        public void Delete()
        {
            if (m_draggedElements.Count == 1)
            {
                var connection = m_draggedElements.First() as IConnectionView;
                if (connection != null)
                {
                    if (connection.Model.Points.Count > 2)
                    {
                        connection.RemovePoint(true);
                    }
                    else
                    {
                        DeleteObject(connection);
                        m_draggedElements.Clear();
                        CheckSelect();
                    }
                }
            }
            else
            {
                //var components = SelectedObjects.Where(o => o is UserComponentView).ToList();
                //components.ForEach(DeleteObject);
                //var rest = SelectedObjects.Where(o => !(o is UserComponentView)).ToList();
                //rest.ForEach(DeleteObject);
                SelectedObjects.Where(so => !(so is InOutOuterView)).ToList().ForEach(DeleteObject);
                CreatedCardsChanged?.Invoke();
                SelectedObjects.Clear();
                m_draggedElements.Clear();
                UnselectObjects();
            }
        }

        public void Reset(Graph graph = null)
        {
            m_views.Clear();
            m_objectsUnderMouse = new List<IObjectView>();
            RealCanvas.Children.Clear();
            RealCanvas.Children.Add(m_selectRectangle);
            if (graph != null)
                m_graph = graph;
            ZoomValue = 0;
        }

        public void AddNodeView(INodeView<MachinationsNode> nodeView)
        {
            nodeView.InitShape();
            AddObjectView(nodeView);
            if (nodeView is ILinkableObject)
                ((ILinkableObject)nodeView).SimlinkViews.ToList().ForEach(AddObjectView);
        }

        public void AddInOutView(InOutView view)
        {
            AddObjectView(view);
        }

        public void AddComponentView(UserComponentView componentView)
        {
            AddObjectView(componentView);
            var inOutViews = componentView.InOutOuterViews.ToList();
            inOutViews.ForEach(AddObjectView);

            componentView.InOutChanged += () =>
            {
                inOutViews.ForEach(DeleteObject);
                inOutViews = componentView.InOutOuterViews.ToList();
                //inOutElements.ForEach(element => RealCanvas.Children.Add(element));
            };
        }

        public void AddConnectionView(IConnectionView connectionView)
        {
            AddObjectView(connectionView);
        }

        public RenderTargetBitmap CreateBitmap()
        {
            Size size = new Size(Convert.ToInt32(CanvasWidth), Convert.ToInt32(CanvasHeight));
            // Measure and arrange the surface
            // VERY IMPORTANT
            RealCanvas.Measure(size);
            RealCanvas.Arrange(new Rect(size));
            RenderTargetBitmap renderBitmap = new RenderTargetBitmap((int)size.Width, (int)CanvasHeight,
                      96d, 96d, PixelFormats.Pbgra32);
            renderBitmap.Render(RealCanvas);
            return renderBitmap;
        }

        private void DeleteObject(IObjectView obj)
        {
            var withSimlinks = obj as ILinkableObject;
            if (withSimlinks != null)
            {
                foreach (var simlinkView in withSimlinks.SimlinkViews)
                {
                    DeleteObject(simlinkView);
                }
            }
            var ucv = obj as UserComponentView;
            if (ucv != null)
            {
                //ucv.Model.Zip();
                ucv.InOutOuterViews.ForEach(view =>
                {
                    DeleteObject(view);
                    m_graph.Elements.Remove(view.Model);
                });
                if (ucv.Model.Type == UserComponentType.Card)
                {
                    var cards = (Cards)ucv.Model.GetUcScheme();
                    cards.Instances.Remove(ucv.Model);
                    if (!cards.Instances.Any())
                    {
                        m_graph.UserComponents.Remove(cards);
                        cards.Dispose();
                    }
                        
                }
                else
                    m_graph.UserComponents.Remove(ucv.Model.GetUcScheme());
            }
            var innerInOut = obj as InOutInnerView;
            if (innerInOut != null)
            {
                innerInOut.Model.UserComponent.RemoveInOut(innerInOut.Model);
            }
            //neslo by to jinak?
            m_views.Where(pair => pair.Value == obj).ToList().ForEach(v => m_views.Remove(v.Key));
            obj.Dispose();
            obj.GetAllUiElements().ForEach(element => RealCanvas.Children.Remove(element));
        }

        private void PaintingCanvas_OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            try
            {
                if (AlwaysMachinationsGraph.Running)
                {
                    if (AlwaysMachinationsGraph.Ended)
                    {
                        return;
                    }
                    LoadElementsUnderMouse();
                    var register =
                        m_objectsUnderMouse.OfType<RegisterView>()
                            .FirstOrDefault();
                    if (register != null)
                    {
                        register.Click(Mouse.GetPosition(RealCanvas));
                    }
                    var nodeUnderMouse =
                        m_objectsUnderMouse.OfType<IObjectView<MachinationsNode>>()
                            .FirstOrDefault(node => node.Model.ActivationMode == ActivationMode.Interactive);
                    if (nodeUnderMouse != null && !nodeUnderMouse.Model.Inhibited)
                    {
                        ModelClick?.Invoke(nodeUnderMouse.Model);
                        nodeUnderMouse.Model.Click();
                        return;
                    }
                    var component = m_objectsUnderMouse.OfType<IComponentView>().FirstOrDefault();
                    var clickableNodes = component?.Model.PresentedElements.OfType<MachinationsNode>()
                        .Where(n => n.ActivationMode == ActivationMode.Interactive).ToList();
                    if (clickableNodes != null && clickableNodes.Count(cn => !cn.Inhibited) == 1)
                    {
                        var node = clickableNodes.Single(cn => !cn.Inhibited);
                        node.Click();
                        ModelClick?.Invoke(node);
                    }
                }
                else
                {
                    if (m_insertedObject == null)
                    {
                        CheckSelect();
                        SelectModeLeftDown();
                        return;
                    }
                    if (m_insertedObject is MachinationsConnection)
                    {
                        ConnectionModeLeftDown();
                        return;
                    }

                    if (m_insertedObject is MachinationsNode)
                    {
                        NodeModeLeftDown();
                        return;
                    }
                    if (m_insertedObject is IUserComponentSchema)
                    {
                        ComponentModeLeftDown();
                        return;
                    }
                }
            }

            catch (Exception)
            {
                MessageBox.Show("Akce se nezdařila.", "Error");
            }
        }

        private void UIElement_OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            if (m_graphControl.ToggledButton == null || (m_graph as MachinationsGraph)?.Running == true)
            {
                return;
            }

            if (m_graphControl.SelectedTbIsSelect())
            {
                try
                {
                    SelectModeLeftUp();
                }
                catch (InvalidOperationException)
                {
                    MessageBox.Show("Nepodařilo se provést výběr", "Error");
                }
                catch (ArgumentException)
                {
                    MessageBox.Show("Hranu se nepodarilo pripojit", "Error");
                }
            }
        }

        private void PaintingCanvas_OnMouseMove(object sender, MouseEventArgs e)
        {
            try
            {
                if ((m_graph as MachinationsGraph)?.Running != true)
                {
                    if (m_selectRectangle.Visibility == Visibility.Visible)
                    {
                        Point mousePos = Mouse.GetPosition(RealCanvas);
                        if (m_selectRectangleDown.X < mousePos.X)
                        {
                            Canvas.SetLeft(m_selectRectangle, m_selectRectangleDown.X);
                            m_selectRectangle.Width = mousePos.X - m_selectRectangleDown.X;
                        }
                        else
                        {
                            Canvas.SetLeft(m_selectRectangle, mousePos.X);
                            m_selectRectangle.Width = m_selectRectangleDown.X - mousePos.X;
                        }

                        if (m_selectRectangleDown.Y < mousePos.Y)
                        {
                            Canvas.SetTop(m_selectRectangle, m_selectRectangleDown.Y);
                            m_selectRectangle.Height = mousePos.Y - m_selectRectangleDown.Y;
                        }
                        else
                        {
                            Canvas.SetTop(m_selectRectangle, mousePos.Y);
                            m_selectRectangle.Height = m_selectRectangleDown.Y - mousePos.Y;
                        }
                    }
                    else
                    {
                        LoadElementsUnderMouse();
                        var forHighlight =
                            m_objectsUnderMouse.Except(new[] { m_draggedElements.FirstOrDefault() }).ToList();
                        if (forHighlight.Count == 1)
                        {
                            if (m_highlighted != null && m_highlighted != forHighlight.First())
                            {
                                m_highlighted.Highlighted = ViewHighlightMode.None;
                            }
                            m_highlighted = forHighlight.First();
                            m_highlighted.Highlighted = ViewHighlightMode.Highlighted;
                        }
                        else
                        {
                            if (m_highlighted != null)
                            {
                                m_highlighted.Highlighted = ViewHighlightMode.None;
                                m_highlighted = null;
                            }
                        }
                        if (m_draggedElements.Any())
                        {
                            Point p = Mouse.GetPosition(RealCanvas);
                            if (m_draggedElements.Count == 1 && m_draggedElements.First() is IConnectionView)
                            {
                                IConnectionView connection = (IConnectionView)m_draggedElements.First();
                                connection.MovePoint(p);
                                m_objectsUnderMouse.Remove(connection);
                            }
                            else
                            {
                                Vector difference = new Vector(p.X - m_dragLast.X, p.Y - m_dragLast.Y);
                                m_draggedElements.ForEach(
                                    draggedElement => draggedElement.Move(difference.X, difference.Y));
                                m_dragLast = p;
                            }
                        }
                    }
                }
            }
            catch (Exception)
            {
                MessageBox.Show("Posun se nezdaril");
                if (SelectedObjects.Any())
                {
                    UnselectObjects();
                }
                if (m_draggedElements.Any())
                {
                    m_draggedElements.Clear();
                }
            }

        }

        private void RealCanvas_OnMouseRightButtonDown(object sender, MouseButtonEventArgs e)
        {
            CreateContextMenu();
            //if (m_insertedObject == null)
            //{
            //    CheckSelect();
            //}
        }

        private void CreateContextMenu()
        {
            ContextMenu menu = new ContextMenu();
            LoadElementsUnderMouse();
            if (m_objectsUnderMouse.Count == 1)
            {
                bool editDisabled;
                if (m_graph is MachinationsGraph)
                    editDisabled = ((MachinationsGraph) m_graph).Running;
                else
                    editDisabled = EditDisabled;
                var objectView = m_objectsUnderMouse.Single();
                var nodeView = objectView as INodeView<MachinationsNode>;
                if (nodeView != null && !editDisabled)
                {
                    var menuItem = new MenuItem()
                    {
                        Header = "Create simlink",
                    };
                    menuItem.Click += (sender, routedEventArgs) =>
                    {
                        SimlinkView simlinkView = nodeView.CreateSimlinkView();
                        AddObjectView(simlinkView);
                    };
                    menu = new ContextMenu();
                    menu.Items.Add(menuItem);
                }
                var sv = objectView as SimlinkView;
                if (sv != null && !editDisabled)
                {
                    var menuItem = new MenuItem()
                    {
                        Header = "Create simlink",
                    };
                    menuItem.Click += (sender, routedEventArgs) =>
                    {
                        SimlinkView simlinkView = sv.CreateSimlinkView();
                        AddObjectView(simlinkView);
                    };
                    menu = new ContextMenu();
                    menu.Items.Add(menuItem);
                }
                else if (m_objectsUnderMouse.Single() is IComponentView)
                {
                    menu = CreateMenuForUserComponent(editDisabled ? "View component" : "Edit component");
                }
            }
            else if(m_graph is UserComponentGraph && !m_editDisabled)
            {
                Point position = Mouse.GetPosition(RealCanvas);
                var menuItem = new MenuItem()
                {
                    Header = "Add InOut",
                };
                menuItem.Click += (sender, routedEventArgs) =>
                {
                    InOutInnerView view = new InOutInnerView(((UserComponentGraph)m_graph).Component.CreateInOut(position));
                    AddInOutView(view);
                };
                menu.Items.Add(menuItem);
            }
            RealCanvas.ContextMenu = menu;
        }

        private ContextMenu CreateMenuForUserComponent(string header)
        {
            var menuItem = new MenuItem()
            {
                Header = header,
            };
            m_editedComponent = (IComponentView) m_objectsUnderMouse.Single();
            menuItem.Click += EditComponent;
            ContextMenu menu = new ContextMenu();
            menu.Items.Add(menuItem);
            return menu;
        }

        private void EditComponent(object sender, RoutedEventArgs e)
        {
            //var graph = (MachinationsGraph) m_graph;
            try
            {
                IUserComponentSchema c = m_editedComponent.Model.GetUcScheme();
                bool editEnabled = m_graph is MachinationsGraph ? !((MachinationsGraph) m_graph).Running : !EditDisabled;
                UserComponentEditor editor = new UserComponentEditor(c,  editEnabled,
                    m_editedComponent.Model.Name,
                    Schemas.OfType<UserComponent>().Where(uc => !c.SameSchema(uc)), AlwaysMachinationsGraph);
                if (editEnabled)
                {
                    editor.Activate();
                    if (editor.ShowDialog() == true)
                    {
                        switch (editor.CloseStatus)
                        {
                            case UcEditorClosedState.None:
                                return;
                            case UcEditorClosedState.SaveAsNew:
                            {
                                var editedSchema = editor.EditedSchema;
                                editedSchema.GenerateNewVersion();
                                editedSchema.SaveToComponentsFolder();
                                editedSchema.UpdateOuters();
                                ReplaceOneSchema(editor.EditedSchema, m_editedComponent.Model.GetUcScheme());
                                ComponentsChanged?.Invoke();
                            }
                                break;
                            case UcEditorClosedState.OverwriteAll:
                            {
                                var editedSchema = editor.EditedSchema;
                                var oldSchema = m_editedComponent.Model.GetUcScheme();
                                oldSchema.DeleteXmlTemplate();
                                var toReplace2 =
                                   m_graph.UserComponents.Where(
                                       uc => uc.SameSchema(editedSchema)).ToList();
                                editedSchema.Created = DateTime.Now;
                                editedSchema.SaveToComponentsFolder();
                                foreach (var userComponentSchema in toReplace2)
                                {
                                    if (userComponentSchema.Equals(oldSchema))
                                    {
                                        editedSchema.UpdateOuters();
                                        ReplaceOneSchema(editedSchema, userComponentSchema);
                                    }
                                    else
                                    {
                                        var clone = editedSchema.Clone();
                                        clone.UpdateOuters();
                                        ReplaceOneSchema(clone, userComponentSchema);
                                    }
                                }
                                ComponentsChanged.Invoke();
                            }
                                break;
                            case UcEditorClosedState.Delete:
                            {
                                var toDelete = m_views.Values.OfType<IComponentView>()
                                    .Where(cv => cv.Model.GetUcScheme().SameSchema(m_editedComponent.Model.GetUcScheme())).ToList();
                                toDelete.ForEach(tr =>
                                {
                                    tr.BeforeDelete();
                                    DeleteObject(tr);
                                });
                                editor.EditedSchema.DeleteXmlTemplate();
                                ComponentsChanged?.Invoke();
                            }
                                break;
                            default:
                                throw new ArgumentOutOfRangeException();
                        }
                        if (editor.EditedSchema.Type == UserComponentType.Card)
                            CreatedCardsChanged.Invoke();
                    }
                }
                else
                {
                    editor.Show();
                }
            }
            catch (Exception)
            {
                // ignored
            }
        }

        private void ReplaceOneSchema(IUserComponentSchema newComponent, IUserComponentSchema oldSchema)
        {
            var instances = new HashSet<IUserComponentInstance>(oldSchema.GetInstances());
            var toReplace = m_views.Values.OfType<IComponentView>().Where(v => instances.Contains(v.Model)).ToList();
            foreach (var componentView in toReplace)
            {
                var newView = AddComponentInstanceToGraph(componentView.Position,
                    newComponent.GetNewUcInstance(componentView.Model));
                bool replace = newView.Model.IsSimilar(componentView.Model);
                UserComponentView replaceView = replace ? newView : null;
                componentView.BeforeDelete(replaceView);
                DeleteObject(componentView);
            }
        }

        private UserComponentView AddComponentInstanceToGraph(Point position, IUserComponentInstance instance)
        {
            instance.AppendToGraph(m_graph);
            var newView = new UserComponentView(instance);
            AddComponentView(newView);
            newView.Position = position;
            return newView;
        }


        private void CheckSelect()
        {
            if (m_graphControl?.SelectButton.IsChecked != true)
            {
                m_graphControl.SelectButton.IsChecked = true;
                m_graphControl.GraphButtonChoose(m_graphControl.SelectButton);
            }
        }

        private void ConnectionModeLeftDown()
        {
            if (m_draggedElements.Count == 1 && m_draggedElements.First() is IConnectionView) //
            {
                IConnectionView line = (IConnectionView)m_draggedElements.First();
                Point p = Mouse.GetPosition(RealCanvas);
                LoadElementsUnderMouse();
                IObjectView obj =
                    m_objectsUnderMouse.Except(new[] { line }).FirstOrDefault();
                if (obj == null)
                {
                    line.SetPoint(p);
                }
                else
                {
                    try
                    {
                        obj.AddConnection(line, false);
                        line.RecalculateEndPoint(false);
                    }
                    catch (Exception e)
                    {
                        if (e is ArgumentException || e is NotImplementedException)
                        {
                            DeleteObject(line);
                            m_draggedElements.Clear();
                            MessageBox.Show("Cannot add this connection. " + Environment.NewLine + e.Message, "Error");
                            return;
                        }
                    }

                    m_draggedElements.Clear();
                }
            }
            else
            {
                Point p = Mouse.GetPosition(RealCanvas);
                LoadElementsUnderMouse();
                IObjectView startElement = m_objectsUnderMouse.FirstOrDefault();
                IConnectionView connection =
                    GraphConnectionType.GetViewFromClass(((MachinationsConnection)m_insertedObject), p);
                m_insertedObject =
                    GraphConnectionType.GetConnectionFromClass(((GraphConnection)m_insertedObject).ConnectionClass);
                m_graph.AddElement(connection.Model);
                if (startElement != null)
                {
                    try
                    {
                        startElement.AddConnection(connection, true);
                        connection.RecalculateStartPoint();
                    }
                    catch (Exception e)
                    {
                        if (e is ArgumentException || e is NotImplementedException)
                        {
                            DeleteObject(connection);
                            //MessageBox.Show("Cannot add this connection." + Environment.NewLine + e.Message, "Error");
                            m_graphControl.GraphButtonChoose(m_graphControl.SelectButton);
                            return;
                        }
                    }

                }

                AddConnectionView(connection);
                m_draggedElements.Add(connection);
                SelectObjects(new[] { connection });
            }
        }

        private void NodeModeLeftDown()
        {
            LoadElementsUnderMouse();
            INodeView<MachinationsNode> node = m_objectsUnderMouse.OfType<INodeView<MachinationsNode>>().FirstOrDefault();
            if (node != null)
            {
                SelectFromNoSelectMode(node);
                return;
            }
            Point p = Mouse.GetPosition(RealCanvas);
            dynamic cloned = ((GraphElement)m_insertedObject).Clone();
            INodeView<MachinationsNode> nodeView = GraphSymbol.GetViewFromClass(cloned);

            //vytvoreni modelu toho sameho typu
            //m_insertedObject = GraphSymbol.GetNodeFromClass(((MachinationsNode)m_insertedObject).NodeClass);

            m_graph.AddElement(nodeView.Model);
            AddNodeView(nodeView);
            nodeView.Position = p;
            SelectObjects(new[] { nodeView });
        }

        private void ComponentModeLeftDown()
        {
            LoadElementsUnderMouse();
            IComponentView component = m_objectsUnderMouse.OfType<IComponentView>().FirstOrDefault();
            if (component != null)
            {
                SelectFromNoSelectMode(component);
                return;
            }
            Point p = Mouse.GetPosition(RealCanvas);
            var schema = (IUserComponentSchema) m_insertedObject;
            if (!m_graph.UserComponents.Contains(schema))
            {
                IUserComponentSchema copy = ((IUserComponentSchema)m_insertedObject).Clone();
                copy.UpdateOuters();
                AddComponentInstanceToGraph(p, copy.GetNewUcInstance());
                if (((IUserComponentSchema)m_insertedObject).Type == UserComponentType.Card)
                {
                    CreatedCardsChanged.Invoke();
                }
            }
            else
            {
                AddComponentInstanceToGraph(p, schema.GetNewUcInstance());
            }
            
        }

        private void SelectFromNoSelectMode(IObjectView view)
        {
            //m_graphControl.SelectButton.IsChecked = true;
            m_graphControl.GraphButtonChoose(m_graphControl.SelectButton);
            m_draggedElements.Add(view);
            SelectObjects(new[] { view });
            m_dragLast = Mouse.GetPosition(RealCanvas);
        }

        private void AddObjectView(IObjectView view)
        {
            m_views.Add(view.UiElement, view);
            foreach (var uiElement in view.GetAllUiElements())
            {
                RealCanvas.Children.Add(uiElement);
                SetUiElementDepth(uiElement);
            }
        }

        private static void SetUiElementDepth(UIElement uiElement)
        {
            TextBlock textBlock = uiElement as TextBlock;
            if (textBlock != null)
            {
                SetZIndex(uiElement, -1);
                return;
            }
            Polyline line = uiElement as Polyline;
            if (line != null)
            {
                SetZIndex(uiElement, -100);
                return;
            }
            UserComponentUiElement component = uiElement as UserComponentUiElement;
            if (component != null)
            {
                SetZIndex(uiElement, -1000);
                return;
            }
            SetZIndex(uiElement, -200);
        }

        private void SelectModeLeftDown()
        {
            LoadElementsUnderMouse();
            if (m_objectsUnderMouse.Any() && (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)))
            {
                if (m_objectsUnderMouse.All(SelectedObjects.Contains))
                {
                    m_objectsUnderMouse.ForEach(o =>
                    {
                        SelectedObjects.Remove(o);
                        o.Selected = ViewSelectionMode.None;
                    });
                }
                else
                {
                    SelectedObjects.AddRange(m_objectsUnderMouse);
                    m_objectsUnderMouse.ForEach(o => o.Selected = ViewSelectionMode.Selected);
                    m_componentsSettings.HideSettings(SelectedObjects);
                }
                //m_draggedElements.AddRange(m_objectsUnderMouse);
                return;
            }
            IConnectionView connection = m_objectsUnderMouse.OfType<IConnectionView>().FirstOrDefault();
            //INodeView<MachinationsNode> node = m_objectsUnderMouse.OfType<INodeView<MachinationsNode>>().FirstOrDefault();
            //IComponentView component = m_objectsUnderMouse.OfType<IComponentView>().FirstOrDefault();
            PointObjectView pointObject = m_objectsUnderMouse.OfType<PointObjectView>().FirstOrDefault();

            if (connection != null)
            {
                var point = Mouse.GetPosition(RealCanvas);
                connection.ClickOn(point);
                m_draggedElements.Add(connection);
                SelectObjects(new[] { connection });
                m_dragLast = point;
                m_dragLast = Mouse.GetPosition(RealCanvas);
            }
            else if (pointObject != null)
            {
                if (SelectedObjects.Contains(pointObject))
                {
                    m_draggedElements.AddRange(SelectedObjects);
                }
                else
                {
                    m_draggedElements.Add(pointObject);
                    SelectObjects(new[] { pointObject });
                }
                m_dragLast = Mouse.GetPosition(RealCanvas);
            }
            else
            {
                UnselectObjects();
                m_selectRectangleDown = Mouse.GetPosition(RealCanvas);
                Canvas.SetLeft(m_selectRectangle, m_selectRectangleDown.X);
                Canvas.SetTop(m_selectRectangle, m_selectRectangleDown.Y);
                m_selectRectangle.Visibility = Visibility.Visible;
            }
        }

        private void SelectModeLeftUp()
        {
            if (m_draggedElements.Any())
            {
                if (m_draggedElements.Count == 1 && m_draggedElements.First() is IConnectionView)
                {
                    var line = (IConnectionView)m_draggedElements.First();
                    var point = Mouse.GetPosition(RealCanvas);
                    if (point.Equals(m_dragLast))
                    {
                        if (!line.MovingPointIsLast() && !line.MovingPointIsFirst())
                        {
                            line.RemovePoint(true);
                        }
                        m_draggedElements.Clear();
                        return;
                    }
                    LoadElementsUnderMouse();
                    var obj = m_objectsUnderMouse.Except(new[] { line }).FirstOrDefault();
                    if (obj == null || (!line.MovingPointIsFirst() && !line.MovingPointIsLast()))
                    {
                        line.SetPoint(point);
                    }
                    else
                    {
                        var end = line.MovingPointIsLast();
                        try
                        {
                            obj.AddConnection(line, !end);
                            if (end) line.RecalculateEndPoint();
                            else line.RecalculateStartPoint();
                        }
                        catch (NotImplementedException)
                        {
                            MessageBox.Show("k tomuto bodu nelze hranu připojit");
                            return;
                        }
                    }
                }
                m_draggedElements.Clear();
                //UnselectObjects();
            }
            else if (m_selectRectangle.Visibility == Visibility.Visible)
            {
                m_selectRectangle.Visibility = Visibility.Collapsed;
                SelectObjects(GetSelectedObjects(Mouse.GetPosition(RealCanvas)));

            }
        }

        private List<IObjectView> GetSelectedObjects(Point selectRectangleUp)
        {
            double left = Math.Min(m_selectRectangleDown.X, selectRectangleUp.X);
            double right = Math.Max(m_selectRectangleDown.X, selectRectangleUp.X);
            double top = Math.Min(m_selectRectangleDown.Y, selectRectangleUp.Y);
            double bottom = Math.Max(m_selectRectangleDown.Y, selectRectangleUp.Y);
            List<IObjectView> selectedViews = new List<IObjectView>();
            if (bottom - top < 5 || right - left < 5) return selectedViews;             //moc maly obdelnik pro vyber
            m_views.Values.OfType<IConnectionView>().ToList().ForEach(connection =>
            {
                if (GeometryUtils.IsBetween(left, right, top, bottom, connection.Model.Points.First()) ||
                    GeometryUtils.IsBetween(left, right, top, bottom, connection.Model.Points.Last()))
                {
                    selectedViews.Add(connection);
                }
            });
            selectedViews.AddRange(
                m_views.Values.OfType<PointObjectView>()
                    .Where(
                        pointObjectView =>
                            !(pointObjectView is InOutOuterView) &&
                            GeometryUtils.IsBetween(left, right, top, bottom, pointObjectView.Position)));
            return selectedViews;
        }

        public void SelectAll()
        {
            SelectObjects(m_views.Values);
        } 

        private void SelectObjects(IEnumerable<IObjectView> selected)
        {
            var selectedViews = selected as IList<IObjectView> ?? selected.ToList();
            if (!selectedViews.Any())
            {
                UnselectObjects();
                return;
            }
            SelectedObjects.ForEach(o => o.Selected = ViewSelectionMode.None);
            SelectedObjects.Clear();
            SelectedObjects.AddRange(selectedViews);
            SelectedObjects.ForEach(o => o.Selected = ViewSelectionMode.Selected);
            m_componentsSettings.HideSettings(m_selectedObjects);
        }

        public void UnselectObjects()
        {
            SelectedObjects.ForEach(o => o.Selected = ViewSelectionMode.None);
            SelectedObjects.Clear();
            m_componentsSettings.HideSettings();
        }

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

        private void LoadElementsUnderMouse()
        {
            m_objectsUnderMouse.Clear();
            Point p = Mouse.GetPosition(RealCanvas);
            VisualTreeHelper.HitTest(RealCanvas, null, new HitTestResultCallback(NodesAndConnectionsCallback),
                new GeometryHitTestParameters(new EllipseGeometry(p, 5, 5)));
        }

        private void OnSizeChanged()
        {
            OnPropertyChanged(nameof(CanvasWidth));
            OnPropertyChanged(nameof(ViewBoxWidth));
            OnPropertyChanged(nameof(CanvasHeight));
            OnPropertyChanged(nameof(ViewBoxHeight));
        }

        private HitTestResultBehavior NodesAndConnectionsCallback(HitTestResult result)
        {
            UIElement hit = result.VisualHit as UIElement;
            if (hit != null)
            {
                if (hit is Canvas)
                {
                    return HitTestResultBehavior.Stop;
                }
                IObjectView obj;
                if (m_views.TryGetValue(hit, out obj))
                {
                    m_objectsUnderMouse.Add(obj);
                }
                return HitTestResultBehavior.Continue;
            }
            else
            {
                return HitTestResultBehavior.Stop;
            }
        }

        private void CopyCommand(object sender, ExecutedRoutedEventArgs e)
        {
            
            List<GraphElement> copiedElements = new List<GraphElement>();
            SelectedObjects.ForEach(so =>copiedElements.Add(so.GetModelElement()));
            var simlinks = SelectedObjects.OfType<SimlinkView>();
            foreach (var simlinkView in simlinks)
            {
                copiedElements.RemoveAll(n => n.Equals(simlinkView.Model.Node));
            }
            foreach (var el in SelectedObjects.Where(c=> !(c.GetModelElement() is MachinationsNode) && !(c.GetModelElement() is MachinationsConnection)))
            {
                copiedElements.Remove(el.GetModelElement());
            }
            CopyElements(copiedElements);
        }

        private void CopyElements(List<GraphElement> copiedElements)
        {
            m_copies.Clear();
            for (int id = 0; id < copiedElements.Count; id++)
            {
                copiedElements[id].Id = id;
            }
            copiedElements.ForEach(ce => m_copies.Add((GraphElement) ce.Clone()));
            foreach (var copy in m_copies)
            {
                copy.TurnPools.Clear();
                if(copy is MachinationsNode)
                    ((MachinationsNode)copy).Simlinks.Clear();
            }
            for (int i = 0; i < copiedElements.Count; i++)
            {
                var connection = copiedElements[i] as MachinationsConnection;
                if (connection != null)
                {
                    if (connection.Start != null )
                    {
                        if (copiedElements.Contains(connection.Start))
                        {
                            ((MachinationsConnection) m_copies[i]).ConnectToElement(
                                m_copies[copiedElements.IndexOf(connection.Start)], true);
                        }
                        else
                        {
                            ((MachinationsConnection)m_copies[i]).Points.Insert(0, connection.Start.GetPosition());
                            ((MachinationsConnection)m_copies[i]).StartId = -1;
                        }
                        
                    }
                    if (connection.End != null)
                    {
                        if (copiedElements.Contains(connection.End))
                        {
                            ((MachinationsConnection) m_copies[i]).ConnectToElement(
                                m_copies[copiedElements.IndexOf(connection.End)], false);
                        }
                        else
                        {
                            ((MachinationsConnection)m_copies[i]).Points.Add( connection.End.GetPosition());
                            ((MachinationsConnection) m_copies[i]).EndId = -1;
                        }

                    }
                }
            }

            m_copies.ForEach(c => { c.CopyShift(); });
        }

        private void PasteCommand(object sender, ExecutedRoutedEventArgs e)
        {
            List<IObjectView> newItems = new List<IObjectView>();
            //SelectObjects(ProgramControlUtils.CreateConnectedViews(this, false, m_copies));
            try
            {
                newItems = ProgramControlUtils.LoadWithViews(m_graph, this, false, m_copies,
                    new List<IUserComponentSchema>()).ToList();
                SelectObjects(newItems);
            }
            catch (Exception exception)
            {
                newItems.ForEach(DeleteObject);
            }

            CopyElements(m_copies.ToList());
        }
    }
}
