using System.Windows;
using System.Windows.Media;

namespace Microscopic_Traffic_Simulator.Renderers
{
    /// <summary>
    /// Base class for drawing visual renderer.
    /// </summary>
    abstract class VisualCanvasRenderer
    {
        /// <summary>
        /// Drawing visual to render to.
        /// </summary>
        protected DrawingVisual visual;

        /// <summary>
        /// Reference view point determining position of real-world point on the top-left corner of the canvas.
        /// </summary>
        private Point referenceViewPoint = new Point(0.0, 0.0);

        /// <summary>
        /// Number of meters per one pixel determining zoom level.
        /// </summary>
        private double pixelsPerMeter = 1.0;
        /// <summary>
        /// Number of meters per one pixel determining zoom level.
        /// </summary>
        protected double PixelsPerMeter { get { return pixelsPerMeter; } }

        /// <summary>
        /// Last position of mouse location used to compute the delta of moved canvas.
        /// </summary>
        private Point? lastMouseLocationOfCanvasMove = null;

        /// <summary>
        /// Initialization of visual canvas renderer.
        /// </summary>
        /// <param name="visual">Drawing visual to render to.</param>
        /// <param name="canvasViewModel">Canvas viewmodel to get the data to redner.</param>
        protected VisualCanvasRenderer(DrawingVisual visual)
        {
            this.visual = visual;            
        }

        /// <summary>
        /// Changes zoom rate in pixels per meter to new zoom rate while leaving the location under the mouse
        /// cursor on the same place.
        /// </summary>
        /// <param name="newPixelsPerMeter">New zoom rate in pixels per meter.</param>
        /// <param name="zoomPoint">The location which location on canvas should be same 
        /// after changing the zoom.</param>
        internal void ChangeZoom(double newPixelsPerMeter, Point zoomPoint)
        {            
            //x/z1 + r1 = x/z2 + r2
            //r2 = x/z1 + r1 - x/z2
            Vector zoomPointAsVector = zoomPoint - new Point(0.0, 0.0);

            referenceViewPoint = zoomPointAsVector / pixelsPerMeter + referenceViewPoint - zoomPointAsVector / newPixelsPerMeter;
            
            pixelsPerMeter = newPixelsPerMeter;
            Render(zoomPoint);
        }

        /// <summary>
        /// Transforms real-world point to point on canvas.
        /// </summary>
        /// <param name="point">Real-world point.</param>
        /// <returns>Canvas point.</returns>
        protected Point TransformRealWorldPoint(Point point)
        {            
            //compute transform
            return (point - referenceViewPoint) * pixelsPerMeter + new Point(0.0, 0.0);
        }

        /// <summary>
        /// Transform point on canvas to real-world point.
        /// </summary>
        /// <param name="point">Canvas point.</param>
        /// <returns>Real-world point.</returns>
        protected Point TransformCanvasPoint(Point point)
        {
            return (point - new Point(0.0, 0.0)) / pixelsPerMeter + referenceViewPoint;
        }

        /// <summary>
        /// Get new mouse cursor location used to move the canvas. If the mouse location already exists then the 
        /// reference view point is moved according to delta.
        /// </summary>
        /// <param name="mouseLocationOnCanvas">New mouse cursor location.</param>
        internal void PushNewMouseLocationOfCanvasMove(Point mouseLocationOnCanvas)
        {
            if (lastMouseLocationOfCanvasMove != null)
            {
                Point currentMouseLocationOfCanvasMove = mouseLocationOnCanvas;
                referenceViewPoint += (lastMouseLocationOfCanvasMove.Value - currentMouseLocationOfCanvasMove)
                    / pixelsPerMeter;
                Render(mouseLocationOnCanvas);
            }
            lastMouseLocationOfCanvasMove = mouseLocationOnCanvas;
        }

        /// <summary>
        /// Reset last mouse cursor location indicating that move of canvas is finished.
        /// </summary>
        internal void ResetCanvasMove()
        {
            lastMouseLocationOfCanvasMove = null;
        }        

        /// <summary>
        /// Method defining render method to be implemented for all renderers.
        /// <param name="currentMouseLocation">Current mouse location</param>
        /// </summary>
        protected abstract void Render(Point currentMouseLocation);        
    }
}
