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 lanes during their building.
    /// </summary>
    class LaneBuildingRenderer : VisualCanvasRenderer
    {
        /// <summary>
        /// Assigns integer identifiers for each point of a lane.
        /// </summary>
        protected class LanePoints
        {
            /// <summary>
            /// Defines identifier for the start point.
            /// </summary>
            internal const int StartPoint = 0;
            /// <summary>
            /// Defines identifier for the end point.
            /// </summary>
            internal const int EndPoint = 1;
        }

        /// <summary>
        /// Thickness of the pen drawing the preview of a lane.
        /// </summary>
        protected const double PenThickness = 1.0;

        /// <summary>
        /// Radius of a circle surrounding the points of a lane. The circle determines the area where cursor must be
        /// when user wants to press left mouse button and drag a point the circle surround.
        /// </summary>
        protected const double DraggingCircleRadius = 4.0;

        /// <summary>
        /// Value of squared radius of dragging circle which can be used to skip sqrt operation in computation of the
        /// Euclidean distance.
        /// </summary>
        protected const double DraggingCircleRadiusSquared = DraggingCircleRadius * DraggingCircleRadius;

        /// <summary>
        /// Pen used to draw road preview.
        /// </summary>
        protected Pen pen = new Pen(Brushes.Green, PenThickness);

        /// <summary>
        /// Start point of a previewing building lane.
        /// </summary>
        protected Point? startWorldPoint = null;
        /// <summary>
        /// Start point of a previewing building lane.
        /// </summary>
        internal Point? StartWorldPoint { get { return startWorldPoint; } }

        /// <summary>
        /// Determines whether the <see cref="startWorldPoint"/> is currently being dragged by mouse cursor.
        /// </summary>
        internal bool isStartPointBeingDragged = false;

        /// <summary>
        /// End point of a previewing building lane.
        /// </summary>
        protected Point? endWorldPoint = null;
        /// <summary>
        /// End point of a previewing building lane.
        /// </summary>        
        internal Point? EndWorldPoint { get { return endWorldPoint; } }

        /// <summary>
        /// Determing whether the <see cref="endWorldPoint" is currently being dragged by mouse cursor./>
        /// </summary>
        internal bool isEndPointBeingDragged = false;

        /// <summary>
        /// Previous reference point for dragging 
        /// </summary>
        protected Point previousReferencePointForDragging;

        /// <summary>
        /// Last cursor point after rendering prewview of building lane.
        /// </summary>
        protected Point lastCursorPointOnCanvas;
        /// <summary>
        /// Last cursor point after rendering prewview of building lane.
        /// </summary>
        internal Point LastCursorPointOnCavnas { get { return lastCursorPointOnCanvas; } }

        /// <summary>
        /// Determines whether the previewing lane has all points defined.
        /// </summary>
        internal virtual bool IsLaneDefined
        {
            get { return startWorldPoint.HasValue && endWorldPoint.HasValue; }
        }

        /// <summary>
        /// Determines whether any point is being dragged.
        /// </summary>
        internal virtual bool IsSomePointBeingDragged
        {
            get { return isStartPointBeingDragged || isEndPointBeingDragged; }
        }

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

        /// <summary>
        /// Render preview  change of move or zoom.
        /// <param name="currentMouseLocation">Current mouse location.</param>
        /// </summary>
        protected override void Render(Point currentMouseLocation)
        {
            RenderBuildingLane(currentMouseLocation);
        }

        /// <summary>
        /// Draws preview of straight lane.
        /// </summary>
        /// <param name="endPointOnCanvas">End point on canvas.</param>
        protected void DrawPreviewStraightLane(Point endPointOnCanvas)
        {
            using (DrawingContext dc = visual.RenderOpen())
            {
                dc.DrawLine(pen, TransformRealWorldPoint(startWorldPoint.Value), endPointOnCanvas);
                if (IsLaneDefined)
                {
                    dc.DrawEllipse(null, pen, TransformRealWorldPoint(startWorldPoint.Value), DraggingCircleRadius,
                        DraggingCircleRadius);
                    dc.DrawEllipse(null, pen, TransformRealWorldPoint(endWorldPoint.Value), DraggingCircleRadius,
                        DraggingCircleRadius);
                }
            }
        }

        /// <summary>
        /// Renders preview of the lane.
        /// </summary>
        /// <param name="cursorPointOnCanvas">Current cursor point on canvas.</param>
        internal virtual void RenderBuildingLane(Point cursorPointOnCanvas)
        {
            lastCursorPointOnCanvas = cursorPointOnCanvas;
            if (startWorldPoint.HasValue)
            {
                if (endWorldPoint.HasValue)
                {
                    DrawPreviewStraightLane(TransformRealWorldPoint(endWorldPoint.Value));
                }
                else
                {
                    DrawPreviewStraightLane(cursorPointOnCanvas);
                }
            }
        }

        /// <summary>
        /// Render preview of the lane.
        /// </summary>
        internal void RenderBuildingLane()
        {
            RenderBuildingLane(lastCursorPointOnCanvas);
        }

        /// <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 virtual void SetPoint(Point cursorPointOnCanvas)
        {
            if (startWorldPoint.HasValue)
            {
                Debug.Assert(!endWorldPoint.HasValue, "Incorrect application state");
                endWorldPoint = TransformCanvasPoint(cursorPointOnCanvas);
            }
            else
            {
                startWorldPoint = TransformCanvasPoint(cursorPointOnCanvas);
            }
            RenderBuildingLane(cursorPointOnCanvas);
        }

        /// <summary>
        /// Resets points of previewing lane and clear any preview.
        /// </summary>
        internal virtual void ResetRendererAndClearAnyPreview()
        {
            startWorldPoint = endWorldPoint = null;
            using (DrawingContext dc = visual.RenderOpen()) { }
        }

        /// <summary>
        /// Reset dragging mode.
        /// </summary>
        internal virtual void ResetDraggingOfPoint()
        {
            isStartPointBeingDragged = isEndPointBeingDragged = false;
        }

        /// <summary>
        /// Moves point which is dragged.
        /// </summary>
        /// <param name="cursorPointOnCanvas">Cursor point as a new point of movement (dragging).</param>
        internal virtual void MovePointOfLane(Point cursorPointOnCanvas)
        {
            startWorldPoint += GetVectorOfMoveOfPointIfItIsBeingDragged(cursorPointOnCanvas, isStartPointBeingDragged);
            endWorldPoint += GetVectorOfMoveOfPointIfItIsBeingDragged(cursorPointOnCanvas, isEndPointBeingDragged);
            RenderBuildingLane(cursorPointOnCanvas);
        }

        /// <summary>
        /// Gets vector of move of point if the point is being dragged and update the previous reference point for
        /// dragging.
        /// </summary>
        /// <param name="cursorPointOnCanvas">Cursor point as a new point of movement (dragging).</param>
        /// <param name="isBeingDragged">Determines whether certain point is being dragged.</param>
        /// <returns>Vector of move of point.</returns>
        protected Vector GetVectorOfMoveOfPointIfItIsBeingDragged(Point cursorPointOnCanvas, bool isBeingDragged)
        {
            if (isBeingDragged)
            {
                Vector movement = (cursorPointOnCanvas - previousReferencePointForDragging) / PixelsPerMeter;
                previousReferencePointForDragging = cursorPointOnCanvas;
                return movement;
            }
            return new Vector();
        }

        /// <summary>
        /// Initialize dragging mode for point if any is sufficiently close to cursor point.
        /// </summary>
        /// <param name="cursorPointOnCanvas">Cursor point on canvas.</param>
        internal virtual void InitializeDraggingModeOfAPointIfAnyIsNear(Point cursorPointOnCanvas)
        {
            IList<double> squaredDistancesOfCanvasPointToWorldLanePoints =
                GetSquaredDistancesOfCanvasPointToWorldLanePoints(cursorPointOnCanvas);
            SetPointToDraggingModeIfCursorIsNearAndDragging(ref isStartPointBeingDragged, cursorPointOnCanvas,
                squaredDistancesOfCanvasPointToWorldLanePoints[LanePoints.StartPoint],
                squaredDistancesOfCanvasPointToWorldLanePoints);
            SetPointToDraggingModeIfCursorIsNearAndDragging(ref isEndPointBeingDragged, cursorPointOnCanvas,
                squaredDistancesOfCanvasPointToWorldLanePoints[LanePoints.EndPoint],
                squaredDistancesOfCanvasPointToWorldLanePoints);
        }

        /// <summary>
        /// Set point to dragging mode if cursor is near to any point of lane.
        /// </summary>
        /// <param name="pointIsInDraggingMode">Flag to set if the point is switched to dragging mode.</param>        
        /// <param name="cursorPointOnCanvas">Cursor point on canvas.</param>
        /// <param name="squaredDistanceOfWorldPointFromCanvasPoint">Squared distance of cursor point to some 
        /// lane world point.</param>
        /// <param name="squaredDistancesOfRemainingWorldPointsFromCanvasPoint">List of squared distances
        /// of cursor point to all lane world points.</param>
        protected void SetPointToDraggingModeIfCursorIsNearAndDragging(ref bool pointIsInDraggingMode,
            Point cursorPointOnCanvas, double squaredDistanceOfWorldPointFromCanvasPoint,
            IList<double> squaredDistancesOfRemainingWorldPointsFromCanvasPoint)
        {
            if (squaredDistanceOfWorldPointFromCanvasPoint < DraggingCircleRadiusSquared)
            {
                if (squaredDistancesOfRemainingWorldPointsFromCanvasPoint.All(
                    i => i >= squaredDistanceOfWorldPointFromCanvasPoint))
                {
                    pointIsInDraggingMode = true;
                    previousReferencePointForDragging = cursorPointOnCanvas;
                    RenderBuildingLane(cursorPointOnCanvas);
                }
            }
        }

        /// <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 virtual IList<double> GetSquaredDistancesOfCanvasPointToWorldLanePoints(
            Point cursorPointOnCanvasPoint)
        {
            return new List<double>
            {
                (cursorPointOnCanvasPoint - TransformRealWorldPoint(startWorldPoint.Value)).LengthSquared,
                (cursorPointOnCanvasPoint - TransformRealWorldPoint(endWorldPoint.Value)).LengthSquared
            };
        }

        /// <summary>
        /// Transfer points from other lane building renderer to this lane building renderer.
        /// </summary>
        /// <param name="previousLaneBuildingRenderer">Renderer the lane points are transferred from.</param>
        internal void TransferPoints(LaneBuildingRenderer previousLaneBuildingRenderer)
        {
            startWorldPoint = previousLaneBuildingRenderer.startWorldPoint;
            endWorldPoint = previousLaneBuildingRenderer.EndWorldPoint;
            lastCursorPointOnCanvas = previousLaneBuildingRenderer.lastCursorPointOnCanvas;
        }
    }
}
