timelimit-android/app/src/main/java/io/timelimit/android/logic/RemainingTime.kt
2024-01-01 01:00:00 +01:00

126 lines
No EOL
5.5 KiB
Kotlin

/*
* TimeLimit Copyright <C> 2019- 2023 Jonas Lochmann
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation version 3 of the License.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package io.timelimit.android.logic
import io.timelimit.android.data.model.TimeLimitRule
import io.timelimit.android.data.model.UsedTimeItem
data class RemainingTime(val includingExtraTime: Long, val default: Long) {
val hasRemainingTime = includingExtraTime > 0
val usingExtraTime = includingExtraTime > 0 && default == 0L
init {
if (includingExtraTime < 0 || default < 0) {
throw IllegalStateException("time is < 0")
}
if (includingExtraTime < default) {
throw IllegalStateException("extra time < default time")
}
}
companion object {
fun min(a: RemainingTime?, b: RemainingTime?): RemainingTime? = if (a == null) {
b
} else if (b == null) {
a
} else {
RemainingTime(
includingExtraTime = Math.min(a.includingExtraTime, b.includingExtraTime),
default = Math.min(a.default, b.default)
)
}
fun getRulesRelatedToDay(dayOfWeek: Int, minuteOfDay: Int, rules: List<TimeLimitRule>): List<TimeLimitRule> {
return rules.filter {
((it.dayMask.toInt() and (1 shl dayOfWeek)) != 0) &&
minuteOfDay >= it.startMinuteOfDay && minuteOfDay <= it.endMinuteOfDay
}
}
fun getRemainingTime(dayOfWeek: Int, minuteOfDay: Int, usedTimes: List<UsedTimeItem>, rules: List<TimeLimitRule>, extraTime: Long, firstDayOfWeekAsEpochDay: Int): RemainingTime? {
if (extraTime < 0) {
throw IllegalStateException("extra time < 0")
}
val relatedRules = getRulesRelatedToDay(dayOfWeek, minuteOfDay, rules)
val withoutExtraTime = getRemainingTime(usedTimes, relatedRules, false, firstDayOfWeekAsEpochDay, dayOfWeek)
val withExtraTime = getRemainingTime(usedTimes, relatedRules, true, firstDayOfWeekAsEpochDay, dayOfWeek)
if (withoutExtraTime == null && withExtraTime == null) {
// no rules
return null
} else if (withoutExtraTime != null && withExtraTime != null) {
// with rules for extra time
val additionalTimeWithExtraTime = withExtraTime - withoutExtraTime
if (additionalTimeWithExtraTime < 0) {
throw IllegalStateException("additional time with extra time < 0")
}
return RemainingTime(
includingExtraTime = withoutExtraTime + Math.min(extraTime, additionalTimeWithExtraTime),
default = withoutExtraTime
)
} else if (withoutExtraTime != null) {
// without rules for extra time
return RemainingTime(
includingExtraTime = withoutExtraTime + extraTime,
default = withoutExtraTime
)
} else {
throw IllegalStateException()
}
}
private fun getRemainingTime(usedTimes: List<UsedTimeItem>, relatedRules: List<TimeLimitRule>, assumeMaximalExtraTime: Boolean, firstDayOfWeekAsEpochDay: Int, dayOfWeek: Int): Long? {
return relatedRules.filter { (!assumeMaximalExtraTime) || it.applyToExtraTimeUsage }.map { rule ->
val usedTime = getUsedTime(
usedTimes = usedTimes,
rule = rule,
firstDayOfWeekAsEpochDay = firstDayOfWeekAsEpochDay,
dayOfWeekForDailyRule = if (rule.perDay) dayOfWeek else null
)
val maxTime = rule.maximumTimeInMillis
(maxTime - usedTime).coerceAtLeast(0)
}.minOrNull()
}
fun getUsedTime(usedTimes: List<UsedTimeItem>, rule: TimeLimitRule, firstDayOfWeekAsEpochDay: Int, dayOfWeekForDailyRule: Int?): Long {
val usedTimeByDay = longArrayOf(0, 0, 0, 0, 0, 0, 0)
usedTimes.forEach { usedTimeItem ->
val doesWeekMatch = usedTimeItem.dayOfEpoch >= firstDayOfWeekAsEpochDay && usedTimeItem.dayOfEpoch <= firstDayOfWeekAsEpochDay + 6
if (doesWeekMatch) {
val usedTimeItemDayOfWeek = usedTimeItem.dayOfEpoch - firstDayOfWeekAsEpochDay
val doesDayMaskMatch = (rule.dayMask.toInt() and (1 shl usedTimeItemDayOfWeek)) != 0
val dayMatch = rule.perDay or doesDayMaskMatch
val hourMatch = rule.startMinuteOfDay <= usedTimeItem.startTimeOfDay && rule.endMinuteOfDay >= usedTimeItem.endTimeOfDay
if (dayMatch && hourMatch)
usedTimeByDay[usedTimeItemDayOfWeek] = usedTimeByDay[usedTimeItemDayOfWeek].coerceAtLeast(usedTimeItem.usedMillis)
}
}
return if (dayOfWeekForDailyRule == null) usedTimeByDay.sum()
else usedTimeByDay[dayOfWeekForDailyRule]
}
}
}