/* Extract all lines and bounds affected by real time delays from list of delayed sections */
func extractAffectedLinesBoundsFromDelays(delays map[string]map[string]map[string]int64) map[string]map[string]bool {
	adjacentStops := make(map[string]map[string]bool)
	for fromStop, fromStopDetails := range delays {
		for toStop, _ := range fromStopDetails {
			_, exists := adjacentStops[fromStop]
			if !exists {
				adjacentStops[fromStop] = make(map[string]bool)
			}
			adjacentStops[fromStop][toStop] = true
		}
	}

	result := make(map[string]map[string]bool)
	lineBoundsServingAdjacentStops := selectdb.SelectLineBoundsServingMultipleAdjacentStops(adjacentStops)
	for _, fromStopDetails := range lineBoundsServingAdjacentStops {
		for _, toStopDetails := range fromStopDetails {
			for line, bound := range toStopDetails {
				_, exists := result[line]
				if !exists {
					result[line] = make(map[string]bool)
				}
				result[line][bound] = true
			}
		}
	}
	return result
}
/* Returns affected lines (as slice) and line bound combinations (as map) for
   each pair of affected adjacent stops in delays */
func extractAllAffectedLineBounds(delays map[string]map[string]map[string]int64) (map[string]map[string][]string, map[string]map[string]map[string]string) {
	if len(delays) == 0 {
		return nil, nil
	}

	adjacentStops := make(map[string]map[string]bool)
	for fromStop, fromStopDetails := range delays {
		for toStop, _ := range fromStopDetails {
			_, exists := adjacentStops[fromStop]
			if !exists {
				adjacentStops[fromStop] = make(map[string]bool)
			}
			adjacentStops[fromStop][toStop] = true
		}
	}

	lineBoundMap := selectdb.SelectLineBoundsServingMultipleAdjacentStops(adjacentStops)
	linesMap := make(map[string]map[string][]string)
	for fromStop, fromStopDetails := range lineBoundMap {
		for toStop, toStopDetails := range fromStopDetails {
			for line, _ := range toStopDetails {
				_, exists := linesMap[fromStop]
				if !exists {
					linesMap[fromStop] = make(map[string][]string)
				}

				_, exists = linesMap[fromStop][toStop]
				if !exists {
					linesMap[fromStop][toStop] = make([]string, 0)
				}
				linesMap[fromStop][toStop] = append(linesMap[fromStop][toStop], line)
			}
		}
	}
	return linesMap, lineBoundMap
}
/* Delay characteristics represented by map
   fromId => (toId => (day of week => (time of day => (delayThreshold => (numBuses => expected lasting time))))) */
func computeDelayCharacteristics(adjacentStops map[string]map[string]bool) map[string]map[string]map[int]map[int]map[time.Duration]map[int]time.Duration {
	delayCharacteristics := make(map[string]map[string]map[int]map[int]map[time.Duration]map[int]time.Duration)

	/* Channel for delay characteristics */
	ch := make(chan map[string]map[string]map[int]map[int]map[time.Duration]map[int]time.Duration)

	lineBounds := selectdb.SelectLineBoundsServingMultipleAdjacentStops(adjacentStops)

	for fromStop, details := range adjacentStops {
		fromStopDelayCharacteristics := make(map[string]map[int]map[int]map[time.Duration]map[int]time.Duration)
		for toStop, _ := range details {
			go func(fromStop, toStop string) {

				/* Form time ranges - Compute for the weekday/weekend of yesterday */
				timeRanges := make(map[time.Time]time.Time)
				endOfYesterday := time.Now().In(timezone.Get()).Truncate(24 * time.Hour)
				startOfYesterday := endOfYesterday.AddDate(0, 0, -1)
				weekday := startOfYesterday.Weekday()
				for _, day := range daysToLookBack {
					timeRanges[startOfYesterday.AddDate(0, 0, -day)] = endOfYesterday.AddDate(0, 0, -day)
				}

				/* Select journey history */
				journeyHistory := selectdb.SelectJourneyHistory(fromStop, toStop, timeRanges, nil)
				times := make([]time.Time, len(journeyHistory))
				actualDurations := make([]time.Duration, len(journeyHistory))

				for i, record := range journeyHistory {
					times[i] = record.ToTimestamp
					actualDurations[i] = time.Duration(record.TimeTaken) * time.Second
				}

				/* Select historic journey times */
				var line, bound string
				for k, v := range lineBounds[fromStop][toStop] {
					line = k
					bound = v
				}
				/* Generate times at intervals for prediction efficiency */
				timesAtIntervals := make([]time.Time, 0)
				for startTime, endTime := range timeRanges {
					for t := startTime.Round(predictionInterval); t.Before(endTime.Add(predictionInterval)); t = t.Add(predictionInterval) {
						timesAtIntervals = append(timesAtIntervals, t)
					}
				}
				historicTimes := PredictJourneyTimes(line, bound, fromStop, toStop, timesAtIntervals, DefaultHistoric(), nil)
				historicDurations := make([]time.Duration, len(journeyHistory))
				for i, t := range times {
					historicDurations[i] = historicTimes[t.Round(predictionInterval)]
				}

				delayCharacteristics := map[string]map[string]map[int]map[int]map[time.Duration]map[int]time.Duration{
					fromStop: map[string]map[int]map[int]map[time.Duration]map[int]time.Duration{
						toStop: computeDelayCharacteristicsForStopPair(fromStop, toStop, weekday, times, actualDurations, historicDurations),
					},
				}
				updateDelayCharacteristics(delayCharacteristics)
				ch <- delayCharacteristics
			}(fromStop, toStop)

			fromStopDelayCharacteristics[toStop] = nil
			// fromStopDelayCharacteristics[toStop] = computeDelayCharacteristicsForStopPair(fromStop, toStop, times, actualDurations, historicDurations)

		}
		// delayCharacteristics[fromStop] = fromStopDelayCharacteristics
		delayCharacteristics[fromStop] = fromStopDelayCharacteristics
	}

	for _, v := range adjacentStops {
		for range v {
			<-ch
			/*entry := <-ch
			delayCharacteristics[entry.fromStop][entry.toStop] = entry.entry
			updateDelayCharacteristics(delayCharacteristics)*/
		}
	}
	return delayCharacteristics
}