using Microscopic_Traffic_Simulator___Model.CellularTopologyObjects;
using Microscopic_Traffic_Simulator___Model.SimulationControl;
using Microscopic_Traffic_Simulator___Model.Utilities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Media;
using System.Windows.Threading;

namespace Microscopic_Traffic_Simulator.Renderers
{
    /// <summary>
    /// Enumeration type containing values representing rendering of one single simulation step and
    /// rendering of more following simulation steps.
    /// </summary>
    enum Rendering { Single, Continuous }

    /// <summary>
    /// Simulation traffic renderer.
    /// </summary>
    class SimulationTrafficRenderer : VisualCanvasRenderer
    {
        /// <summary>
        /// Cellular topology to render.
        /// </summary>
        private CellularTopology cellularTopology;
        /// <summary>
        /// Cellular topology to render.
        /// </summary>
        internal CellularTopology CellularTopology { set { cellularTopology = value; } }

        /// <summary>
        /// DateTime of last performing of the transition function.
        /// </summary>
        private DateTime dateTimeOfLastSimStep;        

        /// <summary>
        /// Indicates whether the last performed simulation steps was performed either as a single simulation step
        /// or as a simulation with maximum speed. This is used to prevent from lag in graphics when running
        /// continous simulation after a single simulation step or after simulation with maximum speed.
        /// </summary>
        private bool wasSingleStepOrMaxSpeed;

        /// <summary>
        /// DateTime of last application of pause.
        /// </summary>
        private DateTime dateTimeOfLastPause;

        /// <summary>
        /// Initialization of simulation traffic renderer.
        /// </summary>
        /// <param name="visual">Drawing visual to render to.</param>       
        internal SimulationTrafficRenderer(DrawingVisual visual) : base(visual) { }
        
        /// <summary>
        /// Redraw the simulation traffic while distinguishing whether to use single or continous rendering
        /// <param name="currentMouseLocation">Current mouse location</param>
        /// </summary>
        protected override void Render(Point currentMouseLocation)
        {
            if (cellularTopology != null)
            {
                if (cellularTopology.Simulation.SimulationState == SimulationState.Running &&
                    !cellularTopology.Simulation.IsMaxSimulationSpeed)
                {
                    RenderSimulationTraffic(Rendering.Continuous);
                }
                else
                {
                    RenderSimulationTraffic(Rendering.Single);
                }
            }
        }

        /// <summary>
        /// Redraw the simulation traffic.
        /// </summary>
        internal void RenderSimulationTraffic(Rendering rendering = Rendering.Single)
        {
            using (DrawingContext dc = visual.RenderOpen())
            {
                //gain exclusive access to cars dictionaries in cellular topology.
                cellularTopology.CarsDictionariesMutex.WaitOne();
                Pen carPen = new Pen(Brushes.Green, 2.0 * PixelsPerMeter);
                Pen gpsCarPen = new Pen(Brushes.LightGreen, 2.0 * PixelsPerMeter);
                DateTime dateTimeNow = DateTime.Now;
                foreach (KeyValuePair<Car, Cell> frontCellWithCar in 
                    cellularTopology.CarsManager.CellsWithCarInPreviousStep)
                {
                    Car car = frontCellWithCar.Key;
                    Cell previousFirstCell = frontCellWithCar.Value;
                    Cell currentFirstCell = cellularTopology.CarsManager.CellsWithCarInCurrentStep[car];                    
                    DrawCar(rendering, dc, dateTimeNow, car.Outside, car.Length, previousFirstCell.Location,
                        currentFirstCell.Location, currentFirstCell.NormalizedDirectionVector, carPen);
                }
                //for each records in current time
                foreach (int carToRender in
                    cellularTopology.GpsRecordsManager.CarPreviousGPSInputLocations.Keys.Where(
                    i => cellularTopology.GpsRecordsManager.CarCurrentGPSInputLocations.ContainsKey(i)))
                {
                    DrawCar(rendering, dc, dateTimeNow, 0, 4, 
                        cellularTopology.GpsRecordsManager.CarPreviousGPSInputLocations[carToRender].Location,
                        cellularTopology.GpsRecordsManager.CarCurrentGPSInputLocations[carToRender].Location,
                        cellularTopology.GpsRecordsManager
                        .CarCurrentGPSInputLocations[carToRender].DirectionNormalizedVector, gpsCarPen);
                }
                
                cellularTopology.CarsDictionariesMutex.ReleaseMutex();
            }
        }        

        /// <summary>
        /// Draws car on canvas.
        /// </summary>
        /// <param name="rendering">Type of the rendering.</param>
        /// <param name="dc">Drawing context to use.</param>
        /// <param name="dateTimeNow">Current date time.</param>
        /// <param name="carOutsideOfTopology">Number of cells the car is outside of the topology.</param>
        /// <param name="carLength">Length of the car.</param>
        /// <param name="previousLocation">Location of the car in the previous simulation step.</param>
        /// <param name="currentLocation">Location of the car in the current simulation step.</param>
        /// <param name="normalizedDirectionVector">Direction vector of the car.</param>
        /// <param name="carsBrush">Brush to use to draw a car.</param>
        private void DrawCar(Rendering rendering, DrawingContext dc, DateTime dateTimeNow, int carOutsideOfTopology,
            int carLength, Point previousLocation, Point currentLocation, Vector carDirectionNormalizedVector, 
            Pen carPen)
        {
            Point location;
            if (rendering == Rendering.Continuous)
            {
                double timePortionElapsed =
                    (double)((dateTimeNow - dateTimeOfLastSimStep)
                    .Multiply(cellularTopology.Simulation.SimulationSpeed).Ticks) /
                    cellularTopology.Parameters.CellularTopologyParameters.P2_SimulationStepInterval.Ticks;
                timePortionElapsed = Math.Min(timePortionElapsed, 1.0);                
                location = previousLocation + (currentLocation + carDirectionNormalizedVector * carOutsideOfTopology * 
                    cellularTopology.Parameters.CellularTopologyParameters.P1_CellLength - previousLocation) * 
                    timePortionElapsed;
            }
            else if (rendering == Rendering.Single)
            {
                location = currentLocation;
            }
            else
            {
                throw new ApplicationException("Not all of rendering types are implemented.");
            }
            //to prevent from visualization of cars which are already outside of 
            //topology.            
            if ((location - previousLocation).Length - (currentLocation - previousLocation).Length <=
                carLength * cellularTopology.Parameters.CellularTopologyParameters.P1_CellLength)            
            {
                dc.DrawLine(carPen, TransformRealWorldPoint(location - carLength * carDirectionNormalizedVector),
                    TransformRealWorldPoint(location));                
            }
        }

        /// <summary>
        /// Event handler for the scheduled pause.
        /// </summary>
        /// <param name="sender">Object sender.</param>
        /// <param name="e">Event args.</param>
        void simulation_SimulationPaused(object sender, EventArgs e)
        {
            PauseApplied();
            Application.Current.Dispatcher.Invoke(() => RenderSimulationTraffic(), DispatcherPriority.Render);
        }

        /// <summary>
        /// Should be called when pause is applied. This can be useful to prevent from lag of 
        /// vehicles after pressing "start" in paused status.
        /// </summary>
        private void PauseApplied()
        {
            dateTimeOfLastPause = DateTime.Now;
        }

        /// <summary>
        /// Should be called when the simulation continutes. dateTimeOfLastSimStep is adjusted to
        /// prevent from lag of vehicles.
        /// </summary>
        private void PauseReleased()
        {
            dateTimeOfLastSimStep += DateTime.Now - dateTimeOfLastPause;
        }

        /// <summary>
        /// Should be called when the simulation speed is increased. dateTimeOfLastSimStep is
        /// adjusted to prevent from lag of vehicles.
        /// </summary>
        /// <param name="rate">A speed increase rate.</param>
        internal void SimulationFaster(double rate)
        {
            if (cellularTopology.Simulation.SimulationState != SimulationState.NotRunning)
            {
                dateTimeOfLastSimStep += (GetCurrentDateTime() - dateTimeOfLastSimStep).Divide(rate);
            }
        }

        /// <summary>
        /// Should be called when the simulation speed is decreased. dateTimeOfLastSimStep is
        /// adjusted to prevent from lag of vehicles.
        /// </summary>
        /// <param name="rate">A speed decrease rate.</param>
        internal void SimulationSlower(double rate)
        {
            if (cellularTopology.Simulation.SimulationState != SimulationState.NotRunning)
            {
                DateTime dateTimeNow = GetCurrentDateTime();
                dateTimeOfLastSimStep = dateTimeNow - (dateTimeNow - dateTimeOfLastSimStep).Multiply(rate);
            }
        }

        /// <summary>
        /// Obtain current date time while considering the state of the simulation.
        /// </summary>
        /// <returns>Current date time of simulation.</returns>
        private DateTime GetCurrentDateTime()
        {
            if (cellularTopology.Simulation.SimulationState == SimulationState.Running)
                return DateTime.Now;
            else if (cellularTopology.Simulation.SimulationState == SimulationState.Paused)
                return dateTimeOfLastPause;
            else
                throw new ApplicationException("Simulation speed increased when simulation is not running");
        }

        /// <summary>
        /// Method for operations related to rendering which are performing before the simulation starts or resumes.
        /// </summary>
        internal void Run()
        {
            if (cellularTopology.Simulation.SimulationState == SimulationState.Paused)
                PauseReleased();
            if (cellularTopology.Simulation.IsMaxSimulationSpeed)
            {
                wasSingleStepOrMaxSpeed = true;
            }
            else
            {
                if (wasSingleStepOrMaxSpeed)
                    AdjustLastSimulationStepTime();
                wasSingleStepOrMaxSpeed = false;
            }
        }

        /// <summary>
        /// Adjust time of last simulation step to prevent from lag in rendering cars after running simulation
        /// after single simulation step or simulation with maximum speed.
        /// </summary>
        private void AdjustLastSimulationStepTime()
        {
            dateTimeOfLastSimStep = DateTime.Now -
                cellularTopology.Parameters.CellularTopologyParameters.P2_SimulationStepInterval;
        }

        /// <summary>
        /// Perform step in simulation.
        /// </summary>
        internal void StepForward()
        {
            wasSingleStepOrMaxSpeed = true;
        }

        /// <summary>
        /// Stops simulation.
        /// </summary>
        internal void Stop()
        {
            RenderSimulationTraffic();
        }

        /// <summary>
        /// Pauses simulation.
        /// </summary>
        internal void Pause()
        {
            if (cellularTopology.Simulation.IsMaxSimulationSpeed)
                RenderSimulationTraffic(Rendering.Single);
            else
                RenderSimulationTraffic(Rendering.Continuous);
            PauseApplied();
            if (cellularTopology.Simulation.IsMaxSimulationSpeed)
                wasSingleStepOrMaxSpeed = true;
        }

        /// <summary>
        /// Detach event handlers from cellular topology which is going to be cancelled.
        /// </summary>
        internal void DetachEventHandlerFromCellularTopology()
        {
            if (cellularTopology != null)
            {
                cellularTopology.Simulation.SimulationPaused -= simulation_SimulationPaused;
                cellularTopology.Simulation.SingleSimulationActionPerformed -=
                    Simulation_SingleSimulationActionPerformed;
                cellularTopology.Simulation.SimulationSpeedChanged -= Simulation_SimulationSpeedChanged;
                cellularTopology.Simulation.IsMaxSimulationSpeedChange -= Simulation_IsMaxSimulationSpeedChange;
                cellularTopology.NextTransitionFunctionStarted -= cellularTopology_NextTransitionFunctionStarted;
                using (DrawingContext dc = visual.RenderOpen()) { } //clean canvas
            }
        }

        /// <summary>
        /// Attach event handlers from cellular topology which is initialized.
        /// </summary>
        internal void AttachEventHandlerFromCellularTopology()
        {
            cellularTopology.Simulation.SimulationPaused += simulation_SimulationPaused;
            cellularTopology.Simulation.SingleSimulationActionPerformed += Simulation_SingleSimulationActionPerformed;
            cellularTopology.Simulation.SimulationSpeedChanged += Simulation_SimulationSpeedChanged;
            cellularTopology.Simulation.IsMaxSimulationSpeedChange += Simulation_IsMaxSimulationSpeedChange;
            cellularTopology.NextTransitionFunctionStarted += cellularTopology_NextTransitionFunctionStarted;
        }

        /// <summary>
        /// Event handler for receiving date time of last simulation step.
        /// </summary>
        /// <param name="sender">Sender object.</param>
        /// <param name="e">Event args containing the time of the beginning of the last simulation step.</param>
        void cellularTopology_NextTransitionFunctionStarted(object sender, DateTimeEventArgs e)
        {
            dateTimeOfLastSimStep = e.DateTime;
        }

        /// <summary>
        /// Event handler for activation/deactivation of maximum simulation speed.
        /// </summary>
        /// <param name="sender">Sender object.</param>
        /// <param name="e">Unused event args.</param>
        void Simulation_IsMaxSimulationSpeedChange(object sender, EventArgs e)
        {
            if (cellularTopology.Simulation.SimulationState == SimulationState.Running)
            {
                if (cellularTopology.Simulation.IsMaxSimulationSpeed)
                    wasSingleStepOrMaxSpeed = true;
                else
                    wasSingleStepOrMaxSpeed = false;
            }
        }

        /// <summary>
        /// Event handler for simulation speed change.
        /// </summary>
        /// <param name="sender">Sender object.</param>
        /// <param name="e">Event args containing simulation speed change .</param>
        void Simulation_SimulationSpeedChanged(object sender, SimulationSpeedChangeEventArgs e)
        {
            if (e.OldSimulationSpeed < e.NewSimulationSpeed)
            {
                SimulationFaster(e.NewSimulationSpeed / e.OldSimulationSpeed);
            }
            else if (e.OldSimulationSpeed > e.NewSimulationSpeed)
            {
                SimulationSlower(e.OldSimulationSpeed / e.NewSimulationSpeed);
            }
        }

        /// <summary>
        /// Event receiver for rendering simulation traffic after the forward step button is pressed 
        /// and command of simulation step is completed.
        /// </summary>
        /// <param name="sender">Sender object.</param>
        /// <param name="e">Unused event args.</param>
        private void Simulation_SingleSimulationActionPerformed(object sender, EventArgs e)
        {
            Application.Current.Dispatcher.Invoke(() => RenderSimulationTraffic(), DispatcherPriority.Render);
        }
    }
}