/* adjacentStops : map(fromId => toId) contains all links which we wish to plot autocorrelation graph
   startTime : beginning time of plot
   endTime   : end time of plot
   interval  : each interval in plot */
func AutoCorrelationPlot(adjacentStops map[string]string, startTime, endTime time.Time,
	interval time.Duration, wr io.Writer, templatePath string) {
	// Partition between startTime and endTime
	partitionedTimes := getPartitionedTimes(startTime, endTime, interval)

	series := make([]Acs, 0)
	for fromId, toId := range adjacentStops {
		logger.GetLogger().Info("Handling journey: %v -> %v", fromId, toId)
		fromStop := selectdb.SelectStop(fromId)
		toStop := selectdb.SelectStop(toId)

		// Get average journey times corresponding to partitions
		partitionedAvgJourneyTimes, _, invalidIndices := getAverageJourneyTimes(fromId, toId, partitionedTimes)

		// Calculate autocorrelation sequence
		acs, invalidResultIndices := statistics.AutoCorrelationSequence(partitionedAvgJourneyTimes, invalidIndices)

		// Insert into series of acs to plot chart
		series = append(series, Acs{fromStop, toStop, acs, invalidResultIndices})
	}

	// Generate chart - Write to *.html
	xlabs := make([]int, len(partitionedTimes))
	for i := 0; i < len(partitionedTimes); i++ {
		xlabs[i] = i
	}
	chart := AutoCorrelationChart{
		xlabs,
		series,
	}
	writeAutoCorrelationChart(wr, chart, templatePath)
}
func performancePlotHandler(w http.ResponseWriter, r *http.Request) {
	/* Extract fields in GET request */
	line := r.URL.Query().Get("line")
	bound := r.URL.Query().Get("bound")
	origin := r.URL.Query().Get("origin")
	departureTime := parseISOTime(r.URL.Query().Get("departureTime"))

	/* Find out which vehicle produced the data and list its journey history */
	vehicleId, vehicleHistory := lookupVehicleHistory(line, bound, origin, departureTime)

	/* Some additional info for nice display*/
	originName := selectdb.SelectStop(origin)
	destinationName := selectdb.SelectStop(selectdb.SelectTerminus(line, bound))

	/* Write HTML output */
	writePerformancePlotDetails(w, PerformancePlotDetails{
		Line:            line,
		Bound:           bound,
		Origin:          origin,
		DepartureTime:   formatTime(departureTime),
		OriginName:      originName,
		DestinationName: destinationName,
		VehicleId:       vehicleId,
		VehicleHistory:  vehicleHistory,
	})
}
/* 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)
}
/* Plots the journey times throughout a day between a pair of stops */
func JourneyTimePlot(adjacentStops map[string]string, dates []time.Time,
	interval time.Duration, wr io.Writer, templatePath string) {
	var charts []JourneyTimeChart

	for fromStop, toStop := range adjacentStops {
		logger.GetLogger().Info("Handling journey: %v -> %v", fromStop, toStop)
		series := make([]JourneyTimeSeries, 0)

		var partitionedTimes []Partition
		for _, date := range dates {
			// Partition between startTime (start of day) and endTime (end of day)
			startTime := date.Truncate(24 * time.Hour)
			endTime := date.Truncate(24 * time.Hour).Add(24 * time.Hour)
			partitionedTimes = getPartitionedTimes(startTime, endTime, interval)

			// Get average journey times corresponding to partitions, and their 95% confidence intervals
			partitionedAvgJourneyTimes, stdDevJourneyTimes, invalidIndices := getAverageJourneyTimes(fromStop, toStop, partitionedTimes)
			lowerCI, upperCI, err := statistics.ConfidenceInterval(partitionedAvgJourneyTimes, stdDevJourneyTimes)

			if err != nil {
				logger.GetLogger().Panic(err)
			}

			// Insert into series the journey time of each partition to plot chart
			series = append(series, JourneyTimeSeries{date, partitionedAvgJourneyTimes, lowerCI, upperCI, invalidIndices,
				randomColour()})
		}

		// Generate chart - Write to *.html
		xlabs := make([]string, len(partitionedTimes))
		for i := 0; i < len(partitionedTimes); i++ {
			start := partitionedTimes[i].start
			// e.g. label x axis with time - e.g. 3:00 PM
			xlabs[i] = start.Format(time.Kitchen)
		}

		// Title of chart
		fromStopName := selectdb.SelectStop(fromStop)
		toStopName := selectdb.SelectStop(toStop)
		title := fmt.Sprintf("Journey times between %v and %v", fromStopName, toStopName)

		chart := JourneyTimeChart{
			title,
			fromStop,
			toStop,
			xlabs,
			series,
		}

		charts = append(charts, chart)
	}
	writeJourneyTimeCharts(wr, charts, templatePath)
}
func performancePlotHandlerV2(w http.ResponseWriter, r *http.Request) {
	/* Extract fields in GET request */
	line := r.URL.Query().Get("line")
	bound := r.URL.Query().Get("bound")
	origin := r.URL.Query().Get("origin")
	destination := r.URL.Query().Get("destination")
	departureTime := parseISOTime(r.URL.Query().Get("departureTime"))

	/* Error message string that functions should append to */
	errorMessage := ""

	/* Some additional info for nice display*/
	originName := selectdb.SelectStop(origin)
	destinationName := selectdb.SelectStop(destination)

	/* Find out which vehicle produced the data and list its journey history */
	vehicleId, vehicleHistory, actualJourneyTime, totalPredictions, totalStddevs := lookupVehicleHistory2(line, bound, origin, destination, departureTime, &errorMessage)

	/* Write HTML output */
	writePerformancePlotDetails2(w, PerformancePlotDetails2{
		ErrorMessage:      errorMessage,
		Line:              line,
		Bound:             bound,
		Origin:            origin,
		DepartureTime:     departureTime.Format(timeFormatString),
		OriginName:        originName,
		DestinationName:   destinationName,
		VehicleId:         vehicleId,
		VehicleHistory:    vehicleHistory,
		PredictionMethods: performanceeval.Methods(),

		ActualTime:  actualJourneyTime,
		Predictions: totalPredictions,
		Stddevs:     totalStddevs,
	})
}
/* 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)
}