using Microscopic_Traffic_Simulator___Model.TrafficObjects;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;

namespace Microscopic_Traffic_Simulator___Model.CellularTopologyObjects
{
    /// <summary>
    /// Manages cars in cellular topology.
    /// </summary>
    public class CarsManager
    {
        /// <summary>
        /// Dictionary containing all cars with their first cells which they occupied before
        /// last transition function.
        /// </summary>
        private Dictionary<Car, Cell> cellsWithCarInPreviousStep = new Dictionary<Car, Cell>();
        /// <summary>
        /// Read only dictionary containing all cars with their first cells which they occupied
        /// before last transition function.
        /// </summary>
        public ReadOnlyDictionary<Car, Cell> CellsWithCarInPreviousStep
        {
            get { return new ReadOnlyDictionary<Car, Cell>(cellsWithCarInPreviousStep); }
        }

        /// <summary>
        /// Dictionary containing all cars with their first cells which they occupied after
        /// last transition function.
        /// </summary>
        private Dictionary<Car, Cell> cellsWithCarInCurrentStep = new Dictionary<Car, Cell>();
        /// <summary>
        /// Read only dictionary containing all cars with their first cells which they occupied 
        /// after last transition function.
        /// </summary>
        public ReadOnlyDictionary<Car, Cell> CellsWithCarInCurrentStep
        {
            get { return new ReadOnlyDictionary<Car, Cell>(cellsWithCarInCurrentStep); }
        }

        /// <summary>
        /// Swap dictionary of cars' first cells before the last transition function with dictionary of cars' first 
        /// cells after the last transition function. The dictionary of cars' first cells before the last transition 
        /// function will be updated by upcoming performing of the last transition function.
        /// </summary>
        internal void SwapCarsPreviousAndCurrentDictionaries()
        {
            Dictionary<Car, Cell> helperDict = cellsWithCarInPreviousStep;
            cellsWithCarInPreviousStep = cellsWithCarInCurrentStep;
            cellsWithCarInCurrentStep = helperDict;
        }

        /// <summary>
        /// Processes cars which are already outside of the topology.
        /// </summary>
        internal void ProcessCarsOutsideOfTopology()
        {
            //initialize list of cars which are to be removed from topology because of their
            //exiting of topology.
            HashSet<Car> carsToRemove = new HashSet<Car>();
            //check if any car is already completely outside of topology. If yes add it to
            //carsToRemove list.
            foreach (KeyValuePair<Car, Cell> frontCellWithCar in cellsWithCarInCurrentStep)
            {
                Car car = frontCellWithCar.Key;
                if (!car.IsStillInTopology)
                {
                    car.RemoveCarFromCells();
                    carsToRemove.Add(car);
                }
            }
            //remove cars in carsToRemove list from the both dictionaries of cars' first cells.
            foreach (Car carToRemove in carsToRemove)
            {
                cellsWithCarInPreviousStep.Remove(carToRemove);
                cellsWithCarInCurrentStep.Remove(carToRemove);
            }
        }

        /// <summary>
        /// Performs transition for all cars.
        /// </summary>
        /// <param name="random">Random instance to make stochastic character of cars' transitions.</param>
        /// <param name="currentModelTime">Current model time in simulation.</param>
        internal void PerformTransitionForAllCars(Random random, TimeSpan currentModelTime)
        {
            //perform transition for all cars.
            foreach (KeyValuePair<Car, Cell> frontCellWithCar in cellsWithCarInPreviousStep)
            {
                Car car = frontCellWithCar.Key;
                Cell newFrontCell = car.PerformTransition(random);
                //check if car reaches a sensor.
                Sensor followingSensor = newFrontCell.FollowingCell == null ? null : newFrontCell.FollowingCell.Sensor;
                if (followingSensor != null)
                {
                    //check if the sensor has already recorded the car.
                    if (!car.IsRecordedBySensor)
                    {
                        //create new output record in the sensor.
                        followingSensor.AddOutputRecord(currentModelTime, car.NewSpeed);
                    }
                }
                cellsWithCarInCurrentStep[car] = newFrontCell;
            }
        }

        /// <summary>
        /// Prepare each car for next simulation step.
        /// </summary>
        internal void PrepareCarsForNextSimulationStep()
        {
            foreach (KeyValuePair<Car, Cell> frontCellWithCar in cellsWithCarInCurrentStep)
            {
                Car car = frontCellWithCar.Key;
                car.PrepareForNextSimulationStep();
            }
        }

        /// <summary>
        /// Adds new car to dictionaries containing cars in topology.
        /// </summary>
        /// <param name="newCar">Car to add.</param>
        /// <param name="cell">Cell where the car is placed.</param>
        internal void AddNewCar(Car newCar, Cell cell)
        {
            cellsWithCarInPreviousStep.Add(newCar, cell);
            cellsWithCarInCurrentStep.Add(newCar, cell);
        }

        /// <summary>
        /// Clear cars from cells and clear dictionaries of cars' first cells.
        /// </summary>
        internal void ClearCars()
        {
            foreach (KeyValuePair<Car, Cell> frontCellWithCar in cellsWithCarInPreviousStep)
            {
                frontCellWithCar.Key.RemoveCarFromCells();
            }

            cellsWithCarInPreviousStep.Clear();
            cellsWithCarInCurrentStep.Clear();
        }
    }
}
