/* Checks whether bus is still serving in same route and direction.
If so, update database and cache.
Otherwise, only update cache. */
func updateCacheDBWithArrivalsBundle(line string, arrivalsBundle, cache map[string]map[string]string,
	numPredictionsPerStop *int) {

	// For logging purpose
	newBuses := make([]string, 0)
	arrivedStop := make([]string, 0)
	withdrawnBuses := make([]string, 0)

	// For updating database later below
	values := make([]string, 0)
	skippedStopsValues := make([]string, 0)

	// For debugging purpose - store all those original (raw) times that are interpolated to zero, say due to
	// backwards in time
	rawArrivalTimes := make([]string, 0)

	// TAKEN OUT FOR NOW SINCE BUS PREDICTIONS MAY TEMPORARILY DISAPPEAR - NOT RELIABLE
	// Remove withdrawn buses
	//for vehicleId, _ := range cache {
	//	/* If bus found in cache, but not in arrivalsBundle, remove from cache. */
	//	if arrivalsBundle[vehicleId] == nil {
	//		withdrawnBuses = append(withdrawnBuses, vehicleId)
	//		delete(cache, vehicleId)
	//	}
	//}

	for vehicleId, bundle := range arrivalsBundle {
		/* If bus not found in cache, add to cache */
		if cache[vehicleId] == nil {
			cache[vehicleId] = createNewCacheEntry(bundle)
			newBuses = append(newBuses, vehicleId)
			continue
		}

		cachedEntry := cache[vehicleId]
		cachedDirection := cachedEntry["direction"]
		cachedFromStop := cachedEntry["fromStop"]
		cachedFromStopExpectedArrival := cachedEntry["fromStopExpectedArrival"]
		cachedFromStopTflTimestamp := cachedEntry["fromStopTflTimestamp"]
		cachedToStop := cachedEntry["toStop"]
		cachedToStopExpectedArrival := cachedEntry["toStopExpectedArrival"]
		cachedToStopTflTimestamp := cachedEntry["toStopTflTimestamp"]

		/* Discard incoming prediction if in the past, just in case
		   (n.b. allow 3 min as buffer due to heavy cpu usage causing delay in processing data)  */
		if timeStampHasPassedBy(bundle["expectedArrival"], 3*time.Minute) {
			/*logger.GetLogger().Error("Expected arrival %v of bus %v: %v (%v) is in the past, skipped.",
				bundle["expectedArrival"], vehicleId, line, bundle["direction"])
			continue*/

			// Temporary fix -- sometimes webconn stops unexpectedly, works on restart of program
			// Change (to a new) numPredictionsPerStop to hopefully remedy the issue
			logger.GetLogger().Error("Expected arrival %v of bus %v: %v (%v) is in the past, skipped.",
				bundle["expectedArrival"], vehicleId, line, bundle["direction"])
			for {
				newNumPredictionsPerStop := getNumPredictionsPerStop()
				if *numPredictionsPerStop != newNumPredictionsPerStop {
					logger.GetLogger().Info("Changed numPredictionsPerStop from %v to %v", *numPredictionsPerStop, newNumPredictionsPerStop)
					*numPredictionsPerStop = newNumPredictionsPerStop
					break
				}
			}
			logger.UseNewLogFile()
			return
		}

		// Handle first time case where fromStop is not yet present in cache
		if cachedFromStopExpectedArrival == "" {
			if !timeStampHasPassed(cachedToStopExpectedArrival) {
				originalToStopSeq, err := selectdb.SelectStopSeq(line, cachedDirection, cachedToStop)
				newToStopSeq, err2 := selectdb.SelectStopSeq(line, cachedDirection, bundle["naptanId"])
				if err != nil || err2 != nil {
					/* Stop may not be well formed, i.e. not on line and direction
					   Bus could have changed direction / terminate early
					   In such cases, safest to delete from cache and start all over again for this vehicle */
					delete(cache, vehicleId)
					continue
				}
				if newToStopSeq <= originalToStopSeq {
					/* Ensure we are not accidentally advancing bus if departure time has not passed.
					   This is because predictions can temporarily disappear,
					   leading to fluctuations in between neighbouring stops even when it is not yet time */
					cache[vehicleId] = updateCacheEntry(cachedEntry, bundle)
				}
			} else {
				cache[vehicleId] = shiftCacheEntry(cachedEntry, bundle)
			}
			continue
		}

		/* If current prediction points to stop at or before cached prediction, update it, since
		   it is guaranteed to be in the future if reached here. */
		fromStopSeq, err := selectdb.SelectStopSeq(line, cachedDirection, cachedFromStop)
		toStopSeq, err2 := selectdb.SelectStopSeq(line, cachedDirection, cachedToStop)
		if err != nil || err2 != nil {
			/* Stop may not be well formed, i.e. not on line and direction
			   Bus could have changed direction / terminate early (if changed direction proceed with interpolation)
			   In such cases, safest to delete from cache and start all over again for this vehicle */
			if err == nil {
				interpolateIfChangedDirection(line, cachedDirection, vehicleId,
					cachedFromStop, cachedToStop, cachedFromStopExpectedArrival, cachedToStopExpectedArrival,
					cachedFromStopTflTimestamp, cachedToStopTflTimestamp,
					&values, &skippedStopsValues, &rawArrivalTimes, cachedEntry)
			}

			delete(cache, vehicleId)
			continue
		}

		if toStopSeq <= fromStopSeq {
			cache[vehicleId] = updateCacheEntry(cachedEntry, bundle)
			continue
		}

		/* Otherwise current prediction points to future stop, update only if timestamp
		   of current prediction has expired, so that bus has left station.
		   And that current prediction refers to a valid time after the cached prediction */
		if !timeStampHasPassed(cachedToStopExpectedArrival) ||
			timeDifference(cachedToStopExpectedArrival, bundle["expectedArrival"]) <= 0 {
			continue
		}

		/* For some random reason expected arrival of to stop happens to occur before from stop.
		   So, check that to stop occurs after from stop. If not, discard. */
		/* Also check that journey time is not erroneously large (e.g. >= 2 hours), if so, discard */
		twoHours := 7200
		timeDiff := timeDifference(cachedFromStopExpectedArrival, cachedToStopExpectedArrival)
		if timeDiff <= 0 || timeDiff >= twoHours {
			cache[vehicleId] = shiftCacheEntry(cachedEntry, bundle)
			continue
		}

		/* Check whether bus has skipped stations, activiate interpolation if required */
		if toStopSeq > fromStopSeq+1 {
			logger.GetLogger().Info("Bus %v of line %v (%v) has skipped stations. From stop: %v. To stop: %v",
				vehicleId, line, cachedDirection, cachedFromStop, cachedToStop)
			cache[vehicleId] = shiftCacheEntry(cachedEntry, bundle)
			arrivedStop = append(arrivedStop, vehicleId)

			/* Add to database using interpolation method */
			interpolateStops(line, cachedDirection, vehicleId, cachedFromStop, cachedToStop,
				cachedFromStopExpectedArrival, cachedToStopExpectedArrival,
				cachedFromStopTflTimestamp, cachedToStopTflTimestamp,
				&values, &skippedStopsValues, &rawArrivalTimes, cachedEntry)
			continue
		}

		values = append(values, fmt.Sprintf("('%v', '%v', '%v', '%v', TIMESTAMP '%v', TIMESTAMP '%v', "+
			"'%v', TIMESTAMP '%v', TIMESTAMP '%v', INTERVAL '%v seconds', FALSE)",
			line, cachedDirection, vehicleId,
			cachedFromStop, toPsqlTimeStamp(cachedFromStopExpectedArrival), toPsqlTimeStamp(cachedFromStopTflTimestamp),
			cachedToStop, toPsqlTimeStamp(cachedToStopExpectedArrival), toPsqlTimeStamp(cachedToStopTflTimestamp),
			timeDiff))
		cache[vehicleId] = shiftCacheEntry(cachedEntry, bundle)
		arrivedStop = append(arrivedStop, vehicleId)
	}

	// Log information
	logger.GetLogger().Info("New buses added to cache of line %v: %v", line, newBuses)
	logger.GetLogger().Info("Buses arrived next stop for line %v: %v", line, arrivedStop)
	logger.GetLogger().Info("Buses withdrawn from line %v: %v", line, withdrawnBuses)

	// If nothing to update in database, return quietly
	if len(values) == 0 {
		return
	}

	dbconn.AcquireLock()
	logger.GetLogger().Info("INSERT INTO journey_history (line, bound, vehicle_id, "+
		"from_id, from_timestamp, from_timestamp_tfl, to_id, to_timestamp, to_timestamp_tfl, "+
		"time_taken, interpolated) VALUES %s",
		strings.Join(values, ","))
	_, err := db.Exec(fmt.Sprintf("INSERT INTO journey_history (line, bound, vehicle_id, "+
		"from_id, from_timestamp, from_timestamp_tfl, to_id, to_timestamp, to_timestamp_tfl, "+
		"time_taken, interpolated) VALUES %s",
		strings.Join(values, ",")))
	dbconn.ReleaseLock()

	if err != nil {
		logger.GetLogger().Panic(err)
	}
	/*
		// If no bus skipped stops, skip quietly
		if len(skippedStopsValues) > 0 {
			dbconn.AcquireLock()
			_, err = db.Exec(
				fmt.Sprintf("INSERT INTO skipped_stops_history (line, bound, vehicle_id, "+
					"from_id, from_timestamp, from_timestamp_tfl, to_id, to_timestamp, to_timestamp_tfl, "+
					"stops_in_between, num_stops_in_between) VALUES %s",
					strings.Join(skippedStopsValues, ",")))
			dbconn.ReleaseLock()

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

		// If no raw arrival times, skip quietly
		if len(rawArrivalTimes) > 0 {
			logger.GetLogger().Info(
				fmt.Sprintf("INSERT INTO raw_arrival_times (line, bound, vehicle_id, naptan_id, timestamp) VALUES %s",
					strings.Join(rawArrivalTimes, ",")))

			dbconn.AcquireLock()
			_, err = db.Exec(
				fmt.Sprintf("INSERT INTO raw_arrival_times (line, bound, vehicle_id, naptan_id, timestamp) VALUES %s",
					strings.Join(rawArrivalTimes, ",")))
			dbconn.ReleaseLock()

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

}
/* Attempts to interpolate until terminus, then back to its reported location (toStop) if bus is found to have
   changed direction near the terminus. */
func interpolateIfChangedDirection(line, bound, vehicleId, fromStop, toStop,
	oldTimeStamp, newTimeStamp, oldTflTimeStamp, newTflTimeStamp string,
	values, skippedStopsValues, rawArrivalTimes *[]string, cachedEntry map[string]string) {
	terminus := selectdb.SelectTerminus(line, bound)

	// Nothing to extrapolate if we already at terminus
	if fromStop == terminus {
		return
	}

	/* Check that we have sufficient data to complete interpolation.
	   Interpolation not possible if predictions up till terminus do not exist, or that they are
	   in the future.  In such case return.*/
	intermediateStopsToTerminus := selectdb.SelectIntermediateStops(line, bound, fromStop, terminus)

	for _, stop := range intermediateStopsToTerminus {
		timeStamp, timeStampPresentInCachedEntry := cachedEntry[stop]
		if !timeStampPresentInCachedEntry || !timeStampHasPassed(timeStamp) {
			return
		}
	}
	timeAtTerminus := cachedEntry[terminus]

	/* Specially handle case where terminus and fromStop are only 1 stop apart since interpolateStops
	   won't work  - it assumes stops are at least 2 apart */
	if len(intermediateStopsToTerminus) == 2 {
		*values = append(*values, fmt.Sprintf("('%v', '%v', '%v', '%v', TIMESTAMP '%v', TIMESTAMP '%v', "+
			"'%v', TIMESTAMP '%v', TIMESTAMP '%v', INTERVAL '%v seconds', FALSE)",
			line, bound, vehicleId,
			fromStop, toPsqlTimeStamp(oldTimeStamp), toPsqlTimeStamp(oldTflTimeStamp),
			terminus, toPsqlTimeStamp(timeAtTerminus), toPsqlTimeStamp(newTflTimeStamp),
			timeDifference(oldTimeStamp, timeAtTerminus)))
	} else {
		interpolateStops(line, bound, vehicleId, fromStop, terminus, oldTimeStamp, timeAtTerminus,
			oldTflTimeStamp, newTflTimeStamp, values, skippedStopsValues, rawArrivalTimes, cachedEntry)
	}

	/* Similar to above -- Further interpolation to current reported location if possible */
	reverseBound := getReverseBound(line, bound)

	// Check that to stop is actually on reverse bound, if not return
	_, err := selectdb.SelectStopSeq(line, reverseBound, toStop)
	if err != nil {
		return
	}

	// Continue with interpolation on reverse bound
	reverseOrigin := selectdb.SelectOrigin(line, reverseBound)
	if toStop == reverseOrigin {
		return
	}

	intermediateStopsReverseBound := selectdb.SelectIntermediateStops(line, reverseBound, reverseOrigin, toStop)
	for _, stop := range intermediateStopsReverseBound {
		timeStamp, timeStampPresentInCachedEntry := cachedEntry[stop]
		if !timeStampPresentInCachedEntry || !timeStampHasPassed(timeStamp) {
			return
		}
	}
	timeAtReverseOrigin := cachedEntry[reverseOrigin]

	if len(intermediateStopsReverseBound) == 2 {
		*values = append(*values, fmt.Sprintf("('%v', '%v', '%v', '%v', TIMESTAMP '%v', TIMESTAMP '%v', "+
			"'%v', TIMESTAMP '%v', TIMESTAMP '%v', INTERVAL '%v seconds', FALSE)",
			line, reverseBound, vehicleId,
			reverseOrigin, toPsqlTimeStamp(timeAtReverseOrigin), toPsqlTimeStamp(oldTflTimeStamp),
			toStop, toPsqlTimeStamp(newTimeStamp), toPsqlTimeStamp(newTflTimeStamp),
			timeDifference(timeAtReverseOrigin, newTimeStamp)))
	} else {
		interpolateStops(line, reverseBound, vehicleId, reverseOrigin, toStop, timeAtReverseOrigin, newTimeStamp,
			oldTflTimeStamp, newTflTimeStamp, values, skippedStopsValues, rawArrivalTimes, cachedEntry)
	}
}