using Microscopic_Traffic_Simulator___Model.CellularTopologyObjects.GeneralParameters;
using Microscopic_Traffic_Simulator___Model.GeometricObjects;
using Microscopic_Traffic_Simulator___Model.GeometricObjects.Lanes;
using Microscopic_Traffic_Simulator___Model.GeometricObjects.Nodes;
using Microscopic_Traffic_Simulator___Model.SimulationControl;
using Microscopic_Traffic_Simulator___Model.TrafficObjects;
using System;
using System.Collections.Generic;
using System.Threading;

namespace Microscopic_Traffic_Simulator___Model.CellularTopologyObjects
{
    /// <summary>
    /// Class representing cellular topology.
    /// </summary>
    public class CellularTopology : ISimulationEventsGenerator, IDisposable
    {
        /// <summary>
        /// Cars manager reference.
        /// </summary>
        private CarsManager carsManager = new CarsManager();
        /// <summary>
        /// Cars manager reference.
        /// </summary>
        public CarsManager CarsManager { get { return carsManager; } }

        /// <summary>
        /// Generators manager reference.
        /// </summary>
        private GeneratorsManager generatorsManager;
        /// <summary>
        /// Generators manager reference.
        /// </summary>
        public GeneratorsManager GeneratorsManager { get { return generatorsManager; } }

        /// <summary>
        /// Gps records manager reference.
        /// </summary>
        private GpsRecordsManager gpsRecordsManager;
        /// <summary>
        /// Gps records manager reference.
        /// </summary>
        public GpsRecordsManager GpsRecordsManager { get { return gpsRecordsManager; } }

        /// <summary>
        /// Simulation object which controls the simulation on the cellular topology.
        /// </summary>
        private Simulation simulation;
        /// <summary>
        /// Simulation object which controls the simulation on the cellular topology.
        /// </summary>        
        public Simulation Simulation { get { return simulation; } }

        /// <summary>
        /// Determines whether the first simulation step was performed.
        /// </summary>
        private bool firstSimulationStepPerformed;

        /// <summary>
        /// Mutex ensuring that cars are not rendering when the transition function is performing
        /// and vice versa.
        /// </summary>
        private Mutex carsDictionariesMutex = new Mutex();
        /// <summary>
        /// Mutex ensuring that cars are not rendering when the transition function is performing
        /// and vice versa.
        /// </summary>
        public Mutex CarsDictionariesMutex { get { return carsDictionariesMutex; } }

        /// <summary>
        /// Number of simulation steps performed.
        /// </summary>
        private ulong simulationSteps = 0;
        /// <summary>
        /// Number of simulation steps performed.
        /// </summary>        
        public ulong SimulationSteps { get { return simulationSteps; } }

        /// <summary>
        /// Number of simulation steps after which the simulation control pauses simulation.
        /// </summary>
        private ulong simulationStepsToPause = ulong.MaxValue;
        /// <summary>
        /// Number of simulation steps after which the simulation control pauses simulation.
        /// </summary>        
        public ulong SimulationStepsToPause
        {
            get { return simulationStepsToPause; }
            set { simulationStepsToPause = value; }
        }
        /// <summary>
        /// Determines whether the number of simulation steps to pause is higher than number
        /// of simulation steps and whether the simulation should be paused.
        /// </summary>
        private bool IsSimulationStepsToPauseReached
        {
            get { return simulationSteps >= simulationStepsToPause; }
        }

        /// <summary>
        /// Event handler of change of number of simulation steps.
        /// </summary>
        public event EventHandler SimulationStepsChanged;

        /// <summary>
        /// Event informing that next transition function already started.
        /// </summary>
        public event EventHandler<DateTimeEventArgs> NextTransitionFunctionStarted;        

        /// <summary>
        /// Cellular topology priority as the simulation action generator.
        /// </summary>
        private const int CellularTopologySimulationPriority = 1;

        /// <summary>
        /// Cellular topology priority as the simulation action generator.
        /// </summary>
        public int Priority { get { return CellularTopologySimulationPriority; } }                

        /// <summary>
        /// Checks if the number of simulation steps is higher than the number of simulation steps to pause.
        /// </summary>
        public bool IsPauseScheduled { get { return IsSimulationStepsToPauseReached; } }

        /// <summary>
        /// Reference to simulation parameters.
        /// </summary>
        private Parameters parameters;
        /// <summary>
        /// Reference to simulation parameters.
        /// </summary>        
        public Parameters Parameters { get { return parameters; } }        

        /// <summary>
        /// Constructor when the cellular topology is build and simulation is initialized.
        /// </summary>
        /// <param name="geometricTopology">Geometric topology which the cellular         
        /// topology is build from.</param>        
        /// <param name="parameters">Reference to simulation parameters.</param>
        public CellularTopology(GeometricTopology geometricTopology, Parameters parameters)
        {
            this.parameters = parameters;
            gpsRecordsManager = new GpsRecordsManager(carsManager, parameters.CellularTopologyParameters);
            generatorsManager = new GeneratorsManager(carsManager, parameters.TransitionFunctionParameters,
                gpsRecordsManager);            
            BuildCellularTopology(geometricTopology, parameters.CellularTopologyParameters);
            InitializeSimulation(geometricTopology);
        }

        /// <summary>
        /// Method for building cellular topology from geometric topology.
        /// </summary>
        /// <param name="geometricTopology">Geometric topology which the cellular 
        /// topology is build from.</param>
        /// <param name="cellularTopologyParameters">Reference to cellular topology parameters.</param>
        private void BuildCellularTopology(GeometricTopology geometricTopology,
            CellularTopologyParameters cellularTopologyParameters)
        {
            new CellularTopologyBuilder(geometricTopology, cellularTopologyParameters).Build();
        }

        /// <summary>
        /// Method for initializing simulation. The cellular topology itself and car generators
        /// are added to simulations's calendar.
        /// </summary>
        /// <param name="geometricTopology">Reference of geometric topology which is used for simulation.</param>        
        private void InitializeSimulation(GeometricTopology geometricTopology)
        {
            firstSimulationStepPerformed = false;
            List<ISimulationEventsGenerator> simulationEventsGenerators = new List<ISimulationEventsGenerator>();
            simulationEventsGenerators.Add(this);
            foreach (Lane lane in geometricTopology.Lanes)
            {
                if (lane.StartNode.ContainsGenerator)
                {
                    generatorsManager.InitializeGeneratorForSimulation(
                        lane.StartNode.Generator, simulationEventsGenerators);
                }
                foreach (InnerNode laneInnerNode in lane.InnerNodes)
                {
                    if (laneInnerNode is SensorNode)
                    {
                        Sensor sensor = (laneInnerNode as SensorNode).Sensor;
                        generatorsManager.InitializeGeneratorForSimulation(sensor, simulationEventsGenerators);
                        sensor.CellularTopologyParameters = parameters.CellularTopologyParameters;
                    }
                }
            }
            simulation = new Simulation(simulationEventsGenerators);
        }        

        /// <summary>
        /// Method for getting the time to performing the next transition function.
        /// </summary>
        /// <param name="random">Random instance which is not used.</param>
        /// <returns>Time to performing the next transition function.</returns>
        public TimeSpan GetTimeToNextAction(Random random)
        {
            if (firstSimulationStepPerformed)
            {
                return parameters.CellularTopologyParameters.P2_SimulationStepInterval;
            }
            else
            {
                firstSimulationStepPerformed = true;
                return TimeSpan.Zero;
            }
        }

        /// <summary>
        /// Method for performing transition function.
        /// </summary>
        /// <param name="random">Random instance which is used by cars to perform their transition function.</param>
        public void PerformAction(Random random)
        {
            //store to temporar variable the DateTime of planned performing start 
            //of the last transition function.
            DateTime dateTimeNow = DateTime.Now;
            //ensure that car dictionaries are not used by renderer
            carsDictionariesMutex.WaitOne();
            //notiy about start of simulation step.
            OnNextTransitionFunctionStarted(dateTimeNow);

            carsManager.SwapCarsPreviousAndCurrentDictionaries();
            carsManager.ProcessCarsOutsideOfTopology();
            generatorsManager.ProcessGenerators(simulation.CurrentModelTime);
            carsManager.PerformTransitionForAllCars(random, simulation.CurrentModelTime);
            gpsRecordsManager.ProcessInputGPSRecords(simulation.CurrentModelTime);

            //the cars mutex can be now released.
            carsDictionariesMutex.ReleaseMutex();

            carsManager.PrepareCarsForNextSimulationStep();
            gpsRecordsManager.RecordCarsGPSLocations(simulation.CurrentModelTime);

            //increment number of simulation steps
            simulationSteps++;
            OnSimulationStepsChanged();
        }                        
        
        /// <summary>
        /// Firing event when number of simulation steps is changed.
        /// </summary>
        private void OnSimulationStepsChanged()
        {
            if (SimulationStepsChanged != null)
            {
                SimulationStepsChanged(this, EventArgs.Empty);
            }
        }

        /// <summary>
        /// Resets the number of simulation steps to pause.
        /// </summary>
        private void ResetSimulationStepsToPause()
        {
            simulationStepsToPause = ulong.MaxValue;
        }

        /// <summary>
        /// When simulation is continued or stopped after reaching the number of simulation steps to
        /// pause the number of simulation steps to pause is reset.
        /// </summary>
        private void ResetSimulationStepsToPauseIfReached()
        {
            if (IsSimulationStepsToPauseReached)
            {
                ResetSimulationStepsToPause();
            }
        }

        /// <summary>
        /// Performs operations before running, continuing or doing step in the simulation.
        /// </summary>
        private void RunOrStep()
        {
            ResetSimulationStepsToPauseIfReached();
            if (simulation.SimulationState == SimulationState.NotRunning)
            {
                generatorsManager.InitializeGenerators();
                gpsRecordsManager.LoadInputRecordsAndInitializeOutputRecords();                
            }
        }

        /// <summary>
        /// Runs simulation.
        /// <param name="seed">Seed to be used in the simulation.</param>
        /// </summary>
        public void Run(int? seed = null)
        {
            RunOrStep();
            simulation.Run(seed);
        }        

        /// <summary>
        /// Perform step in simulation.
        /// <param name="seed">Seed to be used in the simulation.</param>
        /// </summary>
        public void StepForward(int? seed = null)
        {
            RunOrStep();
            simulation.StepForward(seed);
        }

        /// <summary>
        /// Stops simulation.
        /// </summary>
        public void Stop()
        {
            simulation.Stop();                        
            simulationSteps = 0;
            firstSimulationStepPerformed = false;
            OnSimulationStepsChanged();            
            ResetSimulationStepsToPause();
            carsManager.ClearCars();
            generatorsManager.FinalizeGeneratorsWork();
            gpsRecordsManager.ResetGPSInputRecordsInformationAndSaveInputRecords();
        }        

        /// <summary>
        /// Pauses simulation.
        /// </summary>
        public void Pause()
        {
            simulation.Pause();
        }

        /// <summary>
        /// Fires the event informing that next transition function started to be performed.
        /// </summary>
        /// <param name="dateTimeEventArg">Time of the scheduled start of transition function.</param>
        private void OnNextTransitionFunctionStarted(DateTime dateTimeEventArg)
        {
            if (NextTransitionFunctionStarted != null)
                NextTransitionFunctionStarted(this, new DateTimeEventArgs() { DateTime = dateTimeEventArg });
        }                

        /// <summary>
        /// Dispose disposable fields.
        /// </summary>
        /// <param name="disposing">Flag indicating whether to release managed resources.</param>
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (carsDictionariesMutex != null)
                {
                    carsDictionariesMutex.Dispose();
                    carsDictionariesMutex = null;
                }
                if (simulation != null)
                {
                    simulation.Dispose();
                    simulation = null;
                }
            }
        }

        /// <summary>
        /// Disposes cellular topology resources.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }        
    }
}
