/* Copyright (C) 2024 José Rebelo
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see . */
package nodomain.freeyourgadget.gadgetbridge.devices.garmin;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.time.DayOfWeek;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAdjusters;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.GarminIntensityMinutesSample;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.PaiSample;
public class GarminPaiSampleProvider implements TimeSampleProvider {
private final GarminIntensityMinutesSampleProvider intensityMinutesSampleProvider;
public GarminPaiSampleProvider(final GBDevice device, final DaoSession session) {
this.intensityMinutesSampleProvider = new GarminIntensityMinutesSampleProvider(device, session);
}
@NonNull
@Override
public List getAllSamples(final long timestampFrom, final long timestampTo) {
// Intensity minutes reset every monday, so we need to go to the previous monday if we
// are not there yet
final ZoneId tz = ZoneId.systemDefault();
ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(Instant.ofEpochMilli(timestampFrom), tz);
final DayOfWeek dayOfWeek = zonedDateTime.getDayOfWeek();
if (dayOfWeek != DayOfWeek.MONDAY) {
zonedDateTime = zonedDateTime.with(TemporalAdjusters.previous(DayOfWeek.MONDAY));
}
zonedDateTime = zonedDateTime.truncatedTo(ChronoUnit.DAYS);
final List allSamples = intensityMinutesSampleProvider.getAllSamples(
zonedDateTime.toEpochSecond() * 1000L,
timestampTo
);
if (allSamples.isEmpty()) {
return Collections.emptyList();
}
int totalModerate = 0;
int totalVigorous = 0;
int currentDay = 0;
LocalDate lastSampleDate = LocalDate.ofInstant(Instant.ofEpochMilli(allSamples.get(0).getTimestamp()), tz);
final List ret = new ArrayList<>(allSamples.size());
for (final GarminIntensityMinutesSample sample : allSamples) {
final LocalDate sampleDate = LocalDate.ofInstant(Instant.ofEpochMilli(sample.getTimestamp()), tz);
// Since we only persist minute samples for days where there was activity, we need to fill any gaps
for (LocalDate d = lastSampleDate.plusDays(1); d.isBefore(sampleDate); d = d.plusDays(1)) {
if (lastSampleDate.getDayOfWeek() != d.getDayOfWeek()) {
currentDay = 0;
if (d.getDayOfWeek() == DayOfWeek.MONDAY) {
totalModerate = 0;
totalVigorous = 0;
}
}
ret.add(new GarminPaiSample(
d.atStartOfDay(tz).toInstant().toEpochMilli(),
totalModerate,
totalVigorous,
currentDay
));
lastSampleDate = d;
}
if (sampleDate.getDayOfWeek() != lastSampleDate.getDayOfWeek()) {
currentDay = 0;
if (sampleDate.getDayOfWeek() == DayOfWeek.MONDAY) {
totalModerate = 0;
totalVigorous = 0;
}
}
totalModerate += sample.getModerate();
totalVigorous += sample.getVigorous();
currentDay += sample.getModerate() + sample.getVigorous() * 2;
if (sample.getTimestamp() >= timestampFrom && sample.getTimestamp() <= timestampTo) {
ret.add(new GarminPaiSample(
sample.getTimestamp(),
totalModerate,
totalVigorous,
currentDay
));
}
lastSampleDate = sampleDate;
}
// Finally, fill out from the last sample to the end of the timestampTo
final LocalDate endDate = LocalDate.ofInstant(Instant.ofEpochMilli(timestampTo), tz);
for (LocalDate d = lastSampleDate.plusDays(1); !d.isAfter(endDate); d = d.plusDays(1)) {
if (lastSampleDate.getDayOfWeek() != d.getDayOfWeek()) {
currentDay = 0;
if (d.getDayOfWeek() == DayOfWeek.MONDAY) {
totalModerate = 0;
totalVigorous = 0;
}
}
ret.add(new GarminPaiSample(
d.atStartOfDay(tz).toInstant().toEpochMilli(),
totalModerate,
totalVigorous,
currentDay
));
lastSampleDate = d;
}
return ret;
}
@Override
public void addSample(final PaiSample timeSample) {
throw new UnsupportedOperationException("This sample provider is read-only!");
}
@Override
public void addSamples(final List timeSamples) {
throw new UnsupportedOperationException("This sample provider is read-only!");
}
@Override
public PaiSample createSample() {
throw new UnsupportedOperationException("This sample provider is read-only!");
}
@Nullable
@Override
public PaiSample getLatestSample() {
// TODO
return null;
}
@Nullable
@Override
public PaiSample getLatestSample(long until) {
// TODO
return null;
}
@Nullable
@Override
public PaiSample getFirstSample() {
// TODO
return null;
}
public static class GarminPaiSample implements PaiSample {
private final long timestamp;
private final int minutesModerate;
private final int minutesVigorous;
private final int today;
public GarminPaiSample(final long timestamp,
final int moderate,
final int vigorous,
final int today) {
this.timestamp = timestamp;
this.minutesModerate = moderate;
this.minutesVigorous = vigorous;
this.today = today;
}
@Override
public long getTimestamp() {
return timestamp;
}
@Override
public float getPaiLow() {
return 0;
}
@Override
public float getPaiModerate() {
return minutesModerate;
}
@Override
public float getPaiHigh() {
return minutesVigorous * 2;
}
@Override
public int getTimeLow() {
return 0; // not supported
}
@Override
public int getTimeModerate() {
return minutesModerate;
}
@Override
public int getTimeHigh() {
return minutesVigorous;
}
@Override
public float getPaiToday() {
return today;
}
@Override
public float getPaiTotal() {
return minutesModerate + 2 * minutesVigorous;
}
}
}