using System;
using System.Collections.Generic;
using System.Windows;

namespace Microscopic_Traffic_Simulator___Model.GeometricObjects.Lanes
{
    /// <summary>
    /// Represents single bezier lane.
    /// </summary>
    [Serializable]
    public class BezierLane : Lane
    {
        /// <summary>
        /// Represents tuple of point and distance to other point.
        /// </summary>
        private struct PointAndDistanceToOtherPoint
        {
            /// <summary>
            /// Location representing some location.
            /// </summary>
            private Point point;
            /// <summary>
            /// Location representing some location.
            /// </summary>
            public Point Point { get { return point; } }

            /// <summary>
            /// Distance to some other location.
            /// </summary>
            private double distance;
            /// <summary>
            /// Distance to some other location.
            /// </summary>
            public double Distance { get { return distance; } }

            /// <summary>
            /// Initializes class by passing the point representing some location and other point representing
            /// some other location.
            /// </summary>
            /// <param name="point">Location.</param>
            /// <param name="otherPoint">Other location.</param>
            internal PointAndDistanceToOtherPoint(Point point, Point otherPoint)
            {
                this.point = point;
                distance = (otherPoint - point).Length;
            }
        }

        /// <summary>
        /// First control location of point.
        /// </summary>
        private Point firstControlLocation;
        /// <summary>
        /// First control location of point.
        /// </summary>
        public Point FirstControlLocation
        {
            get { return firstControlLocation; }
            set { firstControlLocation = value; }
        }

        /// <summary>
        /// Second control location of point.
        /// </summary>
        private Point secondControlLocation;
        /// <summary>
        /// Second control location of point.
        /// </summary>
        public Point SecondControlLocation
        {
            get { return secondControlLocation; }
            set { SecondControlLocation = value; }
        }

        /// <summary>
        /// Constructor for bezier lane.
        /// </summary>
        /// <param name="startLocation">Start location of lane.</param>
        /// <param name="firstControlLocation">First control location of lane.</param>
        /// <param name="secondControlLocation">Second control location of lane.</param>
        /// <param name="endLocation">End location of lane.</param>
        public BezierLane(Point startLocation, Point firstControlLocation, Point secondControlLocation,
            Point endLocation)
            : base(startLocation, endLocation)
        {
            this.firstControlLocation = firstControlLocation;
            this.secondControlLocation = secondControlLocation;
        }

        /// <summary>
        /// Computes points on the lane.
        /// </summary>
        /// <param name="lanePointsMaxDistance">Distance the points are distant from each other at most.</param>
        /// <returns>Points on the lane.</returns>
        internal override IEnumerable<Point> LanePoints(double lanePointsMaxDistance)
        {
            //return start point
            Point lastPoint;
            yield return lastPoint = startNode.Location;

            //initialization of variables and main cycle returning new lane point after each iteration
            double currentT = 0.0;
            double distanceDivided10 = lanePointsMaxDistance / 10.0;
            while (currentT < 1.0)
            {
                //initialize base t for computing next last point. The base t is t of the previous point.
                double tBase = currentT;
                //get range from current t to 1.0
                double tRange = 1.0 - currentT;
                //initialize addend to find the next last point by initializing it to half of the tRange                                
                double tAddend = tRange / 2.0;
                //divide addend by 2 until the point of t is distant less or equal than tenth of maximum distance
                //between points
                while ((lastPoint - ComputePoint(tBase + tAddend)).Length > distanceDivided10)
                {
                    tAddend /= 2.0;
                }
                //initialize previous and current point and distance to last lane point
                PointAndDistanceToOtherPoint previousPointAndDistanceToOtherPoint =
                    new PointAndDistanceToOtherPoint(lastPoint, lastPoint);
                PointAndDistanceToOtherPoint currentPointAndDistanceToOtherPoint =
                    new PointAndDistanceToOtherPoint(lastPoint, lastPoint);
                //find point distant from last point as much as the maximum distance of lane points
                while (currentPointAndDistanceToOtherPoint.Distance < lanePointsMaxDistance && currentT < 1.0)
                {
                    currentT += tAddend;
                    previousPointAndDistanceToOtherPoint = currentPointAndDistanceToOtherPoint;
                    currentPointAndDistanceToOtherPoint =
                        new PointAndDistanceToOtherPoint(ComputePoint(currentT), lastPoint);
                }

                //determine whether there is no other point except the end point
                if (currentT < 1.0)
                {
                    //determine whether the point before or after the current-t is nearer to current-t
                    if (previousPointAndDistanceToOtherPoint.Distance < currentPointAndDistanceToOtherPoint.Distance)
                    {
                        currentT -= tAddend;
                        lastPoint = previousPointAndDistanceToOtherPoint.Point;
                    }
                    else
                    {
                        lastPoint = currentPointAndDistanceToOtherPoint.Point;
                    }
                    yield return lastPoint;
                }
            }
            yield return endNode.Location;
        }

        /// <summary>
        /// Computes point on bezier curve from parameter 't'.
        /// </summary>
        /// <param name="t">Parameter 't'.</param>
        /// <returns>Pointo on bezier curve.</returns>
        private Point ComputePoint(double t)
        {
            Point p = new Point();
            double u = 1 - t;
            p.X = u * u * u * startNode.Location.X +
                3 * u * u * t * firstControlLocation.X +
                3 * u * t * t * secondControlLocation.X +
                t * t * t * endNode.Location.X;
            p.Y = u * u * u * startNode.Location.Y +
                3 * u * u * t * firstControlLocation.Y +
                3 * u * t * t * secondControlLocation.Y +
                t * t * t * endNode.Location.Y;
            return p;
        }
    }
}
