using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Windows;
using System.Windows.Media;

namespace Microscopic_Traffic_Simulator.Renderers
{
    /// <summary>
    /// Renderer for drawing bezier lanes during their building.
    /// </summary>
    class BezierLaneBuildingRenderer : LaneBuildingRenderer
    {
        /// <summary>
        /// Assigns integer identifiers for additional points of a bezier lane.
        /// </summary>
        protected class BezierLanePoints : LanePoints
        {
            internal const int FirstControldPoint = 2;
            internal const int SecordControlPoint = 3;
        }

        /// <summary>
        /// Radius of filled circle visualizating location of the control points.
        /// </summary>
        private const double ControlPointCircleRadius = 2.0;

        /// <summary>
        /// The first control point of a previewing building lane.
        /// </summary>
        protected Point? firstWorldControlPoint;
        /// <summary>
        /// The first control point of a previewing building lane.
        /// </summary>
        public Point? FirstWorldControlPoint { get { return firstWorldControlPoint; } }

        /// <summary>
        /// Determines whether the <see cref="firstWorldControlPoint"/> is currently being dragged.
        /// </summary>
        protected bool isFirstControlPointBeingDragged;

        /// <summary>
        /// The second control point of a previewing building lane.
        /// </summary>        
        protected Point? secondWorldControlPoint;
        /// <summary>
        /// The second control point of a previewing building lane.
        /// </summary>                
        public Point? SecondWorldControlPoint { get { return secondWorldControlPoint; } }

        /// <summary>
        /// Determines whether the <see cref="secondWorldControlPoint"/> is currently being dragged.
        /// </summary>
        protected bool secondControlPointIsMoving;

        /// <summary>
        /// Determines whether the previewing lane has all points defined.
        /// </summary>
        internal override bool IsLaneDefined
        {
            get
            {
                return base.IsLaneDefined && firstWorldControlPoint.HasValue && secondWorldControlPoint.HasValue;
            }
        }

        /// <summary>
        /// Determines whether any point is being dragged.
        /// </summary>
        internal override bool IsSomePointBeingDragged
        {
            get
            {
                return base.IsSomePointBeingDragged || isFirstControlPointBeingDragged || secondControlPointIsMoving;
            }
        }

        /// <summary>
        /// Initialization of bezier lane building renderer.
        /// </summary>
        /// <param name="visual">Drawing visual to render to.</param>
        internal BezierLaneBuildingRenderer(DrawingVisual visual) : base(visual) { }

        /// <summary>
        /// Renders preview of the lane.
        /// </summary>
        /// <param name="cursorPointOnCanvas">Current cursor point on canvas.</param>
        internal override void RenderBuildingLane(Point cursorPointOnCanas)
        {
            if (startWorldPoint.HasValue)
            {
                if (endWorldPoint.HasValue)
                {
                    lastCursorPointOnCanvas = cursorPointOnCanas;
                    if (firstWorldControlPoint.HasValue)
                    {
                        if (secondWorldControlPoint.HasValue)
                        {
                            using (DrawingContext dc = visual.RenderOpen())
                            {
                                dc.DrawGeometry(null, pen, new BezierCurveGeometryCreator()
                                    .GetBezierCurveGeometry(TransformRealWorldPoint(startWorldPoint.Value),
                                    TransformRealWorldPoint(endWorldPoint.Value),
                                    TransformRealWorldPoint(firstWorldControlPoint.Value),
                                    TransformRealWorldPoint(secondWorldControlPoint.Value)));
                                dc.DrawEllipse(Brushes.Green, null,
                                    TransformRealWorldPoint(firstWorldControlPoint.Value), ControlPointCircleRadius,
                                    ControlPointCircleRadius);
                                dc.DrawEllipse(Brushes.Green, null,
                                    TransformRealWorldPoint(secondWorldControlPoint.Value), ControlPointCircleRadius,
                                    ControlPointCircleRadius);
                                if (IsLaneDefined)
                                {
                                    dc.DrawEllipse(null, pen, TransformRealWorldPoint(startWorldPoint.Value),
                                        DraggingCircleRadius, DraggingCircleRadius);
                                    dc.DrawEllipse(null, pen, TransformRealWorldPoint(endWorldPoint.Value),
                                        DraggingCircleRadius, DraggingCircleRadius);
                                    dc.DrawEllipse(null, pen, TransformRealWorldPoint(firstWorldControlPoint.Value),
                                        DraggingCircleRadius, DraggingCircleRadius);
                                    dc.DrawEllipse(null, pen, TransformRealWorldPoint(secondWorldControlPoint.Value),
                                        DraggingCircleRadius, DraggingCircleRadius);
                                }
                            }
                        }
                        else
                        {
                            using (DrawingContext dc = visual.RenderOpen())
                            {
                                dc.DrawGeometry(null, pen, new BezierCurveGeometryCreator()
                                    .GetBezierCurveGeometry(TransformRealWorldPoint(startWorldPoint.Value),
                                    TransformRealWorldPoint(endWorldPoint.Value),
                                    TransformRealWorldPoint(firstWorldControlPoint.Value), cursorPointOnCanas));
                                dc.DrawEllipse(Brushes.Green, null,
                                    TransformRealWorldPoint(firstWorldControlPoint.Value), ControlPointCircleRadius,
                                    ControlPointCircleRadius);
                            }
                        }
                    }
                    else
                    {
                        using (DrawingContext dc = visual.RenderOpen())
                        {
                            dc.DrawGeometry(null, pen, new BezierCurveGeometryCreator().GetBezierCurveGeometry(
                                TransformRealWorldPoint(startWorldPoint.Value),
                                TransformRealWorldPoint(endWorldPoint.Value), cursorPointOnCanas, cursorPointOnCanas));
                        }
                    }
                }
                else
                {
                    base.RenderBuildingLane(cursorPointOnCanas);
                }
            }
        }

        /// <summary>
        /// Sets next point of the previewing lane.
        /// </summary>
        /// <param name="cursorPointOnCanvas">Location to be set as the start or end point of the previewing.</param>
        internal override void SetPoint(Point cursorPointOnCanvas)
        {
            if (firstWorldControlPoint.HasValue)
            {
                Debug.Assert(!secondWorldControlPoint.HasValue, "Incorrect application state");
                secondWorldControlPoint = TransformCanvasPoint(cursorPointOnCanvas);
                RenderBuildingLane(cursorPointOnCanvas);
            }
            else if (endWorldPoint.HasValue)
            {
                firstWorldControlPoint = TransformCanvasPoint(cursorPointOnCanvas);
                RenderBuildingLane(cursorPointOnCanvas);
            }
            else
            {
                base.SetPoint(cursorPointOnCanvas);
            }
        }

        /// <summary>
        /// Resets points of previewing lane and clear any preview.
        /// </summary>
        internal override void ResetRendererAndClearAnyPreview()
        {
            firstWorldControlPoint = secondWorldControlPoint = null;
            base.ResetRendererAndClearAnyPreview();
        }

        /// <summary>
        /// Reset dragging mode.
        /// </summary>
        internal override void ResetDraggingOfPoint()
        {
            base.ResetDraggingOfPoint();
            isFirstControlPointBeingDragged = secondControlPointIsMoving = false;
        }

        /// <summary>
        /// Moves point which is dragged.
        /// </summary>
        /// <param name="cursorPointOnCanvas">Cursor point as a new point of movement (dragging).</param>
        internal override void MovePointOfLane(Point point)
        {
            firstWorldControlPoint += GetVectorOfMoveOfPointIfItIsBeingDragged(point, isFirstControlPointBeingDragged);
            secondWorldControlPoint += GetVectorOfMoveOfPointIfItIsBeingDragged(point, secondControlPointIsMoving);
            base.MovePointOfLane(point);
        }

        /// <summary>
        /// Initialize dragging mode for point if any is sufficiently close to cursor point.
        /// </summary>
        /// <param name="cursorPointOnCanvas">Cursor point on canvas.</param>
        internal override void InitializeDraggingModeOfAPointIfAnyIsNear(Point cursorPointOnCanvas)
        {
            base.InitializeDraggingModeOfAPointIfAnyIsNear(cursorPointOnCanvas);
            IList<double> squaredDistancesOfCanvasPointToWorldLanePoints =
                GetSquaredDistancesOfCanvasPointToWorldLanePoints(cursorPointOnCanvas);
            SetPointToDraggingModeIfCursorIsNearAndDragging(ref isFirstControlPointBeingDragged, cursorPointOnCanvas,
                squaredDistancesOfCanvasPointToWorldLanePoints[BezierLanePoints.FirstControldPoint],
                squaredDistancesOfCanvasPointToWorldLanePoints);
            SetPointToDraggingModeIfCursorIsNearAndDragging(ref secondControlPointIsMoving, cursorPointOnCanvas,
                squaredDistancesOfCanvasPointToWorldLanePoints[BezierLanePoints.SecordControlPoint],
                squaredDistancesOfCanvasPointToWorldLanePoints);
        }

        /// <summary>
        /// Get squared distances of all lane world points from cursor point on canvas.
        /// </summary>
        /// <param name="cursorPointOnCanvasPoint">Cursor point on canvas.</param>
        /// <returns>List of squared distances of all lane world points from cursor point on canvas.</returns>
        protected override IList<double> GetSquaredDistancesOfCanvasPointToWorldLanePoints(Point canvasPoint)
        {
            return base.GetSquaredDistancesOfCanvasPointToWorldLanePoints(canvasPoint).Concat(
                new List<double>
                {
                    (canvasPoint - TransformRealWorldPoint(firstWorldControlPoint.Value)).LengthSquared,
                    (canvasPoint - TransformRealWorldPoint(secondWorldControlPoint.Value)).LengthSquared
                }).ToList();
        }
    }
}
