public SessionSummaryDto CalculateWorkoutSummary(SessionDto session, List<DetectedInterval> detectedIntervals, int unit)
        {
            var sessionSummaryDtoList = new List<SessionSummaryDto>();

            for (var w = 0; w < detectedIntervals.Count; w++)
            {
                var sessionDataSubsetDto = new SessionDataSubsetDto()
                {
                    MinimumSecond = (double)detectedIntervals[w].StartTime,
                    MaximumSecond = (double)detectedIntervals[w].FinishTime,
                    SessionId = session.Id,
                    Unit = unit
                };

                var sessionSummaryDto = GetSessionDataSubset(sessionDataSubsetDto);
                sessionSummaryDtoList.Add(sessionSummaryDto);
            }

            var totalCount = sessionSummaryDtoList.Count();

            // calculate speed
            var averageSpeed = Math.Round(sessionSummaryDtoList.Sum(x => x.AverageSpeed) / totalCount, 2, MidpointRounding.AwayFromZero);
            var maximumSpeed = sessionSummaryDtoList.MaxBy(s => s.MaximumSpeed).MaximumSpeed;

            // calculate distance
            var totalDistance = Math.Round(sessionSummaryDtoList.Sum(x => x.TotalDistance), 2, MidpointRounding.AwayFromZero);

            // calculate altitiude
            var averageAltitude = Math.Round(sessionSummaryDtoList.Sum(x => x.AverageAltitude) / totalCount, 2, MidpointRounding.AwayFromZero);
            var maximumAltitude = sessionSummaryDtoList.MaxBy(s => s.MaximumAltitude).MaximumAltitude;

            // calculate heart rate
            var averageHeartRate =  Math.Round(sessionSummaryDtoList.Sum(x => x.AverageHeartRate / totalCount), 2, MidpointRounding.AwayFromZero);
            var minimumHeartRate = sessionSummaryDtoList.MinBy(s => s.MinimumHeartRate).MinimumHeartRate;
            var maximumHeartRate = sessionSummaryDtoList.MinBy(s => s.MaximumHeartRate).MaximumHeartRate;

            // calculate power
            var averagePower =  Math.Round(sessionSummaryDtoList.Sum(x => x.AveragePower) / totalCount, 2, MidpointRounding.AwayFromZero);
            var maximumPower = sessionSummaryDtoList.MaxBy(s => s.MaximumPower).MaximumPower;

            // calculate cadence
            var averageCadence =  Math.Round(sessionSummaryDtoList.Sum(x => x.AverageCadence) / totalCount, 2, MidpointRounding.AwayFromZero);
            var maximumCadence = sessionSummaryDtoList.MaxBy(s => s.MaximumCadence).MaximumCadence;

            var workoutSummary = new SessionSummaryDto()
            {
                AverageAltitude = averageAltitude,
                MaximumAltitude = maximumAltitude,
                AverageHeartRate = averageHeartRate,
                MinimumHeartRate = minimumHeartRate,
                MaximumHeartRate = maximumHeartRate,
                AveragePower = averagePower,
                MaximumPower = maximumPower,
                AverageCadence = averageCadence,
                MaximumCadence = maximumCadence,
                AverageSpeed = averageSpeed,
                MaximumSpeed = maximumSpeed,
                TotalDistance = totalDistance,
                Date = session.Date,
                SessionId = session.Id
            };

            return workoutSummary;
        }
        public SessionSummaryDto GetSessionDataSubset(SessionDataSubsetDto sessionDataSubsetDto)
        {
            var minimumSeconds = sessionDataSubsetDto.MinimumSecond;
            var maximumSeconds = sessionDataSubsetDto.MaximumSecond;
            var totalTimeInHours = (maximumSeconds - minimumSeconds) / 3600;
            var requestedUnitIsMetric = sessionDataSubsetDto.Unit == 0;
            
            var session = _context.Sessions.Single(x => x.Id == sessionDataSubsetDto.SessionId);
            var sModeIsMetric = session.SMode.ToString("D9").IsMetric(); // pad int to 9 decimals if zero

            var sessionData = _context.SessionData.Where(x => x.SessionId == sessionDataSubsetDto.SessionId).ToList().OrderBy(x => x.Row)
                .Select(sd => new SessionDataDto()
                {
                    Id = sd.Id,
                    HeartRate = sd.HeartRate,
                    Speed = sd.Speed,
                    Cadence = sd.Cadence,
                    Altitude = sd.Altitude,
                    Power = sd.Power,
                    SessionId = sd.SessionId,
                    Date = DateFormat.CalculateSessionDataRowDate(session.Date, session.Interval, sd.Row)
                }).ToList();

            var filteredSessionData = sessionData.FilterListDates(session.Date, minimumSeconds, maximumSeconds); // filter sessions by min and max seconds
            var totalCount = filteredSessionData.Count();

            double maximumSpeed;
            double averageSpeed;
            double totalSpeed;
            double totalDistance;
            double averageAltitude;
            double maximumAltitude;
            double totalAltitude;

            if (requestedUnitIsMetric) // return metric values
            {
                if (sModeIsMetric)
                {
                    // calculate speed - divided speed by 10 as speed is *10 in file
                    totalSpeed = filteredSessionData.Sum(s => s.Speed);
                    averageSpeed = Math.Round((totalSpeed / 10) / totalCount, 2, MidpointRounding.AwayFromZero);
                    maximumSpeed = Math.Round(filteredSessionData.MaxBy(s => s.Speed).Speed / 10, 2, MidpointRounding.AwayFromZero);

                    // calculate distance
                    totalDistance = Math.Round(averageSpeed * totalTimeInHours, 2, MidpointRounding.AwayFromZero);

                    // calculate altitiude
                    totalAltitude = filteredSessionData.Sum(s => s.Altitude / 10);
                    averageAltitude = Math.Round(totalAltitude / totalCount, 2, MidpointRounding.AwayFromZero);
                    maximumAltitude = Math.Round(filteredSessionData.MaxBy(s => s.Altitude).Altitude / 10, 2, MidpointRounding.AwayFromZero);
                }
                else
                {
                    // calculate speed - divided speed by 10 as speed is *10 in file - converted kilometres to miles
                    totalSpeed = filteredSessionData.Sum(s => s.Speed);
                    averageSpeed = ((totalSpeed / 10) / totalCount).ConvertToKilometres();
                    maximumSpeed = (filteredSessionData.MaxBy(s => s.Speed).Speed / 10).ConvertToKilometres();

                    // calculate distance
                    totalDistance = ((totalSpeed / 10) / totalCount * totalTimeInHours).ConvertToKilometres();

                    // calculate altitiude - convert metres to feet
                    totalAltitude = filteredSessionData.Sum(s => s.Altitude / 10);
                    averageAltitude = (totalAltitude / totalCount).ConvertToMetres();
                    maximumAltitude = (filteredSessionData.MaxBy(s => s.Altitude).Altitude / 10).ConvertToMetres();
                }
            }
            else // return imperial values
            {
                if (sModeIsMetric)
                {
                    // calculate speed - divided speed by 10 as speed is *10 in file - converted kilometres to miles
                    totalSpeed = filteredSessionData.Sum(s => s.Speed);
                    averageSpeed = ((totalSpeed / 10) / totalCount).ConvertToMiles();
                    maximumSpeed = (filteredSessionData.MaxBy(s => s.Speed).Speed / 10).ConvertToMiles();

                    // calculate distance
                    totalDistance = ((totalSpeed / 10) / totalCount * totalTimeInHours).ConvertToMiles();

                    // calculate altitiude - convert metres to feet
                    totalAltitude = filteredSessionData.Sum(s => s.Altitude / 10);
                    averageAltitude = (totalAltitude / totalCount).ConvertToFeet();
                    maximumAltitude = (filteredSessionData.MaxBy(s => s.Altitude).Altitude / 10).ConvertToFeet();
                }
                else
                {
                    // calculate speed - divided speed by 10 as speed is *10 in file
                    totalSpeed = filteredSessionData.Sum(s => s.Speed);
                    averageSpeed = Math.Round((totalSpeed / 10) / totalCount, 2, MidpointRounding.AwayFromZero);
                    maximumSpeed = Math.Round(
                    filteredSessionData.MaxBy(s => s.Speed).Speed / 10, 2, MidpointRounding.AwayFromZero);

                    // calculate distance
                    totalDistance = Math.Round(averageSpeed * totalTimeInHours, 2, MidpointRounding.AwayFromZero);

                    // calculate altitiude
                    totalAltitude = filteredSessionData.Sum(s => s.Altitude / 10);
                    averageAltitude = Math.Round(totalAltitude / totalCount, 2, MidpointRounding.AwayFromZero);
                    maximumAltitude = Math.Round(filteredSessionData.MaxBy(s => s.Altitude).Altitude / 10, 2, MidpointRounding.AwayFromZero);
                }
            }

            // calculate heart rate
            var totalHeartRate = filteredSessionData.Sum(s => s.HeartRate);
            var averageHeartRate =  Math.Round(totalHeartRate / totalCount, 2, MidpointRounding.AwayFromZero);
            var minimumHeartRate = filteredSessionData.MinBy(s => s.HeartRate).HeartRate;
            var maximumHeartRate = filteredSessionData.MaxBy(s => s.HeartRate).HeartRate;

            // calculate power
            var totalPower = filteredSessionData.Sum(s => s.Power);
            var averagePower =  Math.Round(totalPower / totalCount, 2, MidpointRounding.AwayFromZero);
            var maximumPower =  Math.Round(filteredSessionData.MaxBy(s => s.Power).Power, 2, MidpointRounding.AwayFromZero);

            // calculate cadence
            var totalCadence = filteredSessionData.Sum(s => s.Cadence);
            var averageCadence = Math.Round(totalCadence / totalCount, 2, MidpointRounding.AwayFromZero);
            var maximumCadence = Math.Round(filteredSessionData.MaxBy(s => s.Cadence).Cadence, 2, MidpointRounding.AwayFromZero);

            // calculate normalized power
            var filteredSessionDto = new SessionDto()
            {
                Interval = session.Interval,
                SessionData = filteredSessionData
            };
            var normalizedPower = filteredSessionDto.CalculateNormalizedPower();

            // calculate intensity factor
            var functionalThresholdPower = _context.Athletes.Single(a => a.Id == session.AthleteId).FunctionalThresholdPower;
            var intensityFactor = normalizedPower.CalculateIntensityFactor(functionalThresholdPower);

            // calculate training stress score
            var sessionTimeInSeconds = filteredSessionData.Count * filteredSessionDto.Interval; //session.Length.TimeOfDay.TotalSeconds;
            double trainingStressScore = 0;
            string trainingStressScoreStatus = "";

            if (normalizedPower != 0)
            {
                trainingStressScore = Metrics.CalculateTrainingStressScore(sessionTimeInSeconds, normalizedPower, intensityFactor, functionalThresholdPower);
                trainingStressScoreStatus = Metrics.TrainingStressScoreStatus(trainingStressScore);
            }

            var sessionSummaryDto = new SessionSummaryDto()
            {
                AverageAltitude = averageAltitude,
                MaximumAltitude = maximumAltitude,
                AverageHeartRate = averageHeartRate,
                MinimumHeartRate = minimumHeartRate,
                MaximumHeartRate = maximumHeartRate,
                AveragePower = averagePower,
                MaximumPower = maximumPower,
                AverageCadence = averageCadence,
                MaximumCadence = maximumCadence,
                AverageSpeed = averageSpeed,
                MaximumSpeed = maximumSpeed,
                TotalDistance = totalDistance,
                NormalizedPower = normalizedPower,
                IntensityFactor = intensityFactor,
                TrainingStressScore = trainingStressScore,
                TrainingStressScoreStatus = trainingStressScoreStatus,
                Date = session.Date,
                SessionId = session.Id
            };

            return sessionSummaryDto;
        }
        public List<DetectedInterval> DetectIntervals(SessionDto session)
        {
            var sessionData = session.SessionData;
            var interval = session.Interval;
            var potentialIntervalStart = new SessionDataDto();
            var detectedIntervalEnd = new SessionDataDto();
            var detectedIntervals = new List<DetectedInterval>();

            for (var x = 0; x < sessionData.Count; x++)
            {
                bool potentialIntervalDetected = false;
                bool intervalDetected = false;

                for (var p = x; p < sessionData.Count; p++)
                {
                    // get average of proceeding 14 seconds of powers - time taken for rider to reach maximum power
                    var currentPowers = new List<double>();
                    var proceedingPowers = new List<double>();

                    for (var i = 0; i < 14; i++)
                    {
                        if (p + (i + 1) < sessionData.Count)
                        {
                            if (sessionData[p + i].Power == 0) // rider must be applying power for next 14 seconds
                            {
                                break;
                            }
                            currentPowers.Add(sessionData[p + i].Power); // get power for the next 14 seconds
                            proceedingPowers.Add(sessionData[(p + 1) + i].Power); // get power for the next 14 seconds starting at current power +1
                        }
                    }

                    if (currentPowers.Count == 0) // no powers added to the last - last detected power was 0
                    {
                        break;
                    }

                    var currentPowersAverage = currentPowers.Average();
                    var proceedingPowersAverage = proceedingPowers.Average();

                    // check for potential interval
                    if (currentPowersAverage < proceedingPowersAverage)
                    {
                        if (!potentialIntervalDetected)
                        {
                            potentialIntervalStart = sessionData[p];
                            potentialIntervalDetected = true;
                        }
                    }
                    else // possible that cyclist built up speed and reached interval speed to maintain
                    {
                        var potentialIntervalPowerToMaintain = sessionData[p].Power;

                        var percentage = ((potentialIntervalPowerToMaintain * 40) / 100);
                        var minimumPowerRange = potentialIntervalPowerToMaintain - percentage;
                        var maximumPowerRange = potentialIntervalPowerToMaintain + percentage;
                        var minimumIntervalDuration = 10; // interval power must be maintained for atleast 10 seconds

                        var timer = 0;
                        var counter = 1;
                        for (var q = p; q < sessionData.Count; q++)
                        {
                            if (sessionData[q].Power > minimumPowerRange && sessionData[q].Power < maximumPowerRange)
                            {
                                timer += interval * counter;
                            }
                            else
                            {
                                if (timer > minimumIntervalDuration)
                                {
                                    intervalDetected = true;
                                    detectedIntervalEnd = sessionData[q];

                                    var startTime = potentialIntervalStart.Row * interval;
                                    var finishTime = detectedIntervalEnd.Row * interval;

                                    var intervalData = GetSessionDataSubset(new SessionDataSubsetDto() // get interval summary
                                    {
                                        MinimumSecond = startTime,
                                        MaximumSecond = finishTime,
                                        Unit = 0,
                                        SessionId = session.Id
                                    });
                                    
                                    detectedIntervals.Add(new DetectedInterval(startTime, finishTime, intervalData.AveragePower, true));
                                }
                                break;
                            }
                        }
                    }

                    if (intervalDetected)
                    {
                        x = detectedIntervalEnd.Row * interval; // start detecting new interval at the end of the detected interval
                        potentialIntervalStart = sessionData[detectedIntervalEnd.Row];
                        break;
                    }
                }
            }

            // detect workout and rest periods
            var totalCount = sessionData.Count();
            var totalPower = sessionData.Sum(s => s.Power);
            var averagePower =  Math.Round(totalPower / totalCount, 2, MidpointRounding.AwayFromZero);
            var minimumIntervalPower = ((averagePower * 80) / 100); // workout periods must be greater than 30% of average session power

            for (var f = 0; f < detectedIntervals.Count; f ++)
            {
                detectedIntervals[f].IsRest = detectedIntervals[f].AveragePower < minimumIntervalPower;
            }

            return detectedIntervals;
        }
        public SessionDto GetSingle(int sessionId)
        {
            var session = _context.Sessions.Single(x => x.Id == sessionId);
            var sessionData = _context.SessionData.Where(x => x.SessionId == sessionId).ToList().OrderBy(x =>x.Row)
                .Select(sd => new SessionDataDto()
                {
                    Id = sd.Id,
                    HeartRate = sd.HeartRate,
                    Speed = sd.Speed,
                    Cadence = sd.Cadence,
                    Altitude = sd.Altitude,
                    Power = sd.Power,
                    SessionId = sd.SessionId,
                    Row = sd.Row,
                    Date = DateFormat.CalculateSessionDataRowDate(session.Date, session.Interval, sd.Row)
                }).ToList();

            var sessionDto = new SessionDto
            {
                Id = session.Id,
                Title = session.Title,
                SoftwareVersion = session.SoftwareVersion,
                MonitorVersion = session.MonitorVersion,
                SMode = session.SMode,
                StartTime = session.StartTime,
                Length = session.Length,
                Date = session.Date,
                Interval = session.Interval,
                Upper1 = session.Upper1,
                Lower1 = session.Lower1,
                AthleteId = session.AthleteId,
                SessionData = sessionData
            };

            return sessionDto;
        }