using Microscopic_Traffic_Simulator___Model.GeneralParameters;
using Microscopic_Traffic_Simulator___Model.SimulationControl;
using Microscopic_Traffic_Simulator___Model.TrafficObjects;
using System;
using System.Collections.Generic;

namespace Microscopic_Traffic_Simulator___Model.CellularTopologyObjects
{
    /// <summary>
    /// Manages generators and sensors in topology.
    /// </summary>
    public class GeneratorsManager
    {
        /// <summary>
        /// Set of generators containing at least one ticket. The set
        /// will be used in each transition function to generate new car if the generator's
        /// connected cell will be free.
        /// </summary>
        private HashSet<Generator> generatorsWithTicket = new HashSet<Generator>();

        /// <summary>
        /// List of generators.
        /// </summary>
        private List<Generator> generators = new List<Generator>();

        /// <summary>
        /// Reference to manager of cars in topology.
        /// </summary>
        private CarsManager carsManager;

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

        /// <summary>
        /// Reference to manager of gps records.
        /// </summary>
        private GpsRecordsManager gpsRecordsManager;

        /// <summary>
        /// Initializes manager of topology generators.
        /// </summary>
        /// <param name="carsManager">Cars manager reference.</param>
        /// <param name="transitionFunctionParameters">Transition function parameters reference.</param>
        /// <param name="gpsRecordsManager">GPS records manager reference.</param>
        internal GeneratorsManager(CarsManager carsManager, TransitionFunctionParameters transitionFunctionParameters,
            GpsRecordsManager gpsRecordsManager)
        {
            this.carsManager = carsManager;
            this.transitionFunctionParameters = transitionFunctionParameters;
            this.gpsRecordsManager = gpsRecordsManager;
        }

        /// <summary>
        /// Processes generators in the topology.
        /// <param name="currentModelTime">Current model time in simulation.</param>
        /// </summary>
        internal void ProcessGenerators(TimeSpan currentModelTime)
        {
            //initialize list of generators in which after getting their ticket 
            //the number of tickets will be dropped to zero.
            List<Generator> generatorsNowWithoutTicket = new List<Generator>();
            //for each the generators with ticket get new car. If the generator has free connected
            //cell it will return the car. If the number of tickets in the generator will drop to
            //zero then the generator will be added to generatorNowWithoutTicket list.
            foreach (Generator generator in generatorsWithTicket)
            {
                bool generatorNowWithoutTicket;
                Car newCar = generator.GetNewCar(out generatorNowWithoutTicket, transitionFunctionParameters);
                if (newCar != null)
                {
                    carsManager.AddNewCar(newCar, generator.ConnectedCell);
                    gpsRecordsManager.AddOutputRecord(new GpsRecordsManager.Record
                    {
                        Point = generator.ConnectedCell.Location,
                        DirectionNormalizedVector = generator.ConnectedCell.NormalizedDirectionVector,
                        Time = currentModelTime,
                        CarHashCode = newCar.GetHashCode()
                    });
                }
                if (generatorNowWithoutTicket)
                    generatorsNowWithoutTicket.Add(generator);
            }
            //remove generators which are now without ticket from generatorsWithTicket list
            generatorsNowWithoutTicket.ForEach(generatorNowWithoutTicket =>
                generatorsWithTicket.Remove(generatorNowWithoutTicket));
        }

        /// <summary>
        /// Initializes generator for its using in the simulator.
        /// </summary>
        /// <param name="generator">Generator to initialize.</param>
        /// <param name="simulationEventsGenerators">Reference to list of the all simulation events generator 
        /// where the generator will be added to.</param>
        internal void InitializeGeneratorForSimulation(Generator generator,
            List<ISimulationEventsGenerator> simulationEventsGenerators)
        {
            simulationEventsGenerators.Add(generator);
            generators.Add(generator);
            generator.WasZeroTicketsNowOneTicketIsAvailable += ZeroToOneTicketsInGenerator;
        }

        /// <summary>
        /// Detaches event handler <see cref="ZeroToOneTicketsInGenerator"/> from its publisher in generator.
        /// </summary>
        public void DetachFromGeneratorEvents()
        {
            foreach (Generator generator in generators)
            {
                generator.WasZeroTicketsNowOneTicketIsAvailable -= ZeroToOneTicketsInGenerator;
            }
        }

        /// <summary>
        /// Event handler processing when a number of tickets of any generator raises from zero to one. Then
        /// generator which is the sender of the event is added to set of generators with ticket.
        /// generators with tickets is
        /// </summary>
        /// <param name="sender">Generator whose number of tickets is raised from zero to one.</param>
        /// <param name="e">Not used event args.</param>
        private void ZeroToOneTicketsInGenerator(object sender, EventArgs e)
        {
            generatorsWithTicket.Add(sender as Generator);
        }

        /// <summary>
        /// Initialize sensors by loading real traffic data from files.
        /// </summary>
        internal void InitializeGenerators()
        {
            foreach (Generator generator in generators)
            {
                generator.InitializeTickets();
                if (generator is Sensor)
                {
                    (generator as Sensor).LoadInputRecords();
                }
            }
        }

        /// <summary>
        /// Clear list of generators with ticket and saves input records of sensors.
        /// </summary>
        internal void FinalizeGeneratorsWork()
        {
            generatorsWithTicket.Clear();
            foreach (Generator generator in generators)
            {
                if (generator is Sensor)
                {
                    (generator as Sensor).SaveInputRecords();
                }
            }
        }
    }
}
