public HistoryAnalysis Analyse(IReadOnlyCollection <Period> history)
        {
            List <Result> ratings = new List <Result>();

            Result  result;
            double  unDocumentedDuration = 0;
            double  duration             = 0;
            decimal rating = 0;

            //Return 0 duration and 0 rating if no periods available or if configuration is not set
            if (AnalyserConfiguration == null || history == null || history.Count == 0)
            {
                return(new HistoryAnalysis

                {
                    AnalysedDuration = new TimeSpan(0, 0, 0),
                    DriverRating = 0
                });
            }

            //Sort the given list of periods in the ascending order of start time
            history = history.OrderBy(x => x.Start).ToArray();
            var entriesToConsider = history.ToList();
            //Take the index of record with first non zero average speed
            var begin = entriesToConsider.IndexOf(history.FirstOrDefault(x => x.AverageSpeed > 0));
            //Take the index of record with last non zero average speed
            var end = entriesToConsider.IndexOf(history.LastOrDefault(x => x.AverageSpeed > 0));

            //Check if there is any period with non zero average speed, if yes then proceed to calculate duration and ratings
            if (begin != -1)
            {
                //Calculate the duration and ratings for periods that fall between (and including) first and last records with non zero average speeds
                for (int i = begin; i <= end; i++)
                {
                    //For intermediate records, Check for the gaps between current record's start time and previous record's end time
                    //If there is a gap, then calculate undocumented period and rating
                    if (i > begin && history.ElementAt(i).Start > history.ElementAt(i - 1).End)
                    {
                        duration = (history.ElementAt(i).Start - history.ElementAt(i - 1).End).TotalMinutes;

                        unDocumentedDuration += duration;

                        ratings.Add(new Result()
                        {
                            StartTime = history.ElementAt(i - 1).End.TimeOfDay, EndTime = history.ElementAt(i).Start.TimeOfDay, Duration = (decimal)duration, Rating = 0
                        });
                    }

                    //Check if average speed is greater than maximum permitted speed.
                    //If yes, then assign rating configured for exceeding maximum speed
                    //If no, then calculate the rating by linearly mapping the average speeds between 0 and maximum speed to 0-1
                    rating = (history.ElementAt(i).AverageSpeed > AnalyserConfiguration.MaxSpeed) ? AnalyserConfiguration.RatingForExceedingMaxSpeed : (history.ElementAt(i).AverageSpeed / AnalyserConfiguration.MaxSpeed);

                    //Create result set containing Start and End time along with calculated rating
                    result = new Result()
                    {
                        StartTime = history.ElementAt(i).Start.TimeOfDay, EndTime = history.ElementAt(i).End.TimeOfDay, Rating = rating
                    };

                    //Get the duration for the current result set
                    result.Duration = (decimal)(result.EndTime - result.StartTime).TotalMinutes;

                    ratings.Add(result);
                }
            }

            //If no period is considered valid during analysis, return 0 duration and 0 rating
            if (!ratings.Any())
            {
                return(new HistoryAnalysis

                {
                    AnalysedDuration = new TimeSpan(0, 0, 0),
                    DriverRating = 0
                });
            }

            //Sort the rating resultsets in ascending order of start time
            ratings = ratings.OrderBy(x => x.StartTime).ToList();

            //Calculate overall rating for periods considered
            var overallRating = RatingCalculator.CalculateOverallRating(ratings);

            //Return analysis duration (excluding undocumented duration), overall rating and apply penalty if any undocumented periods recorded
            return(new HistoryAnalysis
            {
                AnalysedDuration = ratings.Last().EndTime - ratings.First().StartTime - new TimeSpan(0, (int)unDocumentedDuration, 0),
                DriverRating = overallRating,
                DriverRatingAfterPenalty = unDocumentedDuration > 0 ? overallRating * AnalyserConfiguration.PenaltyForFaultyRecording : overallRating
            });
        }
        public HistoryAnalysis Analyse(IReadOnlyCollection <Period> history)
        {
            List <Result> ratings = new List <Result>();

            Result  result;
            double  unDocumentedDuration = 0;
            double  duration             = 0;
            decimal rating = 0;

            //Return 0 duration and 0 rating if no periods available or if configuration is not set
            if (AnalyserConfiguration == null || history == null || history.Count == 0)
            {
                return(new HistoryAnalysis

                {
                    AnalysedDuration = new TimeSpan(0, 0, 0),
                    DriverRating = 0
                });
            }

            //Sort the given list of periods in the ascending order of start time
            history = history.OrderBy(x => x.Start).ToArray();

            for (int i = 0; i < history.Count; i++)
            {
                //Ignore anything outside the permitted time slot
                if ((history.ElementAt(i).Start.TimeOfDay >= AnalyserConfiguration.EndTime) ||
                    history.ElementAt(i).End.TimeOfDay <= AnalyserConfiguration.StartTime)
                {
                    continue;
                }

                //Check for undocumented periods for the first record by determining gap between record's start time and permitted start time
                if (i == 0 && history.ElementAt(i).Start.TimeOfDay > AnalyserConfiguration.StartTime)
                {
                    duration = (history.ElementAt(i).Start.TimeOfDay - AnalyserConfiguration.StartTime).TotalMinutes;

                    unDocumentedDuration += duration;

                    ratings.Add(new Result()
                    {
                        StartTime = AnalyserConfiguration.StartTime, EndTime = history.ElementAt(i).Start.TimeOfDay, Duration = (decimal)duration, Rating = 0
                    });
                }
                //Check for undocumented periods for the last record by determining gap between record's end time and permitted end time
                else if (i == history.Count - 1 && history.ElementAt(i).End.TimeOfDay < AnalyserConfiguration.EndTime)
                {
                    duration = (AnalyserConfiguration.EndTime - history.ElementAt(i).End.TimeOfDay).TotalMinutes;

                    unDocumentedDuration += duration;

                    ratings.Add(new Result()
                    {
                        StartTime = history.ElementAt(i).End.TimeOfDay, EndTime = AnalyserConfiguration.EndTime, Duration = (decimal)duration, Rating = 0
                    });
                }
                //For intermediate records, Check for the gaps between current record's start time and previous record's end time
                //If there is a gap, then calculate undocumented period and rating
                if (i > 0 && history.ElementAt(i).Start.TimeOfDay > AnalyserConfiguration.StartTime && history.ElementAt(i).Start > history.ElementAt(i - 1).End)
                {
                    duration = (history.ElementAt(i).Start - history.ElementAt(i - 1).End).TotalMinutes;

                    unDocumentedDuration += duration;

                    ratings.Add(new Result()
                    {
                        StartTime = history.ElementAt(i - 1).End.TimeOfDay, EndTime = history.ElementAt(i).Start.TimeOfDay, Duration = (decimal)duration, Rating = 0
                    });
                }

                //Check if average speed is greater than maximum permitted speed.
                //If yes, then assign rating configured for exceeding maximum speed
                //If no, then calculate the rating by linearly mapping the average speeds between 0 and maximum speed to 0-1
                rating = (history.ElementAt(i).AverageSpeed > AnalyserConfiguration.MaxSpeed) ? AnalyserConfiguration.RatingForExceedingMaxSpeed : (history.ElementAt(i).AverageSpeed / AnalyserConfiguration.MaxSpeed);

                //Create result set containing Start and End time along with calculated rating
                result = new Result()
                {
                    StartTime = history.ElementAt(i).Start.TimeOfDay, EndTime = history.ElementAt(i).End.TimeOfDay, Rating = rating
                };

                //Set start time to permitted start time if it starts before permitted start time
                if (history.ElementAt(i).Start.TimeOfDay < AnalyserConfiguration.StartTime)
                {
                    result.StartTime = AnalyserConfiguration.StartTime;
                }

                //Set end time to permitted end time if it ends after permitted end time
                if (history.ElementAt(i).End.TimeOfDay > AnalyserConfiguration.EndTime)
                {
                    result.EndTime = AnalyserConfiguration.EndTime;
                }

                //Get the duration for the current result set
                result.Duration = (decimal)(result.EndTime - result.StartTime).TotalMinutes;

                ratings.Add(result);
            }

            //If no period is considered valid during analysis, return 0 duration and 0 rating
            if (!ratings.Any())
            {
                return(new HistoryAnalysis

                {
                    AnalysedDuration = new TimeSpan(0, 0, 0),
                    DriverRating = 0
                });
            }

            //Sort the rating resultsets in ascending order of start time
            ratings = ratings.OrderBy(x => x.StartTime).ToList();

            //Calculate overall rating for periods considered
            var overallRating = RatingCalculator.CalculateOverallRating(ratings);

            //Return analysis duration (excluding undocumented duration), overall rating and apply penalty if any undocumented periods recorded
            return(new HistoryAnalysis
            {
                AnalysedDuration = ratings.Last().EndTime - ratings.First().StartTime - new TimeSpan(0, (int)unDocumentedDuration, 0),
                DriverRating = overallRating,
                DriverRatingAfterPenalty = unDocumentedDuration > 0 ? overallRating * AnalyserConfiguration.PenaltyForFaultyRecording : overallRating
            });
        }