/* Returns the expected arrival times given vehicleId and naptanId pairs */
func SelectNextBusesByVehicleStopPairs(vehiclesStopsMap map[string]string) []NextBus {
	var nextBuses []NextBus

	innerQueries := make([]string, 0)

	for vehicleId, naptanId := range vehiclesStopsMap {
		innerQueries = append(innerQueries,
			fmt.Sprintf("SELECT line, bound, naptan_id, vehicle_id, expected_arrival_time, already_departed "+
				"FROM next_buses "+
				"WHERE vehicle_id='%v' AND naptan_id='%v'",
				vehicleId, naptanId))
	}

	dbconn.AcquireLock()
	err := db.Select(&nextBuses, strings.Join(innerQueries, " UNION ALL "))
	dbconn.ReleaseLock()

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

	/* Set time zone to UTC */
	for i, v := range nextBuses {
		nextBuses[i].ExpectedArrivalTime = v.ExpectedArrivalTime.In(timezone.Get())
	}
	return nextBuses
}
/* Returns the naptan ids of the terminuses of given line bound combinations,
   as two separate maps */
func SelectTerminuses(lineBoundCombinations map[string]map[string]bool) map[string]map[string]string {
	var stopPoints []StopPoint

	innerQueries := make([]string, 0)
	for line, details := range lineBoundCombinations {
		for bound, _ := range details {
			innerQueries = append(innerQueries,
				fmt.Sprintf(
					"(SELECT line_id, bound, naptan_id FROM line_stop_points WHERE line_id='%v' AND bound='%v' ORDER BY stop_seq DESC LIMIT 1)",
					line, bound))
		}
	}

	dbconn.AcquireLock()
	err := db.Select(&stopPoints, strings.Join(innerQueries, " UNION ALL "))
	dbconn.ReleaseLock()

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

	result := make(map[string]map[string]string)
	for _, stopPoint := range stopPoints {
		_, exists := result[stopPoint.LineId]
		if !exists {
			result[stopPoint.LineId] = make(map[string]string)
		}
		result[stopPoint.LineId][stopPoint.Bound] = stopPoint.NaptanId
	}
	return result
}
/* Returns intermediate stops between (end points inclusive) fromId and toId of line and direction*/
func SelectIntermediateStops(line, bound, fromId, toId string) []string {
	var stopPoints []StopPoint

	dbconn.AcquireLock()
	err := db.Select(&stopPoints,
		"SELECT naptan_id FROM line_stop_points "+
			"WHERE line_id=$1 AND bound=$2 AND stop_seq >="+
			"(SELECT stop_seq FROM line_stop_points WHERE "+
			"line_id=$1 AND bound=$2 AND naptan_id=$3 "+
			"ORDER BY stop_seq LIMIT 1) "+
			"AND stop_seq <= "+
			"(SELECT stop_seq FROM line_stop_points WHERE "+
			"line_id=$1 AND bound=$2 AND naptan_id=$4 "+
			"ORDER BY stop_seq DESC LIMIT 1) "+
			"ORDER BY stop_seq ASC",
		line, bound, fromId, toId)
	dbconn.ReleaseLock()

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

	if len(stopPoints) <= 1 {
		logger.GetLogger().Panicf("No intermediate stops between %v and %v for line %v (%v)",
			fromId, toId, line, bound)
	}

	result := make([]string, len(stopPoints))
	for i, v := range stopPoints {
		result[i] = v.NaptanId
	}
	logger.GetLogger().Info("Get intermediate stops (inclusive) from line_stop_points returned: %v", result)
	return result
}
/* Returns the next buses that are approaching a stop given a line and bound after a certain time */
func SelectNextBuses(lineBoundCombination map[string]string, naptanId string, arrivingAfter time.Time) []NextBus {
	var nextBuses []NextBus

	innerQueries := make([]string, 0)

	for line, bound := range lineBoundCombination {
		innerQueries = append(innerQueries,
			fmt.Sprintf("SELECT line, bound, naptan_id, vehicle_id, expected_arrival_time, already_departed "+
				"FROM next_buses "+
				"WHERE naptan_id='%v' AND line='%v' AND bound='%v' AND expected_arrival_time>=TIMESTAMP '%v'",
				naptanId, line, bound, toPsqlTimeStamp(arrivingAfter)))
	}

	sql := fmt.Sprintf("SELECT line, bound, naptan_id, vehicle_id, expected_arrival_time, already_departed "+
		"FROM (%v) AS all_next_buses ORDER BY expected_arrival_time",
		strings.Join(innerQueries, " UNION ALL "))

	logger.GetLogger().Info(sql)
	dbconn.AcquireLock()
	err := db.Select(&nextBuses, sql)
	dbconn.ReleaseLock()

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

	/* Set time zone to UTC */
	for i, v := range nextBuses {
		nextBuses[i].ExpectedArrivalTime = v.ExpectedArrivalTime.In(timezone.Get())
	}
	return nextBuses
}
/* Returns intermediate stops between (end points inclusive) fromIcs and toIcs of line and direction
   NOTE requires the use of table "stops" */
func SelectIntermediateStopsWithIcsCode(line, bound, fromIcs, toIcs string) ([]string, error) {
	var stopPoints []StopPoint

	dbconn.AcquireLock()
	err := db.Select(&stopPoints,
		"SELECT naptan_id FROM line_stop_points "+
			"WHERE line_id=$1 AND bound=$2 AND stop_seq >="+
			"(SELECT stop_seq FROM line_stop_points JOIN stops ON line_stop_points.naptan_id=stops.id "+
			"WHERE line_id=$1 AND bound=$2 AND icscode=$3 "+
			"ORDER BY stop_seq LIMIT 1) "+
			"AND stop_seq <= "+
			"(SELECT stop_seq FROM line_stop_points JOIN stops ON line_stop_points.naptan_id=stops.id "+
			"WHERE line_id=$1 AND bound=$2 AND icscode=$4 "+
			"ORDER BY stop_seq DESC LIMIT 1) "+
			"ORDER BY stop_seq ASC",
		line, bound, fromIcs, toIcs)
	dbconn.ReleaseLock()

	if err != nil {
		return nil, err
	}

	if len(stopPoints) <= 1 {
		return nil, errors.New(
			fmt.Sprintf("No intermediate stops between %v and %v for line %v (%v)",
				fromIcs, toIcs, line, bound))
	}

	result := make([]string, len(stopPoints))
	for i, v := range stopPoints {
		result[i] = v.NaptanId
	}
	logger.GetLogger().Info("Get intermediate stops (inclusive) with icscode from line_stop_points returned: %v", result)
	return result, nil
}
func insertRouteDurationsIntoDB(routeDurations map[string]map[string]map[string]time.Duration,
	tflEstimates map[string]map[string]time.Duration) {
	if len(routeDurations) == 0 {
		logger.GetLogger().Error("Lack of route durations. Cycle skipped...")
		return
	}

	values := make([]string, 0)
	for line, lineDetails := range routeDurations {
		for bound, boundDetails := range lineDetails {
			duration := boundDetails["duration"]
			historicDuration := boundDetails["historicDuration"]
			realTimeDuration := boundDetails["realTimeDuration"]
			tflDuration := tflEstimates[line][bound]

			values = append(values, fmt.Sprintf("('%v', '%v', INTERVAL '%v seconds', INTERVAL '%v seconds', INTERVAL '%v seconds', INTERVAL '%v seconds')",
				line, bound, duration.Seconds(), historicDuration.Seconds(), realTimeDuration.Seconds(), tflDuration.Seconds()))
		}
	}

	valuesCommaSep := strings.Join(values, ",")

	dbconn.AcquireLock()
	db.MustExec("DELETE FROM delayed_routes")
	db.MustExec(
		fmt.Sprintf("INSERT INTO delayed_routes (line, bound, estimate, historic_estimate, real_time_estimate, tfl_estimate) "+
			"VALUES %v", valuesCommaSep))
	dbconn.ReleaseLock()
}
/* Returns a map of current delays
   fromStop => (toStop =>
   		("historicEstimate" => seconds (int64)
   		 "realTimeEstimate" => seconds (int64)
   		 "lastUpdated" => millis in Unix Time (int64))) */
func SelectDelays() map[string]map[string]map[string]int64 {
	var delays []CurrentEstimate

	dbconn.AcquireLock()
	err := db.Select(&delays,
		"SELECT from_id, to_id, last_updated, delay_count, "+
			"EXTRACT (epoch FROM historic_estimate) AS historic_estimate, "+
			"EXTRACT (epoch FROM real_time_estimate) AS real_time_estimate "+
			"FROM current_estimates "+
			"WHERE delay_count >= 3 "+
			"AND last_updated=(SELECT MAX(last_updated) FROM current_estimates)")
	dbconn.ReleaseLock()

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

	results := make(map[string]map[string]map[string]int64)
	for _, record := range delays {
		fromStopDetails, exists := results[record.FromStop]
		if !exists {
			fromStopDetails = make(map[string]map[string]int64)
		}
		fromStopDetails[record.ToStop] = map[string]int64{
			"historicEstimate": record.HistoricEstimate,
			"realTimeEstimate": record.RealTimeEstimate,
			"delayCount":       record.DelayCount,
			"lastUpdated":      record.LastUpdated.Unix(),
		}
		results[record.FromStop] = fromStopDetails
	}
	return results
}
/* Returns the stop sequence number, given the line, direction and naptanId of the stop
   Should there be ties, an error is returned */
func SelectStopSeq(line, bound, naptanId string) (int, error) {
	var stopPoints []StopPoint

	dbconn.AcquireLock()
	err := db.Select(&stopPoints,
		"SELECT stop_seq FROM line_stop_points WHERE line_id=$1 AND bound=$2 AND naptan_id=$3",
		line, bound, naptanId)
	dbconn.ReleaseLock()

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

	if len(stopPoints) == 0 {
		logger.GetLogger().Error("Failed to find stop sequence number for stop %v of line %v (%v)",
			naptanId, line, bound)
		return -1, errors.New(
			fmt.Sprintf("Failed to find stop sequence number for stop %v of line %v (%v)",
				naptanId, line, bound))
	}

	if len(stopPoints) > 1 {
		logger.GetLogger().Error("Expected unique stop sequence number for stop %v of line %v (%v), but got: %v",
			naptanId, line, bound, stopPoints)
		return -1, errors.New(
			fmt.Sprintf("Expected unique stop sequence number for stop %v of line %v (%v), but got: %v",
				naptanId, line, bound, stopPoints))
	}

	result := stopPoints[0].StopSeq
	return result, nil
}
func createMedian() {
	createFinalMedianSql := "CREATE OR REPLACE FUNCTION _final_median(NUMERIC[]) " +
		"RETURNS NUMERIC AS " +
		"$$ " +
		"SELECT AVG(val) " +
		"FROM ( " +
		"SELECT val " +
		"FROM unnest($1) val " +
		"ORDER BY 1 " +
		"LIMIT  2 - MOD(array_upper($1, 1), 2) " +
		"OFFSET CEIL(array_upper($1, 1) / 2.0) - 1 " +
		") sub; " +
		"$$ " +
		"LANGUAGE 'sql' IMMUTABLE"
	createMedianSql := "CREATE AGGREGATE median(NUMERIC) ( " +
		"SFUNC=array_append, " +
		"STYPE=NUMERIC[], " +
		"FINALFUNC=_final_median, " +
		"INITCOND='{}' " +
		")"

	dbconn.AcquireLock()
	tx, err := db.Begin()
	tx.Exec(createFinalMedianSql)
	tx.Exec(createMedianSql)
	err = tx.Commit()
	dbconn.ReleaseLock()

	if err != nil {
		logger.GetLogger().Warning(err.Error())
		return
	}
	logger.GetLogger().Info("Function 'median' created successfully")
}
/* Returns all current estimates of bus travel times
   CATCH: Not all stop pairs may be present, especially if estimate is not recently generated
          e.g. routes served by night buses in the day*/
func SelectAllCurrentEstimates() map[string]map[string]time.Duration {
	var currentEstimates []CurrentEstimate

	dbconn.AcquireLock()
	err := db.Select(&currentEstimates,
		"SELECT from_id, to_id, last_updated, "+
			"EXTRACT(epoch FROM estimate) AS estimate "+
			"FROM current_estimates "+
			"WHERE last_updated=(SELECT MAX(last_updated) FROM current_estimates)")
	dbconn.ReleaseLock()

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

	result := make(map[string]map[string]time.Duration)
	for _, currentEstimate := range currentEstimates {
		fromStop := currentEstimate.FromStop
		toStop := currentEstimate.ToStop
		estimate := time.Duration(currentEstimate.Estimate) * time.Second

		/* Depending on whether from stop exists or not, decide whether there
		   is need to create a new map in results */
		toStopMap, exists := result[fromStop]
		if !exists {
			toStopMap = make(map[string]time.Duration)
		}
		toStopMap[toStop] = estimate
		result[fromStop] = toStopMap
	}
	return result
}
func replaceTimetable(line, bound, origin string, newTimetable map[time.Weekday]map[selectdb.LocalTime]bool) {
	values := make([]string, 0)
	for weekday, departureTimes := range newTimetable {
		for departureTime, _ := range departureTimes {
			values = append(values, fmt.Sprintf("('%v', '%v', '%v', %v, TIME '%02d:%02d:00')",
				line, bound, origin, int(weekday), departureTime.Hour, departureTime.Minute))
		}
	}

	dbconn.AcquireLock()
	tx := db.MustBegin()
	tx.MustExec(fmt.Sprintf("DELETE FROM timetable WHERE line_id='%v' AND bound='%v' AND naptan_id='%v'", line, bound, origin))

	logger.GetLogger().Info(fmt.Sprintf("INSERT INTO timetable (line_id, bound, naptan_id, weekday, departure_time) VALUES %s",
		strings.Join(values, ",")))
	tx.MustExec(
		fmt.Sprintf("INSERT INTO timetable (line_id, bound, naptan_id, weekday, departure_time) VALUES %s",
			strings.Join(values, ",")))
	err := tx.Commit()
	dbconn.ReleaseLock()

	if err != nil {
		logger.GetLogger().Error(err.Error())
	}
	logger.GetLogger().Info("Replaced timetable for line %v (%v), stop %v", line, bound, origin)
}
/* Returns sequences of all naptanIds, grouped by line and bound
   map (line => (bound => []naptanIds)) */
func SelectAllLineStopPoints() map[string]map[string][]string {
	var stopPoints []StopPoint

	dbconn.AcquireLock()
	err := db.Select(&stopPoints,
		"SELECT line_id, bound, stop_seq, naptan_id "+
			"FROM line_stop_points "+
			"ORDER BY line_id, bound, stop_seq")
	dbconn.ReleaseLock()

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

	result := make(map[string]map[string][]string)
	for _, v := range stopPoints {
		lineDetails, exists := result[v.LineId]
		if !exists {
			lineDetails = make(map[string][]string)
		}

		orderedStopPoints, exists := lineDetails[v.Bound]
		if !exists {
			orderedStopPoints = make([]string, 0)
		}

		orderedStopPoints = append(orderedStopPoints, v.NaptanId)
		lineDetails[v.Bound] = orderedStopPoints
		result[v.LineId] = lineDetails
	}
	logger.GetLogger().Info("Get existing line stop points returned: %v", result)
	return result
}
/* Returns timetable for specified line, bound and stop in the form of a map
   map (weekday => []("hour" => hour
                    "minute" => minute)) */
func SelectTimetable(line, bound, naptanId string) map[time.Weekday]map[LocalTime]bool {
	var timetable []TimetableEntry

	dbconn.AcquireLock()
	err := db.Select(&timetable,
		"SELECT weekday, departure_time FROM timetable WHERE line_id=$1 AND bound=$2 AND naptan_id=$3 ORDER BY weekday,departure_time",
		line, bound, naptanId)
	dbconn.ReleaseLock()

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

	result := make(map[time.Weekday]map[LocalTime]bool)
	for _, entry := range timetable {
		weekday := time.Weekday(entry.Weekday)
		weekdayMap, exists := result[weekday]
		if !exists {
			weekdayMap = make(map[LocalTime]bool, 0)
			result[weekday] = weekdayMap
		}
		weekdayMap[LocalTime{Hour: entry.DepartureTime.Hour(), Minute: entry.DepartureTime.Minute()}] = true
	}
	logger.GetLogger().Info("Get timetable for line %v (%v), stop %v returned: %v", line, bound, naptanId, result)
	return result
}
/* Returns all available routes stored in tfl_predictions_history
   []map ("line" => line,
   		  "bound" => bound,
   		  "fromStop" => fromStop,
   		  "toStop" => toStop)
*/
func SelectAvailableRoutesTflPredictions() []map[string]string {
	type AvailableRoute struct {
		Line     string `db:"line"`
		Bound    string `db:"bound"`
		FromStop string `db:"from_id"`
		ToStop   string `db:"to_id"`
	}

	var availableRoutes []AvailableRoute

	dbconn.AcquireLock()
	err := db.Select(&availableRoutes, "SELECT DISTINCT line, bound, from_id, to_id FROM tfl_predictions_history")
	dbconn.ReleaseLock()

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

	result := make([]map[string]string, len(availableRoutes))
	for i, v := range availableRoutes {
		result[i] = map[string]string{
			"line":     v.Line,
			"bound":    v.Bound,
			"fromStop": v.FromStop,
			"toStop":   v.ToStop,
		}
	}
	return result
}
/* Inserts expected departures found above into table "expected_departures".
   To supply map of expected departures
   map (vehicleId =>
	("line" => line,
	 "bound" => bound,
	 "naptanId" => naptanId of terminus,
	 "expectedDeparture" => expectedDeparture in TfL format,
	))
*/
func insertExpectedDeparturesIntoDB(expectedDepartures map[string]map[string]string) {
	values := make([]string, 0)
	for vehicleId, details := range expectedDepartures {
		values = append(values,
			fmt.Sprintf("('%v', '%v', '%v', '%v', TIMESTAMP '%v')",
				details["line"], details["bound"], details["naptanId"], vehicleId,
				toPsqlTimeStamp(details["expectedDeparture"])))
	}

	valuesCommaSep := strings.Join(values, ",")

	dbconn.AcquireLock()
	db.MustExec(
		fmt.Sprintf("UPDATE terminus_departures AS entry "+
			"SET line=data.line, bound=data.bound, naptan_id=data.naptan_id, expected_departure_time=data.expected_departure_time, last_updated=DEFAULT "+
			"FROM (VALUES %s) AS data(line, bound, naptan_id, vehicle_id, expected_departure_time) "+
			"WHERE data.vehicle_id=entry.vehicle_id",
			valuesCommaSep))

	db.MustExec(
		fmt.Sprintf("WITH data(line, bound, naptan_id, vehicle_id, expected_departure_time) AS (VALUES %s) "+
			"INSERT INTO terminus_departures(line, bound, naptan_id, vehicle_id, expected_departure_time) "+
			"SELECT data.line, data.bound, data.naptan_id, data.vehicle_id, data.expected_departure_time FROM data WHERE NOT EXISTS "+
			"(SELECT 1 FROM terminus_departures entry "+
			"WHERE data.vehicle_id=entry.vehicle_id)",
			valuesCommaSep))
	dbconn.ReleaseLock()
}
func DeleteOldHistory(days int) {
	/* Remove data older than days old from journey_history to save space */
	dbconn.AcquireLock()
	_, err := db.Exec(fmt.Sprintf("DELETE FROM journey_history WHERE to_timestamp < NOW() - INTERVAL '%v days'", days))
	dbconn.ReleaseLock()

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

	/* Remove data older than days old from skipped_stops_history to save space */
	dbconn.AcquireLock()
	_, err = db.Exec(fmt.Sprintf("DELETE FROM skipped_stops_history WHERE to_timestamp < NOW() - INTERVAL '%v days'", days))
	dbconn.ReleaseLock()

	if err != nil {
		logger.GetLogger().Panic(err)
	}
}
/* Returns the tfl predictions at the times specified for a given line, bound, origin and destination
   map time.Time => time.Duration */
func SelectTflPredictions(line, bound, fromStop, toStop string, times []time.Time) map[time.Time]time.Duration {
	tflPredictions := make([]int64, len(times))

	dbconn.AcquireLock()
	tx, err := db.Begin()
	for i, targetTime := range times {

		upperBound := targetTime.Add(10 * time.Minute)
		lowerBound := targetTime.Add(-10 * time.Minute)

		logger.GetLogger().Info(fmt.Sprintf("SELECT EXTRACT(epoch FROM predicted_time) FROM ("+
			"(SELECT predicted_time, departure_time FROM tfl_predictions_history "+
			"WHERE line='%v' AND bound='%v' AND from_id='%v' AND to_id='%v' AND departure_time>='%v' AND departure_time <= '%v' ORDER BY departure_time LIMIT 1) "+
			"UNION ALL "+
			"(SELECT predicted_time, departure_time FROM tfl_predictions_history "+
			"WHERE line='%v' AND bound='%v' AND from_id='%v' AND to_id='%v' AND departure_time<'%v' AND departure_time >= '%v' ORDER BY departure_time DESC LIMIT 1) "+
			") AS result ORDER BY abs(EXTRACT(epoch FROM departure_time-'%v')) LIMIT 1",
			line, bound, fromStop, toStop, toPsqlTimeStamp(targetTime), toPsqlTimeStamp(upperBound),
			line, bound, fromStop, toStop, toPsqlTimeStamp(targetTime), toPsqlTimeStamp(lowerBound),
			toPsqlTimeStamp(targetTime)))

		err = tx.QueryRow(
			fmt.Sprintf("SELECT EXTRACT(epoch FROM predicted_time) FROM ("+
				"(SELECT predicted_time, departure_time FROM tfl_predictions_history "+
				"WHERE line='%v' AND bound='%v' AND from_id='%v' AND to_id='%v' AND departure_time>='%v' AND departure_time <= '%v' ORDER BY departure_time LIMIT 1) "+
				"UNION ALL "+
				"(SELECT predicted_time, departure_time FROM tfl_predictions_history "+
				"WHERE line='%v' AND bound='%v' AND from_id='%v' AND to_id='%v' AND departure_time<'%v' AND departure_time >= '%v' ORDER BY departure_time DESC LIMIT 1) "+
				") AS result ORDER BY abs(EXTRACT(epoch FROM departure_time-'%v')) LIMIT 1",
				line, bound, fromStop, toStop, toPsqlTimeStamp(targetTime), toPsqlTimeStamp(upperBound),
				line, bound, fromStop, toStop, toPsqlTimeStamp(targetTime), toPsqlTimeStamp(lowerBound),
				toPsqlTimeStamp(targetTime))).Scan(&tflPredictions[i])
		if err != nil {
			logger.GetLogger().Error(err.Error())
			tflPredictions[i] = 0
		}
	}
	err = tx.Commit()
	dbconn.ReleaseLock()

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

	result := make(map[time.Time]time.Duration)
	for i, t := range times {
		/* Skip if no recent prediction found (within 20 minute window) */
		tflPrediction := tflPredictions[i]
		if tflPrediction == 0 {
			continue
		}
		result[t] = time.Duration(tflPredictions[i]) * time.Second
	}
	return result
}
/* Returns the time required for each cycle of update process, so that script can compensate for time difference*/
func SelectCurrentEstimatesCompensation() (time.Duration, error) {
	var compensationInSeconds int64

	dbconn.AcquireLock()
	err := db.QueryRow("SELECT EXTRACT(epoch FROM MAX(last_updated)-MIN(last_updated)) " +
		"FROM (SELECT last_updated FROM current_estimates ORDER BY last_updated DESC LIMIT 2) AS lastest").Scan(&compensationInSeconds)
	dbconn.ReleaseLock()

	if err != nil {
		return 0, err
	}
	return time.Duration(compensationInSeconds) * time.Second, nil
}
func SelectPerformanceMeasure2(startTime time.Time) (map[string]map[string][]map[string]string, error) {
	var performanceMeasures []PerformanceMeasure

	dbconn.AcquireLock()
	err := db.Select(&performanceMeasures, "SELECT line, bound, vehicle_id, from_id, to_id, departure_time, arrival_time, "+
		"EXTRACT(epoch FROM tfl_expected_time) AS tfl_expected_time, "+
		"EXTRACT(epoch FROM predicted_time) AS predicted_time, "+
		"EXTRACT(epoch FROM actual_time) AS actual_time "+
		"FROM performance_measure_2 "+
		"WHERE db_inserted_timestamp>=$1", startTime)
	dbconn.ReleaseLock()

	if err != nil {
		return nil, err
	}

	result := make(map[string]map[string][]map[string]string)

	for _, performanceMeasure := range performanceMeasures {
		/* Insert into results, careful not to overwrite map if already exists */
		boundDetails, boundDetailsExists := result[performanceMeasure.Line]
		if !boundDetailsExists {
			boundDetails = make(map[string][]map[string]string)
		}

		innerSlice, innerSliceExists := boundDetails[performanceMeasure.Bound]
		if !innerSliceExists {
			innerSlice = make([]map[string]string, 0)
		}

		details := make(map[string]string)
		details["fromStop"] = performanceMeasure.FromStop
		details["toStop"] = performanceMeasure.ToStop
		details["tflExpectedTime"] = toPsqlFormatDuration(performanceMeasure.TflExpectedTime)
		details["predictedTime"] = toPsqlFormatDuration(performanceMeasure.PredictedTime)
		details["actualTime"] = toPsqlFormatDuration(performanceMeasure.ActualTime)
		details["departureTime"] = toPsqlFormatTime(performanceMeasure.DepartureTime)
		details["arrivalTime"] = toPsqlFormatTime(performanceMeasure.ArrivalTime)
		details["vehicleId"] = performanceMeasure.VehicleId

		innerSlice = append(innerSlice, details)

		boundDetails[performanceMeasure.Bound] = innerSlice

		result[performanceMeasure.Line] = boundDetails
	}

	return result, nil
}
/* Returns the naptan id of a stop given its line, bound and stop sequence */
func SelectStopWithStopSeq(line, bound string, stopSeq int) string {
	var naptanId string

	dbconn.AcquireLock()
	err := db.QueryRow("SELECT naptan_id "+
		"FROM line_stop_points "+
		"WHERE line_id=$1 "+
		"AND bound=$2 "+
		"AND stop_seq=$3 ", line, bound, stopSeq).Scan(&naptanId)
	dbconn.ReleaseLock()

	if err != nil {
		logger.GetLogger().Panic(err)
	}
	return naptanId
}
/* Returns whether the given line is outbound only */
func SelectIsOutboundOnly(line string) bool {
	var isOutboundOnly bool

	dbconn.AcquireLock()
	err := db.QueryRow("SELECT NOT EXISTS "+
		"(SELECT 1 FROM line_stop_points "+
		"WHERE line_id=$1 "+
		"AND bound='inbound')", line).Scan(&isOutboundOnly)
	dbconn.ReleaseLock()

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

	return isOutboundOnly
}
/* Returns the station naptan conrresponding to a naptan id*/
func SelectStationNaptan(id string) (string, error) {
	var stop []Stop

	dbconn.AcquireLock()
	err := db.Select(&stop, "SELECT station_naptan FROM stops WHERE id=$1", id)
	dbconn.ReleaseLock()
	if err != nil {
		return "", err
	}

	if len(stop) != 1 {
		return "", errors.New(fmt.Sprintf("Expected exactly 1 name for naptan id: %v, but got: %v", id, stop))
	}

	return stop[0].StationNaptan, nil
}
/* Return stop name given its naptan id */
func SelectStop(id string) string {
	var stop []Stop

	dbconn.AcquireLock()
	err := db.Select(&stop, "SELECT name FROM stops WHERE id=$1", id)
	dbconn.ReleaseLock()
	if err != nil {
		logger.GetLogger().Panic(err)
	}

	if len(stop) != 1 {
		logger.GetLogger().Panicf("Expected exactly 1 name for naptan id: %v, but got: %v", id, stop)
	}

	return stop[0].Name
}
/* Inserts the three times into database for comparison */
func insertIntoPerformanceMeasureDB(line, bound, vehicleId, origin, destination string, tflExpectedTime, predictedTime, actualTime time.Duration,
	departureTime, arrivalTime time.Time) {
	dbconn.AcquireLock()
	_, err := db.Exec(
		fmt.Sprintf("INSERT INTO performance_measure(line, bound, vehicle_id, from_id, to_id, "+
			"tfl_expected_time, predicted_time, actual_time, departure_time, arrival_time, db_inserted_timestamp) "+
			"VALUES ('%v', '%v', '%v', '%v', '%v', INTERVAL '%v', INTERVAL '%v', INTERVAL '%v', "+
			"TIMESTAMP '%v', TIMESTAMP '%v', TIMESTAMP '%v')",
			line, bound, vehicleId, origin, destination,
			int(tflExpectedTime.Seconds()), int(predictedTime.Seconds()), int(actualTime.Seconds()),
			toPsqlTimeStamp(departureTime), toPsqlTimeStamp(arrivalTime), getCurrentPsqlTimeStamp()))
	dbconn.ReleaseLock()

	if err != nil {
		logger.GetLogger().Panic(err)
	}
}
func SelectLines() []string {
	var lines []Line

	dbconn.AcquireLock()
	err := db.Select(&lines, "SELECT id FROM lines")
	dbconn.ReleaseLock()
	if err != nil {
		logger.GetLogger().Panic(err)
	}

	result := make([]string, len(lines))
	for i, v := range lines {
		result[i] = v.Id
	}
	logger.GetLogger().Info("Get existing lines returned: %v", result)
	return result
}
/* Returns slice of naptan ids */
func SelectStops() []string {
	var stops []Stop

	dbconn.AcquireLock()
	err := db.Select(&stops, "SELECT id FROM stops")
	dbconn.ReleaseLock()
	if err != nil {
		logger.GetLogger().Panic(err)
	}

	result := make([]string, len(stops))
	for i, v := range stops {
		result[i] = v.Id
	}
	logger.GetLogger().Info("Get existing stops returned: %v", result)
	return result
}
/* Returns map of adjacent stops (from_id => to_id) serving given lines and bounds,
   also individual adjacent stops grouped by line and bound */
func SelectMultipleAdjacentStops(lineBoundCombinations map[string]map[string]bool) (map[string]map[string]bool, map[string]map[string]map[string]string) {
	var adjacentStops []AdjacentStop

	innerQueries := make([]string, 0)

	for line, details := range lineBoundCombinations {
		for bound, _ := range details {
			innerQueries = append(innerQueries,
				fmt.Sprintf("SELECT line_id, bound, from_id, to_id FROM adjacent_stops WHERE line_id='%v' AND bound='%v'",
					line, bound))
		}
	}

	dbconn.AcquireLock()
	err := db.Select(&adjacentStops, strings.Join(innerQueries, " UNION ALL "))
	dbconn.ReleaseLock()

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

	result := make(map[string]map[string]bool)
	resultsByLineBound := make(map[string]map[string]map[string]string)
	for _, v := range adjacentStops {
		/* Handle result */
		_, exists := result[v.FromId]
		if !exists {
			result[v.FromId] = make(map[string]bool)
		}
		result[v.FromId][v.ToId] = true

		/* Handle result by line bound */
		_, exists = resultsByLineBound[v.LineId]
		if !exists {
			resultsByLineBound[v.LineId] = make(map[string]map[string]string)
		}
		_, exists = resultsByLineBound[v.LineId][v.Bound]
		if !exists {
			resultsByLineBound[v.LineId][v.Bound] = make(map[string]string)
		}
		resultsByLineBound[v.LineId][v.Bound][v.FromId] = v.ToId
	}
	logger.GetLogger().Info("Get existing adjacent stops for lines %v returned: %v", lineBoundCombinations, result)
	return result, resultsByLineBound
}
/*
  Returns the longest lasting time satisfying supplied constraints
*/
func SelectDelayedRoutes() []DelayedRoute {
	var delayedRoutes []DelayedRoute

	dbconn.AcquireLock()
	err := db.Select(&delayedRoutes,
		"SELECT line, bound, "+
			"EXTRACT(epoch FROM estimate) AS estimate, "+
			"EXTRACT(epoch FROM historic_estimate) AS historic_estimate, "+
			"EXTRACT(epoch FROM real_time_estimate) AS real_time_estimate, "+
			"EXTRACT(epoch FROM tfl_estimate) AS tfl_estimate "+
			"FROM delayed_routes")
	dbconn.ReleaseLock()

	if err != nil {
		logger.GetLogger().Panic(err)
	}
	return delayedRoutes
}
/* Returns terminus given line and direction */
func SelectTerminus(line, bound string) string {
	var stopPoints []StopPoint

	dbconn.AcquireLock()
	err := db.Select(&stopPoints,
		"SELECT naptan_id FROM line_stop_points WHERE line_id=$1 AND bound=$2 ORDER BY stop_seq DESC LIMIT 1",
		line, bound)
	dbconn.ReleaseLock()

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

	if len(stopPoints) == 0 {
		logger.GetLogger().Panicf("Failed to find terminus for line %v (%v)", line, bound)
	}
	result := stopPoints[0].NaptanId
	return result
}
/* Returns slice of all naptanIds from table line_stop_points */
func SelectAllStopsFromLineStopPoints() []string {
	var stopPoints []StopPoint

	dbconn.AcquireLock()
	err := db.Select(&stopPoints,
		"SELECT DISTINCT naptan_id FROM line_stop_points")
	dbconn.ReleaseLock()

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

	result := make([]string, len(stopPoints))
	for i, v := range stopPoints {
		result[i] = v.NaptanId
	}
	logger.GetLogger().Info("Get all stop points from line_stop_points returned: %v", result)
	return result
}