/* * TimeLimit Copyright 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 . */ 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): List { 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, rules: List, 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, relatedRules: List, 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, 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] } } }