func StoreDaysOfInjections(context context.Context, userProfileKey *datastore.Key, daysOfInjections []apimodel.DayOfInjections) (keys []*datastore.Key, err error) {
	elementKeys := make([]*datastore.Key, len(daysOfInjections))
	for i := range daysOfInjections {
		elementKeys[i] = datastore.NewKey(context, "DayOfInjections", "", daysOfInjections[i].StartTime.Unix(), userProfileKey)
	}
	daysOfInjections, err = reconcileDayOfInjectionsWithExisting(context, elementKeys, daysOfInjections)
	if err != nil {
		return nil, err
	}
	log.Infof(context, "Emitting a PutMulti with %d keys for all %d days of meals", len(elementKeys), len(daysOfInjections))
	keys, error := datastore.PutMulti(context, elementKeys, daysOfInjections)
	if error != nil {
		log.Criticalf(context, "Error writing %d days of meals with keys [%s]: %v", len(elementKeys), elementKeys, error)
		return nil, error
	}
	return elementKeys, nil
}
StoreDaysOfInjections stores a batch of DayOfInjections elements. It is a optimized operation in that:
1. One element represents a relatively short-and-wide entry of all meals for a single day.
2. We have multiple DayOfInjections elements and we use a PutMulti to make this faster.
For details of how a single element of DayOfInjections is physically stored, see the implementation of apimodel.Store and apimodel.Load.
package store
import (
	"github.com/alexandre-normand/glukit/app/apimodel"
	"github.com/alexandre-normand/glukit/app/container"
	"github.com/alexandre-normand/glukit/app/model"
	"github.com/alexandre-normand/glukit/app/util"
	"context"
	"google.golang.org/appengine"
	"google.golang.org/appengine/datastore"
	"google.golang.org/appengine/log"
	"math"
	"sort"
	"time"
)
const (
	GLUKIT_SCORE_PUT_MULTI_SIZE = 10
)
type StoreError struct {
	msg       string
	Temporary bool  
}
func (e StoreError) Error() string {
	return e.msg
}
type ScoreScanQuery struct {
	Limit *int
	From  *time.Time
	To    *time.Time
}
var (
	ErrNoImportedDataFound = StoreError{"store: no imported data found", true}
	ErrNoSteadySailorMatchFound = StoreError{"store: no match for a steady sailor found", true}
)
func GetUserKey(context context.Context, email string) (key *datastore.Key) {
	return datastore.NewKey(context, "GlukitUser", email, 0, nil)
}
func StoreUserProfile(context context.Context, updatedAt time.Time, userProfile model.GlukitUser) (key *datastore.Key, err error) {
	key, error := datastore.Put(context, GetUserKey(context, userProfile.Email), &userProfile)
	if error != nil {
		util.Propagate(error)
	}
	return key, nil
}
func GetUserProfile(context context.Context, key *datastore.Key) (userProfile *model.GlukitUser, err error) {
	userProfile = new(model.GlukitUser)
	log.Infof(context, "Fetching user profile for key: %s", key.String())
	err = datastore.Get(context, key, userProfile)
	if err != nil {
		if unknownFields, ok := err.(*datastore.ErrFieldMismatch); ok {
			log.Infof(context, "Ignoring unknown fields [%s]", unknownFields.Error())
		} else {
			return nil, err
		}
	}
	return userProfile, nil
}
func GetGlucoseReads(context context.Context, email string, lowerBound time.Time, upperBound time.Time) (reads []apimodel.GlucoseRead, err error) {
	key := GetUserKey(context, email)
	scanStart := lowerBound.Add(time.Duration(-24 * time.Hour))
	scanEnd := upperBound.Add(time.Duration(24 * time.Hour))
	log.Infof(context, "Scanning for reads between %s and %s to get reads between %s and %s", scanStart, scanEnd, lowerBound, upperBound)
	query := datastore.NewQuery("DayOfReads").Ancestor(key).Filter("startTime >=", scanStart).Filter("startTime <=", scanEnd).Order("startTime")
	daysOfReads := new(apimodel.DayOfGlucoseReads)
	readsForPeriod := make([]apimodel.GlucoseRead, 0)
	iterator := query.Run(context)
	for _, err := iterator.Next(daysOfReads); err == nil; _, err = iterator.Next(daysOfReads) {
		log.Debugf(context, "Loaded batch of %d reads...", len(daysOfReads.Reads))
		readsForPeriod = mergeGlucoseReadArrays(readsForPeriod, daysOfReads.Reads)
		daysOfReads = new(apimodel.DayOfGlucoseReads)
	}
	readSlice := apimodel.GlucoseReadSlice(readsForPeriod)
	startIndex, endIndex := apimodel.GetBoundariesOfElementsInRange(readSlice, lowerBound, upperBound)
	filteredReads := readsForPeriod[startIndex : endIndex+1]
	if err != datastore.Done {
		util.Propagate(err)
	}
	return filteredReads, nil
}
func StoreDaysOfReads(context context.Context, userProfileKey *datastore.Key, daysOfReads []apimodel.DayOfGlucoseReads) (keys []*datastore.Key, err error) {
	elementKeys := make([]*datastore.Key, len(daysOfReads))
	for i := range daysOfReads {
		log.Debugf(context, "Storing day of reads with [%d] reads and key [%d]", len(daysOfReads[i].Reads), daysOfReads[i].StartTime.Unix())
		elementKeys[i] = datastore.NewKey(context, "DayOfReads", "", daysOfReads[i].StartTime.Unix(), userProfileKey)
	}
	daysOfReads, err = reconcileDayOfReadsWithExisting(context, elementKeys, daysOfReads)
	if err != nil {
		return nil, err
	}
	log.Infof(context, "Emitting a PutMulti with %d keys for all %d days of reads", len(elementKeys), len(daysOfReads))
	keys, error := datastore.PutMulti(context, elementKeys, daysOfReads)
	if error != nil {
		log.Warningf(context, "Error writing %d days of reads with keys [%s]: %v", len(elementKeys), elementKeys, error)
		return nil, error
	}
	userProfile, err := GetGlukitUserWithKey(context, userProfileKey)
	if err != nil {
		log.Criticalf(context, "Error reading user profile [%s] for its most recent read value: %v", userProfileKey, err)
		return nil, err
	}
	lastDayOfRead := daysOfReads[len(daysOfReads)-1]
	lastRead := lastDayOfRead.Reads[len(lastDayOfRead.Reads)-1]
	if userProfile.MostRecentRead.GetTime().Before(lastRead.GetTime()) {
		log.Infof(context, "Updating most recent read date to %s", lastRead.GetTime())
		userProfile.MostRecentRead = lastRead
		_, err := StoreUserProfile(context, time.Now(), *userProfile)
		if err != nil {
			log.Criticalf(context, "Error storing updated user profile [%s] with most recent read value of %s: %v", userProfileKey, userProfile.MostRecentRead, err)
			return nil, err
		}
	}
	return elementKeys, nil
}
func reconcileDayOfReadsWithExisting(context context.Context, elementKeys []*datastore.Key, freshData []apimodel.DayOfGlucoseReads) (reconciledData []apimodel.DayOfGlucoseReads, err error) {
	reconciledData = make([]apimodel.DayOfGlucoseReads, len(freshData))
	existingData := make([]apimodel.DayOfGlucoseReads, len(elementKeys))
	err = datastore.GetMulti(context, elementKeys, existingData)
	if multierr, ok := err.(appengine.MultiError); !ok && err != nil {
		log.Warningf(context, "Got error: %v", err)
		return nil, err
	} else {
		if err == nil {
			for i := range existingData {
				log.Debugf(context, "Merging old ([%d]) with new ([%d]) at index [%d]", len(existingData[i].Reads), len(freshData[i].Reads), i)
				reconciledReads := reconcileReads(existingData[i].Reads, freshData[i].Reads)
				log.Debugf(context, "Merged reads ([%d]) is [%v]", len(reconciledReads), reconciledReads)
				reconciledData[i] = apimodel.DayOfGlucoseReads{reconciledReads, existingData[i].StartTime, freshData[i].EndTime}
			}
		}
		for i, elementErr := range multierr {
			if elementErr == datastore.ErrNoSuchEntity {
				log.Debugf(context, "Keeping day of reads for key [%s] as-is since we have no pre-existing data for it.", elementKeys[i].String())
				reconciledData[i] = freshData[i]
			} else {
				log.Debugf(context, "Merging old ([%d]) with new ([%d]) at index [%d]", len(existingData[i].Reads), len(freshData[i].Reads), i)
				reconciledReads := reconcileReads(existingData[i].Reads, freshData[i].Reads)
				log.Debugf(context, "Merged reads ([%d]) is [%v]", len(reconciledReads), reconciledReads)
				reconciledData[i] = apimodel.DayOfGlucoseReads{reconciledReads, existingData[i].StartTime, freshData[i].EndTime}
			}
		}
	}
	return reconciledData, nil
}
func reconcileReads(older, recent []apimodel.GlucoseRead) (reconciledReads []apimodel.GlucoseRead) {
	allKeys := make([]int64, 0)
	values := make(map[int64]apimodel.GlucoseRead)
	for i := range older {
		timestamp := older[i].Time.Timestamp
		allKeys = append(allKeys, timestamp)
		values[timestamp] = older[i]
	}
	for i := range recent {
		timestamp := recent[i].Time.Timestamp
		if _, exists := values[timestamp]; !exists {
			allKeys = append(allKeys, timestamp)
		}
		values[timestamp] = recent[i]
	}
	sort.Sort(container.Int64Slice(allKeys))
	reconciledReads = make([]apimodel.GlucoseRead, len(allKeys))
	for i := range allKeys {
		reconciledReads[i] = values[allKeys[i]]
	}
	return reconciledReads
}
func GetCalibrations(context context.Context, email string, lowerBound time.Time, upperBound time.Time) (meals []apimodel.CalibrationRead, err error) {
	key := GetUserKey(context, email)
	scanStart := lowerBound.Add(time.Duration(-24 * time.Hour))
	scanEnd := upperBound.Add(time.Duration(24 * time.Hour))
	log.Infof(context, "Scanning for calibrations between %s and %s to get calibrations between %s and %s", scanStart, scanEnd, lowerBound, upperBound)
	query := datastore.NewQuery("DayOfCalibrationReads").Ancestor(key).Filter("startTime >=", scanStart).Filter("startTime <=", scanEnd).Order("startTime")
	daysOfCalibration := new(apimodel.DayOfCalibrationReads)
	calibrationsForPeriod := make([]apimodel.CalibrationRead, 0)
	iterator := query.Run(context)
	for _, err := iterator.Next(daysOfCalibration); err == nil; _, err = iterator.Next(daysOfCalibration) {
		log.Debugf(context, "Loaded batch of %d calibrations...", len(daysOfCalibration.Reads))
		calibrationsForPeriod = mergeCalibrationReadArrays(calibrationsForPeriod, daysOfCalibration.Reads)
		daysOfCalibration = new(apimodel.DayOfCalibrationReads)
	}
	calibrationSlice := apimodel.CalibrationReadSlice(calibrationsForPeriod)
	startIndex, endIndex := apimodel.GetBoundariesOfElementsInRange(calibrationSlice, lowerBound, upperBound)
	filteredCalibrations := calibrationsForPeriod[startIndex : endIndex+1]
	if err != datastore.Done {
		util.Propagate(err)
	}
	return filteredCalibrations, nil
}
func StoreCalibrationReads(context context.Context, userProfileKey *datastore.Key, daysOfCalibrationReads []apimodel.DayOfCalibrationReads) (keys []*datastore.Key, err error) {
	elementKeys := make([]*datastore.Key, len(daysOfCalibrationReads))
	for i := range daysOfCalibrationReads {
		log.Debugf(context, "Storing day of calibration reads with [%d] reads and key [%d]", len(daysOfCalibrationReads[i].Reads), daysOfCalibrationReads[i].StartTime.Unix())
		elementKeys[i] = datastore.NewKey(context, "DayOfCalibrationReads", "", daysOfCalibrationReads[i].StartTime.Unix(), userProfileKey)
	}
	daysOfCalibrationReads, err = reconcileDayOfCalibrationsWithExisting(context, elementKeys, daysOfCalibrationReads)
	if err != nil {
		return nil, err
	}
	log.Infof(context, "Emitting a PutMulti with %d keys for all %d days of calibration reads", len(elementKeys), len(daysOfCalibrationReads))
	keys, error := datastore.PutMulti(context, elementKeys, daysOfCalibrationReads)
	if error != nil {
		log.Criticalf(context, "Error writing %d days of calibration reads with keys [%s]: %v", len(elementKeys), elementKeys, error)
		return nil, error
	}
	return elementKeys, nil
}
func reconcileDayOfCalibrationsWithExisting(context context.Context, elementKeys []*datastore.Key, freshData []apimodel.DayOfCalibrationReads) (reconciledData []apimodel.DayOfCalibrationReads, err error) {
	reconciledData = make([]apimodel.DayOfCalibrationReads, len(freshData))
	existingData := make([]apimodel.DayOfCalibrationReads, len(elementKeys))
	err = datastore.GetMulti(context, elementKeys, existingData)
	if multierr, ok := err.(appengine.MultiError); !ok && err != nil {
		log.Warningf(context, "Got error: %v", err)
		return nil, err
	} else {
		if err == nil {
			for i := range existingData {
				log.Debugf(context, "Merging old ([%d]) with new ([%d]) at index [%d]", len(existingData[i].Reads), len(freshData[i].Reads), i)
				reconciledReads := reconcileCalibrations(existingData[i].Reads, freshData[i].Reads)
				log.Debugf(context, "Merged calibrations ([%d]) is [%v]", len(reconciledReads), reconciledReads)
				reconciledData[i] = apimodel.DayOfCalibrationReads{reconciledReads, existingData[i].StartTime, freshData[i].EndTime}
			}
		}
		for i, elementErr := range multierr {
			if elementErr == datastore.ErrNoSuchEntity {
				log.Debugf(context, "Keeping day of calibrations for key [%s] as-is since we have no pre-existing data for it.", elementKeys[i].String())
				reconciledData[i] = freshData[i]
			} else {
				log.Debugf(context, "Merging old ([%d]) with new ([%d]) at index [%d]", len(existingData[i].Reads), len(freshData[i].Reads), i)
				reconciledReads := reconcileCalibrations(existingData[i].Reads, freshData[i].Reads)
				log.Debugf(context, "Merged calibrations ([%d]) is [%v]", len(reconciledReads), reconciledReads)
				reconciledData[i] = apimodel.DayOfCalibrationReads{reconciledReads, existingData[i].StartTime, freshData[i].EndTime}
			}
		}
	}
	return reconciledData, nil
}
func reconcileCalibrations(older, recent []apimodel.CalibrationRead) (reconciledReads []apimodel.CalibrationRead) {
	allKeys := make([]int64, 0)
	values := make(map[int64]apimodel.CalibrationRead)
	for i := range older {
		timestamp := older[i].Time.Timestamp
		allKeys = append(allKeys, timestamp)
		values[timestamp] = older[i]
	}
	for i := range recent {
		timestamp := recent[i].Time.Timestamp
		if _, exists := values[timestamp]; !exists {
			allKeys = append(allKeys, timestamp)
		}
		values[timestamp] = recent[i]
	}
	sort.Sort(container.Int64Slice(allKeys))
	reconciledReads = make([]apimodel.CalibrationRead, len(allKeys))
	for i := range allKeys {
		reconciledReads[i] = values[allKeys[i]]
	}
	return reconciledReads
}
func GetInjections(context context.Context, email string, lowerBound time.Time, upperBound time.Time) (meals []apimodel.Injection, err error) {
	key := GetUserKey(context, email)
	scanStart := lowerBound.Add(time.Duration(-24 * time.Hour))
	scanEnd := upperBound.Add(time.Duration(24 * time.Hour))
	log.Infof(context, "Scanning for meals between %s and %s to get meals between %s and %s", scanStart, scanEnd, lowerBound, upperBound)
	query := datastore.NewQuery("DayOfInjections").Ancestor(key).Filter("startTime >=", scanStart).Filter("startTime <=", scanEnd).Order("startTime")
	daysOfInjections := new(apimodel.DayOfInjections)
	mealsForPeriod := make([]apimodel.Injection, 0)
	iterator := query.Run(context)
	for _, err := iterator.Next(daysOfInjections); err == nil; _, err = iterator.Next(daysOfInjections) {
		log.Debugf(context, "Loaded batch of %d meals...", len(daysOfInjections.Injections))
		mealsForPeriod = mergeInjectionArrays(mealsForPeriod, daysOfInjections.Injections)
		daysOfInjections = new(apimodel.DayOfInjections)
	}
	mealSlice := apimodel.InjectionSlice(mealsForPeriod)
	startIndex, endIndex := apimodel.GetBoundariesOfElementsInRange(mealSlice, lowerBound, upperBound)
	filteredInjections := mealsForPeriod[startIndex : endIndex+1]
	if err != datastore.Done {
		util.Propagate(err)
	}
	return filteredInjections, nil
}