using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Effects;
using System.Windows.Shapes;
using Machinations.FromFlash.graph;
using Machinations.FromFlash.machinations.model;

namespace Machinations.GUI
{
    /// <summary>
    /// view for Machinations connections. Is connected to two different views
    /// </summary>
    public abstract class ConnectionView : IConnectionView
    {
        protected readonly MachinationsConnection m_model;
        protected Polyline m_polyline;
        protected Polyline m_arrowHead;
        private IObjectView m_startPoint;
        private IObjectView m_endPoint;
        protected int m_movingPointIndex;
        //private double m_length;
        private readonly TextBlock m_textBlock;
        private readonly TextBlock m_descriptionTextBlock;
        private ViewSelectionMode m_selected;
        private ViewHighlightMode m_highlighted;
        private Point m_labelPosition;
        protected List<IConnectionView> m_inputConnections;
        public event Action OnLoaded;


        protected ConnectionView(MachinationsConnection model, Point? startingPoint) : this(model)
        {
            m_polyline = new Polyline{Points = model.Points};
            if (startingPoint != null)
            {
                model.Points.Add(startingPoint.Value);
                model.Points.Add(new Point(startingPoint.Value.X, startingPoint.Value.Y));
                RefreshPolyline();
                m_movingPointIndex = m_polyline.Points.Count - 1;
                m_arrowHead = CreateArrowhead(startingPoint.Value);
            }
            else
            {
                RefreshPolyline();
                m_arrowHead = CreateArrowhead(new Point());
            }
        }

        protected ConnectionView(MachinationsConnection model)
        {
            m_model = model;
            model.StartConnected += ModelOnStartConnected;
            model.EndConnected += ModelOnEndConnected;
            m_textBlock = new TextBlock() ;
            m_textBlock.Background = ColorsUtils.ConnectionTextBrush;
            m_descriptionTextBlock = new TextBlock
            {
                Text = Model.Description, FontSize = 8, Background = ColorsUtils.ConnectionTextBrush
            };
            m_inputConnections = new List<IConnectionView>();
            model.VisualChanged += RefreshPolyline;
            model.InhibitedChanged += () => Selected = ViewSelectionMode.None;
            model.DescriptionChanged += () => m_descriptionTextBlock.Text = m_model.Description;
            m_model.OnSelect += () => { Selected = ViewSelectionMode.Selected; };
            m_model.OnUnselect += () => { Selected = ViewSelectionMode.None; };
            SetTextBlockValue();
            m_model.Changed += (sender, e) => SetTextBlockValue();
        }
        public virtual MachinationsConnection Model
        {
            get { return m_model; }
        }
       
        public UIElement UiElement
        {
            get { return m_polyline; }
        }

        public string Label
        {
            get
            {
                return m_model.Label.Text; }
            set
            {
                bool idDynamicDice = m_model.Label.Type == LabelType.DynamicDice;
                m_model.Label.Text = value;
                if(idDynamicDice != (m_model.Label.Type == LabelType.DynamicDice))
                    m_model.UpdateDescription();
                SetTextBlockValue();
            }
        }

        private void SetTextBlockValue()
        {
            m_textBlock.Text = m_model.Label.Text.Equals("1") ? "" : m_model.Label.Text;
            if(Model.Points.Count >= 2)
                RecalculateLabel();
        }

        public Color BaseColor
        {
            get { return m_model.Color; }
            set { m_model.Color = value; }
        }

        public ViewSelectionMode Selected
        {
            get
            {
                return m_selected;
            }
            set
            {
                m_selected = value;
                var c = GetColor();
                ColorSelf(c);
            }
        }

        public ViewHighlightMode Highlighted
        {
            get { return m_highlighted; }
            set
            {
                if (value == ViewHighlightMode.Highlighted && m_highlighted != ViewHighlightMode.Highlighted)
                {
                    HighlightSurr();
                    //m_textBlock.Background = ColorsUtils.ConnectionSelectedTextBrush;
                    m_textBlock.FontSize = 18;
                    m_descriptionTextBlock.FontSize = 12;
                }
                if (m_highlighted == ViewHighlightMode.Highlighted && value != ViewHighlightMode.Highlighted)
                {
                    UnHighlightSurr();
                    //m_textBlock.Background = ColorsUtils.ConnectionTextBrush;
                    m_textBlock.FontSize = 12;
                    m_descriptionTextBlock.FontSize = 8;
                }
                m_highlighted = value;
                var b = GetColor();
                ColorSelf(b);
            }
        }

        public Point Position
        {
            get
            {
                RecalculateLabel();
                return m_labelPosition;
            }
        }

        public List<UIElement> GetAllUiElements()
        {
            return new List<UIElement> { m_polyline, m_arrowHead, m_textBlock, m_descriptionTextBlock };
        }

        public Point LinePosition(Point beforeLast)
        {
            if (Model.Points.Count >= 2)
            {
                RecalculateLabel();
            }
            return GetLinePosition(beforeLast);
        }

        public GraphElement GetModelElement()
        {
            return Model;
        }

        public void AddConnection(IConnectionView connectionView, bool start, bool withModel = true)
        {
            if (!start)
            {
                m_inputConnections.Add(connectionView);
                connectionView.AddEndView(this);
                if(withModel)
                    connectionView.Model.ConnectToElement(Model, start);
            }
            else
            {
                throw new ArgumentException("connecting new connection to connection");
            }
            
        }

        public void RemoveConnection(IConnectionView connection, bool start)
        {
            if (!start)
            {
                m_inputConnections.Remove(connection);
                connection.RemoveEnd();
                connection.Model.DisconnectElement(start);
            }
            else
            {
                throw new ArgumentException();
            }
        }

        public void Move(double deltaX, double deltaY)
        {
            for (int i = 0; i < Model.Points.Count; i++)
            {
                Model.Points[i] = new Point(
                    Model.Points[i].X + deltaX, Model.Points[i].Y + deltaY);
            }
        }

        public void Dispose()
        {
            m_model.Dispose();
            m_inputConnections.ForEach(ic => ic.RemoveEnd());
            m_startPoint?.RemoveConnection(this, true);
            m_endPoint?.RemoveConnection(this, false);
        }

        public void AddEndView(IObjectView view)
        {
            m_endPoint = view;
        }

        public void RecalculateAfterLoaded()
        {
            if ((m_startPoint != null || Model.StartId == -1)
                && (m_endPoint != null || Model.EndId == -1))
            {
                if (m_startPoint != null)
                    Model.Points[0] = m_startPoint.Position;
                if (m_endPoint != null)
                    RecalculateEndPoint(Model.StartId != -1);
                else
                {
                    if (m_startPoint != null)
                        RecalculateStartPoint();
                    RecalculateArrow();
                }
            }
        }

        public void ClickOn(Point selectedPoint)
        {
            var list = Model.Points.ToList();
            int nearestIndex = 0;
            double smallestAngle = 180;
            for (int i = 0; i < list.Count; i++)
            {
                if (GeometryUtils.PointDistance(selectedPoint, list[i]) < 5)
                {
                    m_movingPointIndex = i;
                    if (i == 0 && m_startPoint != null)
                    {
                        m_startPoint.RemoveConnection(this, true);
                    }
                    else if (i == list.Count - 1 && m_endPoint != null)
                    {
                        m_endPoint.RemoveConnection(this, false);
                    }
                    return;
                }
                if (i == list.Count - 1) break;
                Vector next = list[i + 1] - list[i];
                Vector might = selectedPoint - list[i];
                double angle = Math.Abs(Vector.AngleBetween(next, might));
                if (angle < smallestAngle)
                {
                    nearestIndex = i;
                    smallestAngle = angle;
                }
            }
            Model.Points.Insert(nearestIndex + 1, selectedPoint);
            m_movingPointIndex = nearestIndex + 1;
        }

        public void SetPoint(Point p)//, IObjectView view)
        {
            RecalculateArrow();
            RecalculateLabel();
            //ukotveni posledniho bodu
            if (m_movingPointIndex == Model.Points.Count - 1)
            {
                {
                    Model.Points[m_movingPointIndex] = p;
                    Model.Points.Add(p);
                    m_movingPointIndex++;
                }
                
            }
        }

        public void AddStartView(IObjectView view)
        {
            m_startPoint = view;
        }

        public void RemovePoint(bool dragFinished)
        {
            if (m_movingPointIndex == 0) Model.Points.RemoveAt(1);
            else if (m_movingPointIndex == Model.Points.Count - 1) Model.Points.RemoveAt(m_movingPointIndex - 1);
            else Model.Points.RemoveAt(m_movingPointIndex);
            if (m_movingPointIndex >= Model.Points.Count)
            {
                if (Model.Points.Count == 2)
                {
                    m_movingPointIndex = 1;
                    if (m_endPoint != null)
                       m_endPoint.RemoveConnection(this, false);
                }
                else
                    m_movingPointIndex--;
            }
            //odebral jsem prostredni bod, ale konec skocil na jeho misto, aniz by se odpojil
            else if (Model.Points.Count == 2 && m_movingPointIndex == 1 && !dragFinished)
            {
                m_endPoint.RemoveConnection(this,false);
            }
        }

        public bool MovingPointIsLast()
        {
            return m_movingPointIndex == Model.Points.Count - 1;
        }

        public bool MovingPointIsFirst()
        {
            return m_movingPointIndex == 0;
        }

        public void RemoveEnd()
        {
            m_endPoint = null;
        }

        public void RemoveStart()
        {
            m_startPoint = null;
        }

        public void Loaded()
        {
            if (OnLoaded != null)
            {
                OnLoaded.Invoke();
                OnLoaded = null;
            }
        }

        public void MovePoint(Point p)
        {
            Point shiftedPoint = new Point(p.X, p.Y);
            Model.Points[m_movingPointIndex] = shiftedPoint;

            if (m_movingPointIndex == 1 && m_startPoint != null)
            {
                Model.Points[0] = m_startPoint.LinePosition(p);
            }
            if (m_movingPointIndex == Model.Points.Count - 2 && m_endPoint != null)
            {
                Model.Points[Model.Points.Count - 1] = m_endPoint.LinePosition(p);
                RecalculateArrow();
            }
            if (m_movingPointIndex == Model.Points.Count - 1)
            {
                RecalculateArrow();
            }
            RecalculateLabel();
        }

        public void RecalculateStartPoint(bool recalculateEnd = false)
        {
            if(m_startPoint != null)
                Model.Points[0] = m_startPoint.LinePosition(Model.Points[1]);
            if (recalculateEnd && Model.Points.Count >= 2)
            {
                RecalculateEndPoint();
            }
            if (!recalculateEnd)
            {
                RecalculateLabel();
            }
        }

        public void RecalculateEndPoint(bool recalculateStart = false)
        {
            if (m_endPoint != null)
            {
                var endPosition = m_endPoint.LinePosition(Model.Points[Model.Points.Count - 2]);
                Model.Points[Model.Points.Count - 1] = endPosition;
                if (endPosition.Equals(new Point(0, 0)))
                {
                    m_endPoint.OnLoaded += EndPointOnLoaded;
                }
            }
            RecalculateArrow();
            if (recalculateStart && Model.Points.Count >= 2 && m_startPoint != null)
            {
                RecalculateStartPoint();
            }
            if (!recalculateStart || m_startPoint == null)
            {
                RecalculateLabel();
            }
        }

        private void EndPointOnLoaded()
        {
            RecalculateEndPoint();
        }


        protected Polyline CreateArrowhead(Point startingPoint)
        {
            return new Polyline
            {
                Points = new PointCollection(new[]
                {
                    new Point(startingPoint.X, startingPoint.Y),
                    new Point(startingPoint.X, startingPoint.Y),
                    new Point(startingPoint.X, startingPoint.Y)
                }),
                StrokeThickness = m_model.Thickness > 0 ? m_model.Thickness : 2,
                Stroke = new SolidColorBrush(m_model.Color)
            };
        }

        protected void RefreshPolyline()
        {
            m_polyline.StrokeThickness = m_model.Thickness > 0 ? m_model.Thickness : 2;
            m_polyline.Stroke = new SolidColorBrush(GetColor());
        }

        //private double GetTotalLength()

        //{

        //    double length = 0;

        //    for (int i = 1; i < Polyline.Points.Count; i++)

        //    {

        //        length += GeometryUtils.PointDistance(Polyline.Points[i - 1], Polyline.Points[i]);

        //    }

        //    return length;

        //}

        private Point GetLinePosition(Point beforeLast)
        {
            Vector v = new Vector(beforeLast.X - m_labelPosition.X, beforeLast.Y - m_labelPosition.Y + 5);
            v.Normalize();
            return new Point(m_labelPosition.X + (v.X * 10), m_labelPosition.Y + 5 + (v.Y * 10));
            //return new Point(m_labelPosition.X, m_labelPosition.Y + shift);
        }

        private void RecalculateLabel()
        {
            var middlePoint = Model.GetMiddlePoint();
            var finalPoint = new Point(middlePoint.X, middlePoint.Y);
            m_textBlock.SetValue(Canvas.LeftProperty, finalPoint.X - m_textBlock.Text.Length * 3);
            m_textBlock.SetValue(Canvas.TopProperty, finalPoint.Y - 7);
            m_descriptionTextBlock.SetValue(Canvas.LeftProperty, finalPoint.X + m_textBlock.Text.Length * 3.5 + 2) ;
            m_descriptionTextBlock.SetValue(Canvas.TopProperty, finalPoint.Y - 8);
            m_labelPosition = finalPoint;
            m_inputConnections.ForEach(connection =>
            {
                if(connection == this)
                    return;
                //connection.RecalculateEndPoint(true);
                connection.Model.Points[connection.Model.Points.Count - 1] =
                    GetLinePosition(connection.Model.Points[connection.Model.Points.Count - 2]);
                (connection as ConnectionView)?.RecalculateArrow();
                //if (Math.Abs(previous.X - (m_labelPosition.X)) < 10
                //     && Math.Abs(previous.Y - (m_labelPosition.Y)) < 10)
                //    return;
                (connection as ConnectionView)?.RecalculateLabel();
            });
        }

        private void RecalculateArrow()
        {
            Point preLast = Model.Points[Model.Points.Count - 2];
            Point last = Model.Points[Model.Points.Count - 1];
            m_arrowHead.Points[1] = last;
            Vector vector = new Vector(last.X - preLast.X, last.Y - preLast.Y);
            double length = GeometryUtils.Length(vector);
            double arrowLength = 10;
            Vector correctSizeVector = new Vector(vector.X / length * arrowLength, vector.Y / length * arrowLength);
            Matrix minusTurn = Matrix.Identity;
            minusTurn.Rotate(-45);
            Vector minusVector = minusTurn.Transform(correctSizeVector);
            m_arrowHead.Points[0] = new Point(last.X - minusVector.X, last.Y - minusVector.Y);
            Matrix plusTurn = Matrix.Identity;
            plusTurn.Rotate(45);
            Vector plusVector = plusTurn.Transform(correctSizeVector);
            m_arrowHead.Points[2] = new Point(last.X - plusVector.X, last.Y - plusVector.Y);
        }

        private void ModelOnEndConnected(IObjectView objectView)
        {
            objectView.AddConnection(this, false);
            RecalculateAfterLoaded();
        }

        private void ModelOnStartConnected(IObjectView objectView)
        {
            objectView.AddConnection(this, true);
            RecalculateAfterLoaded();
        }

        private Color GetColor()
        {
            if(m_highlighted == ViewHighlightMode.Highlighted)
                return Colors.DarkKhaki;
            if(m_highlighted == ViewHighlightMode.NextToHighlighted)
                return Colors.CadetBlue;
            switch (m_selected)
            {
                case ViewSelectionMode.None:
                    if (Model.Inhibited) return Colors.LightGray;
                    if (m_model.Thickness > 0)
                    {
                        return m_model.Color;
                    }
                    return Colors.Transparent;
                case ViewSelectionMode.Selected:
                    return Colors.Chartreuse;
                case ViewSelectionMode.ClickedAndWaiting:
                    return Colors.Aqua;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }

        private void ColorSelf(Color c)
        {
            var baseBrush = new SolidColorBrush(c);
            m_polyline.Stroke = baseBrush;
            m_arrowHead.Stroke = c.Equals(Colors.Transparent) ? new SolidColorBrush(m_model.Color) : baseBrush;
            //m_textBlock.Foreground = b;
            //m_descriptionTextBlock.Foreground = b;
        }

        private void HighlightSurr()
        {
            if(m_endPoint != null)
                m_endPoint.Highlighted = ViewHighlightMode.NextToHighlighted;
            if (m_startPoint != null)
                m_startPoint.Highlighted = ViewHighlightMode.NextToHighlighted;
            m_inputConnections.ForEach(i => i.Highlighted = ViewHighlightMode.NextToHighlighted);
        }

        private void UnHighlightSurr()
        {
            if (m_endPoint != null && m_endPoint != this)
                m_endPoint.Highlighted = ViewHighlightMode.None;
            if (m_startPoint != null && m_startPoint != this)
                m_startPoint.Highlighted = ViewHighlightMode.None;
            m_inputConnections.ForEach(i => i.Highlighted = ViewHighlightMode.None);
        }
    }
}