func mapToPerformanceTestSeries2(name string, predictedJourneyTimes, actualJourneyTimes map[time.Time]time.Duration) PerformanceTestSeries2 {

	departureTimes := make([]time.Time, len(predictedJourneyTimes))
	durations := make([]time.Duration, len(predictedJourneyTimes))
	i := 0

	/* For calculating the root mean squared error, and mean absolute error */
	diffInSeconds := make([]float64, 0)
	absDiffInSeconds := make([]float64, 0)

	for departureTime, duration := range predictedJourneyTimes {
		departureTimes[i] = departureTime
		durations[i] = duration

		actualJourneyTime := actualJourneyTimes[departureTime]
		diffInSeconds = append(diffInSeconds, (duration - actualJourneyTime).Seconds())
		absDiffInSeconds = append(absDiffInSeconds, math.Abs((duration - actualJourneyTime).Seconds()))

		i++
	}

	rmse, err := statistics.RootMeanSquare(absDiffInSeconds)
	if err != nil {
		/* If lack of data points simply assume 0 */
		logger.GetLogger().Error(err.Error())
		rmse = 0
	}

	mae, err := statistics.Mean(absDiffInSeconds)
	if err != nil {
		/* If lack of data points simply assume 0 */
		logger.GetLogger().Error(err.Error())
		mae = 0
	}

	mad, err := statistics.Median(absDiffInSeconds)
	if err != nil {
		/* If lack of data points simply assume 0 */
		logger.GetLogger().Error(err.Error())
		mad = 0
	}

	variance, err := statistics.Variance(diffInSeconds)
	if err != nil {
		/* If lack of data points simply assume 0 */
		logger.GetLogger().Error(err.Error())
		variance = 0
	}

	return PerformanceTestSeries2{
		SeriesName: name,
		X:          departureTimes,
		Y:          durations,
		Count:      len(durations),
		RMSE:       time.Duration(rmse) * time.Second,
		MAE:        time.Duration(mae) * time.Second,
		MAD:        time.Duration(mad) * time.Second,
		Variance:   time.Duration(variance) * time.Second,
	}
}
func computeDelayCharacteristicsForHour(hour int, times []time.Time, actualDurations []time.Duration, historicDurations []time.Duration) map[time.Duration]map[int]time.Duration {
	// Extract times and durations relavant to the hour (and < hour + 2 hours)
	truncatedTimes := make([]time.Time, 0)
	truncatedActualDurations := make([]time.Duration, 0)
	truncatedHistoricDurations := make([]time.Duration, 0)
	for i, t := range times {
		if hour <= t.Hour() && t.Hour() < hour+int(continuousObservationPeriod.Hours()) {
			truncatedTimes = append(truncatedTimes, t)
			truncatedActualDurations = append(truncatedActualDurations, actualDurations[i])
			truncatedHistoricDurations = append(truncatedHistoricDurations, historicDurations[i])
		}
	}

	// Build delay characteristics table
	delayCharacteristics := make(map[time.Duration]map[int]time.Duration)

	for _, delayThreshold := range DelayThresholds {
		delayCharacteristicsPerThreshold := make(map[int]time.Duration)
		earlyCharacteristicsPerThreshold := make(map[int]time.Duration)

		for _, busCountThreshold := range DelayBusCountThresholds {

			delaysLasting := make([]float64, 0)
			delayFlag := false
			currentDelayCount := 0

			var startOfDelay, endOfDelay time.Time

			for i, currentTime := range truncatedTimes {
				actualDuration := truncatedActualDurations[i]
				historicDuration := truncatedHistoricDurations[i]

				// Skip if comparison can't be made
				if historicDuration == 0 {
					continue
				}

				if delayFlag {

					// Handle during delay here (special case if delay still occuring at last point in time)
					if actualDuration < historicDuration+delayThreshold || i == len(truncatedTimes)-1 {
						// End of delay
						endOfDelay = currentTime

						// Safety check no bus overtakes, and not artifically large
						if endOfDelay.After(startOfDelay) && endOfDelay.Sub(startOfDelay) < 24*time.Hour {
							delayDuration := endOfDelay.Sub(startOfDelay)
							delaysLasting = append(delaysLasting, delayDuration.Seconds())
						}
						// Reset flags
						delayFlag = false
						currentDelayCount = 0
					}
				} else {
					// Handle no delay / delay not yet found here
					if actualDuration >= historicDuration+delayThreshold {
						// Accumulate until delay found
						if currentDelayCount < busCountThreshold {
							currentDelayCount++
							continue
						}
						startOfDelay = currentTime
						delayFlag = true
					}
					currentDelayCount = 0
				}
			}

			averageLastingTime, _ := statistics.Mean(delaysLasting)
			delayCharacteristicsPerThreshold[busCountThreshold] = time.Duration(averageLastingTime) * time.Second

			// Do the same for early
			earlyLasting := make([]float64, 0)
			earlyFlag := false
			currentEarlyCount := 0

			var startOfEarly, endOfEarly time.Time

			for i, currentTime := range truncatedTimes {
				actualDuration := truncatedActualDurations[i]
				historicDuration := truncatedHistoricDurations[i]

				// Skip if comparison can't be made
				if historicDuration == 0 {
					continue
				}

				if earlyFlag {

					// Handle during delay here (special case if early still occuring at last point in time)
					if actualDuration > historicDuration-delayThreshold || i == len(truncatedTimes)-1 {
						// End of early
						endOfEarly = currentTime

						// Safety check no bus overtakes, and not artifically large
						if endOfEarly.After(startOfEarly) && endOfEarly.Sub(startOfEarly) < 24*time.Hour {
							earlyDuration := endOfEarly.Sub(startOfEarly)
							earlyLasting = append(earlyLasting, earlyDuration.Seconds())
						}
						// Reset flags
						earlyFlag = false
						currentEarlyCount = 0
					}
				} else {
					// Handle no early / early not yet found here
					if actualDuration <= historicDuration-delayThreshold {
						// Accumulate until early found
						if currentEarlyCount < busCountThreshold {
							currentEarlyCount++
							continue
						}
						startOfEarly = currentTime
						earlyFlag = true
					}
					currentEarlyCount = 0
				}
			}

			averageEarly, _ := statistics.Mean(earlyLasting)
			earlyCharacteristicsPerThreshold[busCountThreshold] = time.Duration(averageEarly) * time.Second
		}
		delayCharacteristics[delayThreshold] = delayCharacteristicsPerThreshold
		delayCharacteristics[-delayThreshold] = earlyCharacteristicsPerThreshold
	}
	return delayCharacteristics
}
/* Writes to .html file for resulting performance graph */
func writePerformanceChart2(wr io.Writer, summary PerformancePlotSummary2, templatePath string) {

	funcMap := template.FuncMap{
		// Prettifies x and y axes into js format
		"formatXAxis": func(i int, slice []time.Time) string {
			str := fmt.Sprintf("['x%v'", i)
			for i := 0; i < len(slice); i++ {
				str += ", " + fmt.Sprintf("new Date(Date.UTC(%v, %v, %v, %v, %v, %v))",
					slice[i].Year(), int(slice[i].Month())-1, slice[i].Day(),
					slice[i].Hour(), slice[i].Minute(), slice[i].Second())
			}
			str += "]"
			return str
		},
		"formatYAxis": func(seriesName string, slice []time.Duration) string {
			str := fmt.Sprintf("['%v'", seriesName)
			for i := 0; i < len(slice); i++ {
				minutes := int(slice[i].Minutes())
				if minutes == 0 {
					str += ", null"
				} else {
					str += ", " + strconv.Itoa(minutes)
				}
			}
			str += "]"
			return str
		},
		"formatRMSE": func(rmse time.Duration) string {
			if rmse > 0 {
				return rmse.String()
			}
			return "NA"
		},
		"getOverallRMSE": func(heuristic string, method performanceeval.Method, charts []PerformanceChart2) string {
			percentageRMSEs := make([]float64, 0)
			for _, chart := range charts {
				/* First find out error between tfl and actual */
				var tflHeuristicError time.Duration
				for _, series := range chart.MultipleSeries {
					if series.SeriesName == "Tfl Expected Time" {
						switch heuristic {
						case "RMSE":
							tflHeuristicError = series.RMSE
						case "MAE":
							tflHeuristicError = series.MAE
						case "MAD":
							tflHeuristicError = series.MAD
						case "Variance":
							tflHeuristicError = series.Variance
						default:
							logger.GetLogger().Panicf("Unknown heuristic: %v", heuristic)

						}
					}
				}

				/* Find out error between method and actual */
				var heuristicError time.Duration
				for _, series := range chart.MultipleSeries {
					if series.SeriesName == method.String() {
						switch heuristic {
						case "RMSE":
							heuristicError = series.RMSE
						case "MAE":
							heuristicError = series.MAE
						case "MAD":
							heuristicError = series.MAD
						case "Variance":
							heuristicError = series.Variance
						default:
							logger.GetLogger().Panicf("Unknown heuristic: %v", heuristic)
						}
					}
				}

				/* Skip if either is invalid */
				if heuristicError == 0 || tflHeuristicError == 0 {
					continue
				}

				/* Calculate percentage error as that of tfl */
				percentError := (tflHeuristicError.Seconds() - heuristicError.Seconds()) / tflHeuristicError.Seconds()
				percentageRMSEs = append(percentageRMSEs, percentError)
			}
			overallPercentRMSE, err := statistics.Mean(percentageRMSEs)
			if err != nil {
				return "NA"
			}
			return fmt.Sprintf("%0.2f%%", overallPercentRMSE*100)
		},
		/* Counts the number of journeys which are superior than the tfl expected time for a particular prediction method */
		"getSuperiorCount": func(heuristic string, method performanceeval.Method, charts []PerformanceChart2) int {
			count := 0
			for _, chart := range charts {
				/* First find out error between tfl and actual */
				var tflHeuristicError time.Duration
				for _, series := range chart.MultipleSeries {
					if series.SeriesName == "Tfl Expected Time" {
						switch heuristic {
						case "RMSE":
							tflHeuristicError = series.RMSE
						case "MAE":
							tflHeuristicError = series.MAE
						case "MAD":
							tflHeuristicError = series.MAD
						case "Variance":
							tflHeuristicError = series.Variance
						default:
							logger.GetLogger().Panicf("Unknown heuristic: %v", heuristic)

						}
					}
				}

				/* Find out error between method and actual */
				var heuristicError time.Duration
				for _, series := range chart.MultipleSeries {
					if series.SeriesName == method.String() {
						switch heuristic {
						case "RMSE":
							heuristicError = series.RMSE
						case "MAE":
							heuristicError = series.MAE
						case "MAD":
							heuristicError = series.MAD
						case "Variance":
							heuristicError = series.Variance
						default:
							logger.GetLogger().Panicf("Unknown heuristic: %v", heuristic)
						}
					}
				}

				/* Skip if either is invalid */
				if heuristicError == 0 || tflHeuristicError == 0 {
					continue
				}

				if heuristicError < tflHeuristicError {
					count++
				} else if heuristicError > tflHeuristicError {
					count--
				}
			}
			return count
		},
		/* Returns the average % count of data points available of a particular prediction method with respect to the actual times*/
		"getAverageAvailableDataPoints": func(method performanceeval.Method, charts []PerformanceChart2) string {
			percentages := make([]float64, 0)
			for _, chart := range charts {
				/* First find out count for actual series */
				var actualCount int
				for _, series := range chart.MultipleSeries {
					if series.SeriesName == "Actual Time" {
						actualCount = series.Count
					}
				}

				/* Find out count for method*/
				var methodCount int
				for _, series := range chart.MultipleSeries {
					if series.SeriesName == method.String() {
						methodCount = series.Count
					}
				}

				/* Prevent zero numerator or denominator */
				if methodCount != 0 && actualCount != 0 {
					percentages = append(percentages, float64(methodCount)/float64(actualCount))
				}
			}
			averagePercentage, err := statistics.Mean(percentages)
			if err != nil {
				return "NA"
			}
			return fmt.Sprintf("%.2f%%", averagePercentage*100)
		},
	}

	t, err := template.New(path.Base(templatePath)).Funcs(funcMap).ParseFiles(templatePath)
	if err != nil {
		logger.GetLogger().Panic(err)
	}

	err = t.Execute(wr, summary)
	if err != nil {
		logger.GetLogger().Panic(err)
	}
}
/* Returns map[string]map[string]map[string]int
   ( map : fromStop =>
   		(map : toStop =>
   			(map : "estimate" => estimate in seconds
   			       "stddev" => standard deviation in seconds
   			       "delayBusCount" => bus count as evidence of delay
   			       "delayExpectedToLast" => how long delay is expected to last in seconds
   			 )
   		)
   	)
   	the estimated journey times, standard deviation, between map of neighbouring stops
   	for mixed, historic and real time.
*/
func getEstimates(adjacentStops map[string]map[string]bool) (map[string]map[string]map[string]int, map[string]map[string]map[string]int, map[string]map[string]map[string]int) {
	departureTime := time.Now().In(timezone.Get())

	timeRanges := map[time.Time]time.Time{
		departureTime.AddDate(0, 0, -7).Add(-30 * time.Minute):  departureTime.AddDate(0, 0, -7).Add(30 * time.Minute),
		departureTime.AddDate(0, 0, -14).Add(-30 * time.Minute): departureTime.AddDate(0, 0, -14).Add(30 * time.Minute),
		departureTime.AddDate(0, 0, -21).Add(-30 * time.Minute): departureTime.AddDate(0, 0, -21).Add(30 * time.Minute),
	}

	realTimeEstimates := make(map[string]map[string]map[string]int)
	mixedEstimates := make(map[string]map[string]map[string]int)

	/* Select historic and real time estimates from database */
	historicEstimates := selectdb.SelectHistoricEstimates(timeRanges)
	realTimeData := selectdb.SelectRealTimeEstimates(realTimeWindow)

	/* Combine historic and real time data */
	for fromStop, fromStopDetails := range historicEstimates {
		for toStop, historicDetails := range fromStopDetails {
			historicEstimate := time.Duration(historicDetails["estimate"]) * time.Second

			/* Extract real time data and lookup delay characteristics, skip for cases lacking real time estimate */
			delayBusCount := 0
			realTimeFromStopDetails, exists := realTimeData[fromStop]
			if !exists {
				continue
			}
			realTimeDetails, exists := realTimeFromStopDetails[toStop]
			if !exists {
				continue
			}
			realTimeCount, _ := realTimeDetails["count"]
			if realTimeCount < 3 {
				continue
			}
			realTimeRecords := make([]time.Duration, realTimeCount)
			for i := 1; i <= realTimeCount; i++ {
				// Since real time records are already reversed, reverse it again for ascending order in time,
				// so that handleDelayReverseLookup will work fine
				realTimeRecord := time.Duration(realTimeDetails[strconv.Itoa(realTimeCount+1-i)]) * time.Second
				realTimeRecords[i-1] = realTimeRecord
			}

			/* Calculate real time estimate using last three buses averaged */
			realTimeLastThreeInSeconds := make([]float64, 3)
			for j := 0; j < 3; j++ {
				realTimeLastThreeInSeconds[j] = realTimeRecords[len(realTimeRecords)-1-j].Seconds()
			}
			realTimeEstimateInSeconds, _ := statistics.Mean(realTimeLastThreeInSeconds)
			_, exists = realTimeEstimates[fromStop]
			if !exists {
				realTimeEstimates[fromStop] = make(map[string]map[string]int)
				mixedEstimates[fromStop] = make(map[string]map[string]int)
			}
			realTimeEstimates[fromStop][toStop] = map[string]int{
				"estimate": int(realTimeEstimateInSeconds),
			}

			/* Get mixed estimates below */

			shouldUseHistoric, delayBusCount, delayExpectedToLast := performanceeval.HandleDelayReverseLookup(
				fromStop, toStop, departureTime, delayLastingThreshold, historicEstimate, realTimeRecords)

			if shouldUseHistoric {
				mixedEstimates[fromStop][toStop] = historicEstimates[fromStop][toStop]
				mixedEstimates[fromStop][toStop]["delayBusCount"] = delayBusCount
			} else {
				mixedEstimates[fromStop][toStop] = map[string]int{
					"estimate":            int(realTimeEstimateInSeconds),
					"delayBusCount":       delayBusCount,
					"delayExpectedToLast": int(delayExpectedToLast.Seconds()),
					"delay":               1,
				}
			}
		}
	}
	return mixedEstimates, historicEstimates, realTimeEstimates
}