using Microscopic_Traffic_Simulator___Model.CellularTopologyObjects.CellWalkers;
using Microscopic_Traffic_Simulator___Model.GeneralParameters;
using System;
using System.Diagnostics;

namespace Microscopic_Traffic_Simulator___Model.CellularTopologyObjects
{
    /// <summary>
    /// Class representing car
    /// </summary>
    public class Car
    {
        /// <summary>
        /// Speed computed two transition function ago.
        /// </summary>
        private int previousSpeed;
        /// <summary>
        /// Speed computed two transition function ago.
        /// </summary>        
        internal int PreviousSpeed { get { return previousSpeed; } }

        /// <summary>
        /// Speed computed in the previous transition function.
        /// </summary>        
        private int currentSpeed;
        /// <summary>
        /// Speed computed in the previous transition function.
        /// </summary>                
        internal int CurrentSpeed { get { return currentSpeed; } }

        /// <summary>
        /// Speed computed in the current transition function.
        /// </summary>
        private int newSpeed;
        /// <summary>
        /// Speed computed in the current transition function.
        /// </summary>
        internal int NewSpeed { get { return newSpeed; } }

        /// <summary>
        /// Car's length in number of cells it uses.
        /// </summary>
        private int length;
        /// <summary>
        /// Car's length in number of cells it uses.
        /// </summary>        
        public int Length { get { return length; } }

        /// <summary>
        /// The first car's cell.
        /// </summary>
        private Cell firstCell;

        /// <summary>
        /// Flag indicating if car has not already left the topology.
        /// </summary>
        internal bool IsStillInTopology { get { return outside < length; } }

        /// <summary>
        /// Number of cells which car used if they would exist after the end of the blind lane.
        /// </summary>
        private int outside = 0;
        /// <summary>
        /// Number of cells which car used if they would exist after the end of the blind lane.
        /// </summary>
        public int Outside { get { return outside; } }

        /// <summary>
        /// Reference to transition function parameters.
        /// </summary>
        private TransitionFunctionParameters parameters;        

        /// <summary>
        /// Determines whether the car is already recorded by sensor. This is useful when car is moving so slowly that
        /// during more simulation steps is the car partly in the topology.
        /// </summary>
        private bool isRecordedBySensor = false;
        /// <summary>
        /// The getter for the isRecordedBySensor attribute implemented by such a specific way that when the getter
        /// is the called the first time the false value is returned while isRecordedBySensor is set to true which is
        /// returned in all successive calls of getter.
        /// </summary>
        internal bool IsRecordedBySensor
        {
            get
            {
                bool toReturn = isRecordedBySensor;
                if (!isRecordedBySensor)
                    isRecordedBySensor = true;
                return toReturn;
            }
        }

        /// <summary>
        /// Constructor of car.
        /// </summary>
        /// <param name="firstCell">First cell of car. When the car is generated it takes
        /// only one cell as it is generating in the beginning of lane.</param>
        /// <param name="speed">Initial speed of the car.</param>
        /// <param name="transitionFunctionParameters">Transition function parameters reference.</param>
        internal Car(Cell firstCell, int speed, TransitionFunctionParameters transitionFunctionParameters)
        {
            this.firstCell = firstCell;
            this.previousSpeed = this.currentSpeed = speed;
            parameters = transitionFunctionParameters;
            length = 4;
        }

        /// <summary>
        /// Perform transition of car which consits of computing new speed and taking the new cells. 
        /// </summary>
        /// <param name="random">Random instance needed to simulate random component 
        /// of transition.</param>
        /// <returns>New first cell of car.</returns>
        internal Cell PerformTransition(Random random)
        {
            ComputeNewSpeed(random);
            return SetToNextCells();
        }

        /// <summary>
        /// Method for computing new speed of car (Korcek et. al.).
        /// </summary>
        /// <param name="random">Random instance needed to simulate random component 
        /// of transition.</param>
        private void ComputeNewSpeed(Random random)
        {
            //apply speed from previous transition to newSpeed. newSpeed will be updated to new
            //speed computed from this transition.
            newSpeed = currentSpeed;

            //acceleration step
            if (newSpeed < 30)
            {
                if (random.NextDouble() < parameters.P7_AccelerationProbability)
                    newSpeed++;
            }

            //get gap length and acceleration of next vehicle
            Gap gap = ComputeGap();
            if (gap.Length == int.MaxValue ||
                (gap.Length + gap.FollowingCarAcceleration) > newSpeed)
            {
                //Car does not need to brake. Random slowing is applied.
                if (newSpeed > 1)
                {

                    if (newSpeed < parameters.P6_DecProbabilitySpeedBound)
                    {
                        if (random.NextDouble() < parameters.P5_LowSpeedDecProbability)
                            newSpeed--;
                    }
                    else
                    {
                        if (random.NextDouble() < parameters.P8_HighSpeedDecProbability)
                            newSpeed--;
                    }
                }
            }
            else
            {
                //braking because of too small gap
                if (gap.FollowingCarAcceleration > 0)
                    newSpeed = (int)Math.Round((gap.Length + gap.FollowingCarAcceleration) /
                        parameters.P9_DecRateWhenLeadingAcc);
                else
                    newSpeed = (int)Math.Round((gap.Length + gap.FollowingCarAcceleration) /
                        parameters.P10_DecRateWhenLeadingDec);
            }

            Debug.Assert(newSpeed <= gap.Length, "Speed larger than gap");
            //Debug.Assert(newSpeed >= 0, "Negative new speed");
        }

        /// <summary>
        /// Computation of gap to next car if there is any considerable near.
        /// </summary>        
        /// <returns>Gap structure containing information about gap</returns>
        private Gap ComputeGap()
        {
            LimitedStepsCellWalker cellWalker = new LimitedStepsCellWalker(
                new ForwardCellWalker(firstCell.FollowingCell), newSpeed * 2);
            foreach (Cell cell in cellWalker.GetNext())
            {
                if (cell.Car != null)
                {
                    return new Gap(cellWalker.CellsWalked,
                        cell.Car.CurrentSpeed - cell.Car.PreviousSpeed);
                }
            }
            return new Gap(int.MaxValue);
        }

        /// <summary>
        /// Take new cells by car according to new speed. Increase
        /// outside field if the car is leaving the topology.
        /// </summary>
        /// <returns>New first cell.</returns>
        private Cell SetToNextCells()
        {
            LimitedMinimumStepsCellWalker walker = new LimitedMinimumStepsCellWalker(
                new ForwardCellWalker(firstCell.FollowingCell), newSpeed);
            foreach (Cell cell in walker.GetNext())
            {
                if (cell == null)
                    outside++;
                else
                    cell.Car = this;
            }
            firstCell = walker.LastCell ?? firstCell;
            return firstCell;
        }

        /// <summary>
        /// Prepare car for the next simulation step by assigning current speed to previous
        /// speed and new speed to current speed. Then cells which car does not currently take
        /// according to their length are marked as free. This method is called for all cars after
        /// performing transition for all cars to catch the fact that cars have reaction delay so
        /// when car computes transition and looks for the next car's acceleration the speeds used
        /// for calculation the acceleration are from the previous two transition functions and
        /// current new speed (if already computed) is not used.
        /// </summary>
        internal void PrepareForNextSimulationStep()
        {
            //reassign fields of speeds
            previousSpeed = currentSpeed;
            currentSpeed = newSpeed;

            //go backwards the 'length' number of steps
            LimitedMinimumStepsCellWalker walker = new LimitedMinimumStepsCellWalker(
                new BackwardCellWalker(firstCell.PreviousCell), length);
            foreach (Cell cell in walker.GetNext())
                if (cell == null)
                    return;

            //following backward cells which were occupied by car in previous topology state 
            //are marked as free.
            RemoveCarFromCells(walker.LastCell);
        }

        /// <summary>
        /// Mark cells as free.
        /// </summary>
        /// <param name="fromCell">Cell to start by.</param>
        private void RemoveCarFromCells(Cell fromCell)
        {
            BackwardCarCellWalker walker = new BackwardCarCellWalker(fromCell, this);
            foreach (Cell cell in walker.GetNext())
            {
                cell.Car = null;
            }
        }

        /// <summary>
        /// Remove entire car from topology. Useful when clearing all topology.
        /// </summary>
        internal void RemoveCarFromCells()
        {
            RemoveCarFromCells(firstCell);
        }
    }
}
