using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Machinations.FromFlash.graph;
using Machinations.FromFlash.machinations.model;
using Machinations.UserComponents;
using Machinations.WPF;

namespace Machinations.GUI
{
    /// <summary>
    /// view class for user components. keep pointers to views of attached outer views
    /// </summary>
    public class UserComponentView :PointObjectView, IComponentView
    {
        private IUserComponentInstance m_model;
        private List<InOutOuterView> m_inOutOuterViews;

        public event Action InOutChanged;


        public UserComponentView(IUserComponentInstance component)
        {
            m_model = component;
            component.ComponentChanged += OnComponentChanged;
            m_uiElement = new UserComponentUiElement(component.Radius, component.InternalGraph, m_model.Color);
            m_textBlock.Text = m_model.Name;
            UpdateLabelPosition();
            m_inOutOuterViews = new List<InOutOuterView>();
            UpdateInOutViews();
            m_model.InhibitedChanged += () => Selected = ViewSelectionMode.None;
            m_model.OnSelect += () => { Selected = ViewSelectionMode.Selected; };
            m_model.OnUnselect += () => { Selected = ViewSelectionMode.None; };
        }


        public override string Label
        {
            get { return m_model.Name; }
            set { m_model.Name = value; }
        }

        public override Point Position
        {
            get { return m_model.Position; }
            set
            {
                m_model.Position = value;
                UpdateInOutViewsPositions();
                base.Position = value;
            } 
        }

        public IUserComponentInstance Model
        {
            get { return m_model; }
            set { m_model = value; }
        }

        public List<InOutOuterView> InOutOuterViews
        {
            get { return m_inOutOuterViews; }
            set { m_inOutOuterViews = value; }
        }

        public override GraphElement GetModelElement()
        {
            return (GraphElement)Model;
        }

        public override int Radius
        {
            get
            {
                return m_model.Radius; 
            }
        }

        protected override Brush GetBrushForNormalSelection()
        {
            if (Model.Inhibited) return Brushes.LightGray;
            return new SolidColorBrush(m_model.Color);
        }

        protected override void SetMainUiElementPosition(Point value)
        {
            m_uiElement.SetValue(Canvas.LeftProperty, value.X - Radius/2 );
            m_uiElement.SetValue(Canvas.TopProperty, value.Y - Radius/2 );
        }

        protected override void UpdateLabelPosition()
        {
            m_textBlock.SetValue(Canvas.LeftProperty, Model.Position.X - m_textBlock.Text.Length * 3);
            m_textBlock.SetValue(Canvas.TopProperty, Model.Position.Y + Radius * 0.6);
        }

        public void BeforeDelete(IComponentView replaceView = null)
        {
            //Model.Zip();
            for (int i = 0; i < InOutOuterViews.Count; i++)
            {
                var inOut = InOutOuterViews[i];
                bool input = inOut.Model.Input;
                List<IConnectionView> connections;
                if (input)
                    connections = inOut.InputConnections;
                else
                    connections = inOut.OutputConnections;
                while (connections.Any())
                {
                    var connection = connections.First();
                    inOut.RemoveConnection(connection, !input);
                    inOut.Model.OuterConnections.Clear();
                    replaceView?.InOutOuterViews[i].AddConnection(connection, !input);
                    connection.RecalculateEndPoint();
                }
            }
            m_inputConnections.ForEach(c => replaceView?.AddConnection(c, false));
            m_inputConnections.Clear();
            
            m_outputConnections.ForEach(c => replaceView?.AddConnection(c, true));
            m_outputConnections.Clear();
            //replaceView?.Model.UnZip();
        }

        public override void RemoveConnection(IConnectionView connection, bool start)
        {
            base.RemoveConnection(connection, start);
            connection.Model.DisconnectElement(start);
        }

        internal void UpdateInOutViewsPositions()
        {
            Point center = m_model.InternalGraph.GetCenter();
            m_inOutOuterViews.ForEach(v => v.Position = PointOnEdge(center, v.Model.Position));
        }

        public override void ColorView(Brush brush)
        {
            ((UserComponentUiElement) m_uiElement).Brush = brush;
            m_uiElement.InvalidateVisual();
        }

        public override void Dispose()
        {
            base.Dispose();
            m_model.Dispose();
        }

        public List<UIElement> GetInOutsUiElements()
        {
            return m_inOutOuterViews.SelectMany(v => v.GetAllUiElements()).ToList();
        }

        public override void AddConnection(IConnectionView connectionView, bool start, bool withModel = true)
        {
            base.AddConnection(connectionView, start);
            if (withModel)
                connectionView.Model.ConnectToElement((GraphElement)Model, start);
        }


        private void OnComponentChanged()
        {
            //m_textBlock.Text = m_model.Name;
            //UpdateLabelPosition();
            UserComponentUiElement uielement = (UserComponentUiElement) m_uiElement;
            if(!uielement.Graph.Equals(m_model.InternalGraph))
                uielement.ChangeGraph(m_model.InternalGraph);
            uielement.Width = Radius;
            uielement.Height = Radius;
            Position = Position;
        }

        private Point PointOnEdge(Point center, Point inOutPosition)
        {
            Point shifted = new Point(inOutPosition.X - center.X, inOutPosition.Y - center.Y);
            Point onEdge;
            var halfRad = Radius/2;
            if (Math.Abs(halfRad * shifted.Y) <= Math.Abs(halfRad * shifted.X))
            {
                onEdge = new Point(Math.Sign(shifted.X) * halfRad, Math.Sign(shifted.X) * (halfRad * shifted.Y) / shifted.X);
            }
            else
            {
                onEdge = new Point(Math.Sign(shifted.Y) * (halfRad * shifted.X) / shifted.Y, Math.Sign(shifted.Y) * halfRad);
            }
            return new Point(onEdge.X + Position.X, onEdge.Y + Position.Y);
        }

        private void UpdateInOutViews()
        {
            //m_inOutOuterViews.ForEach(v => v.Dispose()); nejak smazat stare z canvasu
            m_inOutOuterViews.Clear();
            if (m_model.InternalGraph.Elements.Any())
            {
                Point center = m_model.InternalGraph.GetCenter();
                foreach (var inOut in m_model.OuterInOuts)
                {
                    Point position = PointOnEdge(center, inOut.Position);
                    InOutOuterView view = new InOutOuterView(inOut, position);
                    m_inOutOuterViews.Add(view);
                }
            }
            InOutChanged?.Invoke();
        }
    }

    /// <summary>
    /// ui elemet for usercomponentView.Consist of a border and a picture of visualised inner painting canvas
    /// </summary>
    public class UserComponentUiElement : Border
    {
        private Brush m_brush;
        private PaintingCanvas m_paintingCanvas;
        private Graph m_graph;
        private Viewbox m_viewBox;

        public UserComponentUiElement(int radius, Graph graph, Color color)
        {
            m_graph = graph;
            Width = radius;
            Height = radius;
            m_paintingCanvas = new WPF.PaintingCanvas();
            m_paintingCanvas.Reset();
            m_paintingCanvas.Initialize(m_graph, null, null);
            m_paintingCanvas.IsHitTestVisible = false;
            m_viewBox = new Viewbox {Child = CreateImage()};
            Child = m_viewBox;
            m_brush = new SolidColorBrush(color);
        }

        public Brush Brush
        {
            get { return m_brush; }
            set { m_brush = value; }
        }

        public Graph Graph
        {
            get { return m_graph; }
        }


        public void ChangeGraph(Graph graph)
        {
            m_graph = graph;
            m_paintingCanvas.Reset(graph);
            m_viewBox.Child = CreateImage();
        }

        protected override void OnRender(DrawingContext drawingContext)
        {
            Background = m_brush;
            base.OnRender(drawingContext);
        }

        private Image CreateImage()
        {
            Dictionary<GraphElement, IObjectView> createdViews = new Dictionary<GraphElement, IObjectView>();
            m_graph.Elements.ForEach(
                e => ProgramControlUtils.CreateViews(e, m_paintingCanvas, createdViews, m_graph.Elements, true));
            m_graph.Elements.ForEach(e =>
            {
                MachinationsConnection c = e as MachinationsConnection;
                if (c != null) ProgramControlUtils.ConnectConnections(c, m_graph.Elements, createdViews, false);
            });
            try
            {
                CroppedBitmap bitmap = new CroppedBitmap(m_paintingCanvas.CreateBitmap(), m_graph.GetRectangle());
                return new Image() { Source = bitmap };
            }
            catch (ArgumentException)
            {
                MessageBox.Show("nepovedlo se nacist obrazek komponenty");

            }
            return new Image();
        }
    }
}
