/* Generates predicted joruney times based on historic average and real time measurements */
func getPredictedJourneyTime(stopPoints []string) (time.Duration, error) {
	adjacentStops := make(map[string]map[string]bool)
	for i := 0; i < len(stopPoints)-1; i++ {
		fromStop := stopPoints[i]
		toStop := stopPoints[i+1]

		fromStopMap, fromStopExists := adjacentStops[fromStop]
		if !fromStopExists {
			fromStopMap = make(map[string]bool)
		}
		fromStopMap[toStop] = true
		adjacentStops[fromStop] = fromStopMap
	}
	currentEstimates, _, _, _, _, err := selectdb.SelectCurrentEstimates(adjacentStops)
	if err != nil {
		return 0, err
	}

	counterInit := time.Time{}
	counter := counterInit
	for _, details := range currentEstimates {
		for _, duration := range details {
			counter = counter.Add(duration)
		}
	}
	return counter.Sub(counterInit), nil
}
/* Returns our prediction of how long it takes (mixed/historic/real time) for a given line to travel from a stop to another */
func updateBusPrediction(line, bound string, intermediateStopsOrdered []string, departureTime time.Time) (time.Duration, time.Duration, time.Duration, map[string]map[string]time.Duration, error) {
	intermediateStops := make(map[string]map[string]bool)
	for i := 0; i < len(intermediateStopsOrdered)-1; i++ {
		fromStop := intermediateStopsOrdered[i]
		toStop := intermediateStopsOrdered[i+1]
		intermediateStops[fromStop] = map[string]bool{
			toStop: true,
		}
	}

	/* Short circut if departure time is found to be recent, and use current_estimates instead */
	if departureTime.Before(time.Now().Add(time.Hour)) {
		logger.GetLogger().Info("Case 1")
		_, historicEstimates, realTimeEstimates, expectedLastingTimes, delayCounts, err := selectdb.SelectCurrentEstimates(intermediateStops)

		prediction := time.Duration(0)
		historicPrediction := time.Duration(0)
		realTimePrediction := time.Duration(0)
		predictionAdjacentStops := make(map[string]map[string]time.Duration)

		for i := 0; i < len(intermediateStopsOrdered)-1; i++ {
			fromStop := intermediateStopsOrdered[i]
			toStop := intermediateStopsOrdered[i+1]
			expectedLastingTime := expectedLastingTimes[fromStop][toStop]
			delayCount := delayCounts[fromStop][toStop]

			var estimate time.Duration
			if delayCount >= 3 ||
				time.Now().Add(expectedLastingTime).After(departureTime.Add(prediction)) {
				estimate = realTimeEstimates[fromStop][toStop]
			} else {
				estimate = historicEstimates[fromStop][toStop]
			}
			prediction += estimate
			historicPrediction += historicEstimates[fromStop][toStop]
			realTimePrediction += realTimeEstimates[fromStop][toStop]
			predictionAdjacentStops[fromStop] = map[string]time.Duration{
				toStop: estimate,
			}
		}
		return prediction, historicPrediction, realTimePrediction, predictionAdjacentStops, err

	} else {
		logger.GetLogger().Info("Case 2")
		estimates2, historicEstimates2, realTimeEstimates2, err := performanceeval.PredictJourneyWithMixedData(performanceeval.DefaultMethod(), line, bound, intermediateStops, intermediateStopsOrdered, departureTime)
		if err != nil {
			return 0, 0, 0, nil, err
		}
		/* Convert data structures */
		estimates := castEstimates(estimates2)
		historicEstimates := castEstimates(historicEstimates2)
		realTimeEstimates := castEstimates(realTimeEstimates2)

		prediction := time.Duration(0)
		historicPrediction := time.Duration(0)
		realTimePrediction := time.Duration(0)
		predictionAdjacentStops := make(map[string]map[string]time.Duration)

		for i := 0; i < len(intermediateStopsOrdered)-1; i++ {
			fromStop := intermediateStopsOrdered[i]
			toStop := intermediateStopsOrdered[i+1]
			historicPrediction += historicEstimates[fromStop][toStop]
			realTimePrediction += realTimeEstimates[fromStop][toStop]
			prediction += estimates[fromStop][toStop]
			predictionAdjacentStops[fromStop] = map[string]time.Duration{
				toStop: estimates[fromStop][toStop],
			}
		}
		return prediction, historicPrediction, realTimePrediction, predictionAdjacentStops, err
	}

}
/* Get durations, historic and real time for all routes
   map (line =>
   	           (bound =>
   	           		("duration" => duration
   	           		 "historicDuration" => historic duration
   	           		 "realTimeDuration" => real time duration))) */
func getRouteDurations(lineBounds map[string]map[string]bool) map[string]map[string]map[string]time.Duration {
	allLineStopPoints := selectdb.SelectMultipleLineStopPoints(lineBounds)
	adjacentStops := extractAdjacentStops(allLineStopPoints)

	currentEstimates, currentHistoricEstimates, currentRealTimeEstimates, _, delayCounts, _ := selectdb.SelectCurrentEstimates(adjacentStops)

	routeDurations := make(map[string]map[string]map[string]time.Duration)

	for line, lineDetails := range lineBounds {
		for bound, _ := range lineDetails {

			duration, historicDuration, realTimeDuration, err := getRouteDuration(line, bound, allLineStopPoints[line][bound],
				currentEstimates, currentHistoricEstimates, currentRealTimeEstimates, delayCounts)

			if err != nil {
				logger.GetLogger().Error("Failed to get route duartion for line %v (%v): %v", line, bound, err.Error())
				continue
			}

			_, exists := routeDurations[line]
			if !exists {
				routeDurations[line] = make(map[string]map[string]time.Duration)
			}
			routeDurations[line][bound] = map[string]time.Duration{
				"duration":         duration,
				"historicDuration": historicDuration,
				"realTimeDuration": realTimeDuration,
			}
		}
	}
	return routeDurations
}