using Microscopic_Traffic_Simulator___Model.CellularTopologyObjects.GeneralParameters;
using Microscopic_Traffic_Simulator___Model.Utilities;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.IO;
using System.Windows;

namespace Microscopic_Traffic_Simulator___Model.CellularTopologyObjects
{
    /// <summary>
    /// Manages GPS records for cellular topology.
    /// </summary>
    public partial class GpsRecordsManager
    {
        /// <summary>
        /// Reference to cars manager.
        /// </summary>
        private CarsManager carsManager;

        /// <summary>
        /// Path to file with input time-location tuples from cars
        /// </summary>
        private string pathToInGPSFile = "gpsin.txt";

        /// <summary>
        /// Path to file to output the time-location tuples from cars
        /// </summary>
        private string pathToOutGPSFile = "gpsout.txt";

        /// <summary>
        /// Output GPS records of all cars.
        /// </summary>
        private List<Record> outputRecords;

        /// <summary>
        /// Input GPS records of all cars.
        /// </summary>
        private List<Record> inputRecords;

        /// <summary>
        /// Dictionary mapping hash code of a recorded car to its location in previous simulation step.
        /// </summary>
        private Dictionary<int, LocationAndDirection> carPreviousGPSInputLocations =
            new Dictionary<int, LocationAndDirection>();
        /// <summary>
        /// Dictionary mapping hash code of a recorded car to its location in previous simulation step.
        /// </summary>
        public ReadOnlyDictionary<int, LocationAndDirection> CarPreviousGPSInputLocations
        {
            get { return new ReadOnlyDictionary<int, LocationAndDirection>(carPreviousGPSInputLocations); }
        }

        /// <summary>
        /// Dictionary mapping hash code of a recorded car to its location in current simulation step.
        /// </summary>
        private Dictionary<int, LocationAndDirection> carCurrentGPSInputLocations =
            new Dictionary<int, LocationAndDirection>();
        /// <summary>
        /// Dictionary mapping hash code of a recorded car to its location in current simulation step.
        /// </summary>
        public ReadOnlyDictionary<int, LocationAndDirection> CarCurrentGPSInputLocations
        {
            get { return new ReadOnlyDictionary<int, LocationAndDirection>(carCurrentGPSInputLocations); }
        }

        /// <summary>
        /// Input GPS records counter.
        /// </summary>
        private int gpsInputCarsCounter = 0;

        /// <summary>
        /// Set of cars in the previous step used to remove cars in current cars dictionary that are not in previous 
        /// cars dictionary.
        /// </summary>
        private HashSet<int> carsInPreviousStepNotInCurrentStep = new HashSet<int>();

        /// <summary>
        /// Reference to cellular topology parameters.
        /// </summary>
        private CellularTopologyParameters cellularTopologyParameters;

        /// <summary>
        /// Initializes GPS records manager.
        /// </summary>
        /// <param name="carsManager">Reference to cars manager.</param>
        /// <param name="cellularTopologyParameters">Reference to cellular topology parameters.</param>
        internal GpsRecordsManager(CarsManager carsManager, CellularTopologyParameters cellularTopologyParameters)
        {
            this.carsManager = carsManager;
            this.cellularTopologyParameters = cellularTopologyParameters;
        }

        /// <summary>
        /// Adds record to output records list.
        /// </summary>
        /// <param name="record">Record to add.</param>
        internal void AddOutputRecord(Record record)
        {
            outputRecords.Add(record);
        }

        /// <summary>
        /// Record location of each cars.
        /// <param name="currentModelTime">Current model time in simulation.</param>
        /// </summary>
        internal void RecordCarsGPSLocations(TimeSpan currentModelTime)
        {
            foreach (KeyValuePair<Car, Cell> frontCellWithCar in carsManager.CellsWithCarInCurrentStep)
            {
                AddOutputRecord(new Record()
                {
                    Point = frontCellWithCar.Value.Location +
                        new Vector(frontCellWithCar.Key.Outside * cellularTopologyParameters.P1_CellLength, 0.0),
                    DirectionNormalizedVector = frontCellWithCar.Value.NormalizedDirectionVector,
                    Time = currentModelTime + cellularTopologyParameters.P2_SimulationStepInterval,
                    CarHashCode = frontCellWithCar.Key.GetHashCode()
                });
            }
        }

        /// <summary>
        /// Processes input gps records.
        /// <param name="currentModelTime">Current model time in simulation.</param>
        /// </summary>
        internal void ProcessInputGPSRecords(TimeSpan currentModelTime)
        {
            //check if there are any gps input records left
            if (gpsInputCarsCounter < inputRecords.Count)
            {
                Record record;

                if (gpsInputCarsCounter == 0)
                {
                    //initialize processing of gps input records by initializing car current gps input locations
                    //and list of the cars in previous step not present in the current step
                    for (; gpsInputCarsCounter < inputRecords.Count &&
                        (record = inputRecords[gpsInputCarsCounter]).Time == currentModelTime;
                        gpsInputCarsCounter++)
                    {
                        carCurrentGPSInputLocations.Add(record.CarHashCode, new LocationAndDirection(
                            record.Point, record.DirectionNormalizedVector));
                    }
                }

                //initialze the set of the cars occured in this step
                HashSet<int> carsInCurrentStep = null;

                //perform transition of cars in gps records
                for (bool isFirstIteration = true;
                    gpsInputCarsCounter < inputRecords.Count &&
                    (record = inputRecords[gpsInputCarsCounter]).Time ==
                        currentModelTime + cellularTopologyParameters.P2_SimulationStepInterval;
                    gpsInputCarsCounter++, isFirstIteration = false)
                {
                    if (isFirstIteration)
                    {
                        Dictionary<int, LocationAndDirection> helpDictionary = carPreviousGPSInputLocations;
                        carPreviousGPSInputLocations = carCurrentGPSInputLocations;
                        carCurrentGPSInputLocations = helpDictionary;
                        carsInCurrentStep = new HashSet<int>();
                    }
                    if (carCurrentGPSInputLocations.ContainsKey(record.CarHashCode))
                    {
                        carCurrentGPSInputLocations[record.CarHashCode] = new LocationAndDirection(
                            record.Point, record.DirectionNormalizedVector);
                    }
                    else
                    {
                        carCurrentGPSInputLocations.Add(record.CarHashCode, new LocationAndDirection(
                            record.Point, record.DirectionNormalizedVector));
                    }
                    carsInPreviousStepNotInCurrentStep.Remove(record.CarHashCode);
                    carsInCurrentStep.Add(record.CarHashCode);
                }

                //remove car gps records of cars which are not in next gps recrods
                foreach (int carHashCode in carsInPreviousStepNotInCurrentStep)
                {
                    carCurrentGPSInputLocations.Remove(carHashCode);
                }

                //switch cars current steup set to set of cars in previous step
                if (carsInCurrentStep != null)
                {
                    carsInPreviousStepNotInCurrentStep = carsInCurrentStep;
                }
            }
            else
            {
                //clear gps input locations
                carPreviousGPSInputLocations.Clear();
                carCurrentGPSInputLocations.Clear();
            }
        }

        /// <summary>
        /// Reset GPS input records information and save records from outputRecords to file with path specified in 
        /// <see cref="pathToOutputRecords"/>.
        /// </summary>
        internal void ResetGPSInputRecordsInformationAndSaveInputRecords()
        {
            gpsInputCarsCounter = 0;
            carPreviousGPSInputLocations.Clear();
            carCurrentGPSInputLocations.Clear();
            carsInPreviousStepNotInCurrentStep.Clear();
            using (StreamWriter sw = new FileDataStore(pathToOutGPSFile).GetStreamWriter())
            {
                foreach (Record record in outputRecords)
                {
                    sw.WriteLine(string.Format(CultureInfo.InvariantCulture, "{0} {1} {2} {3}", record.Point,
                        record.DirectionNormalizedVector, record.Time, record.CarHashCode));
                }
            }
        }

        /// <summary>
        /// Loads records from file with path stored in pathToInputRecords to inputRecords and initializes output
        /// records.
        /// </summary>
        internal void LoadInputRecordsAndInitializeOutputRecords()
        {
            outputRecords = new List<Record>();
            inputRecords = new List<Record>();
            foreach (string[] lineItems in new FileLineDataLoader(pathToInGPSFile).GetLinesWithData())
            {
                inputRecords.Add(new Record()
                {
                    Point = Point.Parse(lineItems[0]),
                    DirectionNormalizedVector = Vector.Parse(lineItems[1]),
                    Time = TimeSpan.Parse(lineItems[2]),
                    CarHashCode = int.Parse(lineItems[3])
                });
            }
        }
    }
}
