/* Inserts into channel arrivalsBundle once data is received from tfl and parsed
  map (vehicleId =>
	("line" => line,
	 "bound" => bound,
	 "naptanId" => naptanId of terminus,
	 "expectedDeparture" => expectedDeparture in TfL format,
	))
*/
func parseAllTerminusDeparturesFromBody(body []byte, numPredictionsPerStop *int) {

	type Arrivals struct {
		LineId            string `json:"lineId"`
		VehicleId         string `json:"vehicleId"`
		Bound             string `json:"direction"`
		NaptanId          string `json:"naptanId"`
		TimeStamp         string `json:"timeStamp"`
		ExpectedDeparture string `json:"expectedArrival"`
	}

	var arrivals []Arrivals

	err := json.Unmarshal(body, &arrivals)

	/* Sometimes Tfl returns "Technical error".
	   In such case we skip and return gracefully */
	if err != nil {
		logger.GetLogger().Error("Tfl failed to give proper arrivals response: %v", string(body))
		return
	}

	/* Select all departing terminuses of all lines
	   map (line => (bound => naptanId)) */
	interestedTerminuses := selectdb.SelectAllOrigins()

	result := make(map[string]map[string]string)
	for _, bus := range arrivals {

		/* Check that incoming prediction is not 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(bus.ExpectedDeparture, 5*time.Minute) {
			// 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.",
				bus.ExpectedDeparture, bus.VehicleId, bus.LineId, bus.Bound)
			for {
				newNumPredictionsPerStop := getNumPredictionsPerStop()
				if *numPredictionsPerStop != newNumPredictionsPerStop {
					logger.GetLogger().Info("Changed numPredictionsPerStop from %v to %v", *numPredictionsPerStop, newNumPredictionsPerStop)
					*numPredictionsPerStop = newNumPredictionsPerStop
					break
				}
			}
			logger.UseNewLogFile()
			return
		}

		/* Check if prediction refers to a departing terminus */
		if bus.NaptanId == interestedTerminuses[bus.LineId][bus.Bound] {
			result[bus.VehicleId] = map[string]string{
				"line":              bus.LineId,
				"bound":             bus.Bound,
				"naptanId":          bus.NaptanId,
				"expectedDeparture": bus.ExpectedDeparture,
			}
		}
	}
	insertExpectedDeparturesIntoDB(result)
}
/* 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)
			}
		}*/

}