/* Grabs all data stored in "performance_measure" to produce time plots */
func PerformancePlot(startTime time.Time, wr io.Writer, templatePath string) {
	performanceMeasures, err := selectdb.SelectPerformanceMeasure(startTime)
	if err != nil {
		logger.GetLogger().Panic(err)
	}

	charts := make([]PerformanceChart, 0)

	for line, boundDetails := range performanceMeasures {
		for bound, innerSlice := range boundDetails {
			departureTimes := make([]time.Time, len(innerSlice))
			tflExpectedTimes := make([]time.Duration, len(innerSlice))
			predictedTimes := make([]time.Duration, len(innerSlice))
			actualTimes := make([]time.Duration, len(innerSlice))

			var origin, destination string
			for i, details := range innerSlice {
				departureTimes[i] = toTime(details["departureTime"])
				tflExpectedTimes[i] = toDuration(details["tflExpectedTime"])
				predictedTimes[i] = toDuration(details["predictedTime"])
				actualTimes[i] = toDuration(details["actualTime"])
				origin = details["fromStop"]
				destination = details["toStop"]
			}

			performanceTestSeries := PerformanceTestSeries{
				X:  departureTimes,
				Y1: tflExpectedTimes,
				Y2: predictedTimes,
				Y3: actualTimes,
			}

			sort.Sort(performanceTestSeries)

			originName := selectdb.SelectStop(origin)
			destinationName := selectdb.SelectStop(destination)

			charts = append(charts, PerformanceChart{
				Line:             line,
				Bound:            bound,
				Origin:           origin,
				Destination:      destination,
				OriginName:       originName,
				DestinationName:  destinationName,
				XValues:          performanceTestSeries.X,
				TflExpectedTimes: performanceTestSeries.Y1,
				PredictedTimes:   performanceTestSeries.Y2,
				ActualTimes:      performanceTestSeries.Y3,
				ServerDomain:     credentials.GetGoServerDomain(),
			})
		}
	}
	writePerformanceChart(wr, charts, templatePath)
}
/* Grabs all stored historic and real time data, produce time plots and
   also percentages of consistently delayed or early */
func DelayConsistencyPlot(startTime, endTime time.Time, wr io.Writer, templatePath string) {
	charts := make([]DelayConsistencyChart, 0)

	availableRoutes := selectdb.SelectAvailableRoutesTflPredictions()
	for _, route := range availableRoutes {
		// Extract fields from routes
		line := route["line"]
		bound := route["bound"]
		fromStop := route["fromStop"]
		toStop := route["toStop"]
		fromStopName := selectdb.SelectStop(fromStop)
		toStopName := selectdb.SelectStop(toStop)

		actualJourneyTimes := performanceeval.FetchActualJourneyTimes(line, bound, fromStop, toStop, startTime, endTime)

		// Extract the actual departure times for generating predictions later on (discarding those that are)
		actualDepartureTimes := make([]time.Time, len(actualJourneyTimes))
		actualJourneyTimeDurations := make([]time.Duration, len(actualJourneyTimes))
		i := 0
		for departureTime, duration := range actualJourneyTimes {
			actualDepartureTimes[i] = departureTime
			actualJourneyTimeDurations[i] = duration
			i++
		}

		// Fetch Tfl predictions
		// tflPredictions := performanceeval.FetchTflJourneyTimes(line, bound, fromStop, toStop, actualDepartureTimes)

		/* Cache for real time estimates which can be reused for different prediction methods
		   map (fromStop => (toStop => (departureTime => "estimate"/"stddev" => int in seconds)))) */
		realTimeEstimatesCache := make(map[string]map[string]map[time.Time]map[string]int64)

		// Generate predictions using our methods (best historic and real time only)
		predictions := make(map[performanceeval.Method]map[time.Time]time.Duration)
		predictionsAdjacentStops := make(map[performanceeval.Method]map[time.Time]map[string]map[string]time.Duration)
		methods := []performanceeval.Method{
			performanceeval.DefaultHistoric(),
			performanceeval.DefaultRealTime(),
		}
		// Highest # consistency possible - number of pairs of adjacent stops in route
		maxScore := 0
		for _, method := range methods {
			predictions[method], predictionsAdjacentStops[method], maxScore = predictJourneyTimesWithDetails(line, bound, fromStop, toStop, actualDepartureTimes, method, realTimeEstimatesCache)
		}

		// Convert the above into PerformanceTestSeries2
		actualJourneyTimesSeries := PerformanceTestSeries2{
			SeriesName: "Actual Time",
			X:          actualDepartureTimes,
			Y:          actualJourneyTimeDurations,
			Count:      len(actualJourneyTimeDurations),
		}
		sort.Sort(actualJourneyTimesSeries)

		/*tflPredictionsSeries := mapToPerformanceTestSeries2("Tfl Expected Time", tflPredictions, actualJourneyTimes)
		sort.Sort(tflPredictionsSeries)*/

		performanceTestSeries2 := []PerformanceTestSeries2{
			actualJourneyTimesSeries,
			// tflPredictionsSeries,
		}

		// Add all prediction series into performance test series 2
		for method, predictedJouneyTimes := range predictions {
			predictionSeries := mapToPerformanceTestSeries2(method.String(), predictedJouneyTimes, actualJourneyTimes)
			sort.Sort(predictionSeries)

			performanceTestSeries2 = append(performanceTestSeries2, predictionSeries)
		}

		// Form delay consistency (percentages) series
		delayConsistencySeries := getDelayConsistencySeries(predictionsAdjacentStops[performanceeval.DefaultHistoric()],
			predictionsAdjacentStops[performanceeval.DefaultRealTime()], maxScore)

		charts = append(charts, DelayConsistencyChart{
			Line:                     line,
			Bound:                    bound,
			Origin:                   fromStop,
			Destination:              toStop,
			OriginName:               fromStopName,
			DestinationName:          toStopName,
			MultipleSeries:           performanceTestSeries2,
			MultiplePercentageSeries: []DelayConsistencySeries{delayConsistencySeries},
			ServerDomain:             credentials.GetGoServerDomain(),
		})

		logger.GetLogger().Info("Done for line: %v, bound: %v, fromStop: %v, toStop: %v", line, bound, fromStop, toStop)
	}

	summary := DelayConsistencyPlotSummary{
		Methods: performanceeval.Methods(),
		Charts:  charts,
	}
	writeDelayConsistencyChart(wr, summary, templatePath)
}
/* Grabs all stored data to produce time plots */
func PerformancePlot2(startTime, endTime time.Time, wr io.Writer, templatePath string) {
	charts := make([]PerformanceChart2, 0)

	availableRoutes := selectdb.SelectAvailableRoutesTflPredictions()
	for _, route := range availableRoutes {
		// Extract fields from routes
		line := route["line"]
		bound := route["bound"]
		fromStop := route["fromStop"]
		toStop := route["toStop"]
		fromStopName := selectdb.SelectStop(fromStop)
		toStopName := selectdb.SelectStop(toStop)

		actualJourneyTimes := performanceeval.FetchActualJourneyTimes(line, bound, fromStop, toStop, startTime, endTime)

		// Extract the actual departure times for generating predictions later on (discarding those that are)
		actualDepartureTimes := make([]time.Time, len(actualJourneyTimes))
		actualJourneyTimeDurations := make([]time.Duration, len(actualJourneyTimes))
		i := 0
		for departureTime, duration := range actualJourneyTimes {
			actualDepartureTimes[i] = departureTime
			actualJourneyTimeDurations[i] = duration
			i++
		}

		// Fetch Tfl predictions
		tflPredictions := performanceeval.FetchTflJourneyTimes(line, bound, fromStop, toStop, actualDepartureTimes)

		/* Cache for real time estimates which can be reused for different prediction methods
		   map (fromStop => (toStop => (departureTime => "estimate"/"stddev" => int in seconds)))) */
		realTimeEstimatesCache := make(map[string]map[string]map[time.Time]map[string]int64)

		// Generate predictions using our methods
		predictions := make(map[performanceeval.Method]map[time.Time]time.Duration)
		for _, method := range performanceeval.Methods() {
			predictions[method] = performanceeval.PredictJourneyTimes(line, bound, fromStop, toStop, actualDepartureTimes, method, realTimeEstimatesCache)
		}

		// Convert the above into PerformanceTestSeries2
		actualJourneyTimesSeries := PerformanceTestSeries2{
			SeriesName: "Actual Time",
			X:          actualDepartureTimes,
			Y:          actualJourneyTimeDurations,
			Count:      len(actualJourneyTimeDurations),
		}
		sort.Sort(actualJourneyTimesSeries)

		tflPredictionsSeries := mapToPerformanceTestSeries2("Tfl Expected Time", tflPredictions, actualJourneyTimes)
		sort.Sort(tflPredictionsSeries)

		performanceTestSeries2 := []PerformanceTestSeries2{
			actualJourneyTimesSeries,
			tflPredictionsSeries,
		}

		// Add all prediction series into performance test series 2
		for method, predictedJouneyTimes := range predictions {
			predictionSeries := mapToPerformanceTestSeries2(method.String(), predictedJouneyTimes, actualJourneyTimes)
			sort.Sort(predictionSeries)

			performanceTestSeries2 = append(performanceTestSeries2, predictionSeries)
		}

		charts = append(charts, PerformanceChart2{
			Line:            line,
			Bound:           bound,
			Origin:          fromStop,
			Destination:     toStop,
			OriginName:      fromStopName,
			DestinationName: toStopName,
			MultipleSeries:  performanceTestSeries2,
			ServerDomain:    credentials.GetGoServerDomain(),
		})

		logger.GetLogger().Info("Done for line: %v, bound: %v, fromStop: %v, toStop: %v", line, bound, fromStop, toStop)
	}

	summary := PerformancePlotSummary2{
		Methods: performanceeval.Methods(),
		Charts:  charts,
	}
	writePerformanceChart2(wr, summary, templatePath)
}