package com.vgmlr.kiln
import java.time.LocalDate
import java.time.temporal.ChronoUnit
import kotlin.math.abs
object KilnMoodInfluence {
private const val MIN_MOOD_DAYS = 20
private const val RAMP_DAYS = 28
private const val MAX_SHIFT_DEG = 30f
private const val SMOOTH = 2L
fun weight(records: List<KilnDayRecord>, buttons: KilnButtonSet): Float {
val n = records.count { it.mood != null || buttons.hasSymptom(it) }
return ((n - MIN_MOOD_DAYS).toFloat() / RAMP_DAYS).coerceIn(0f, 1f)
}
private val SENTIMENT = floatArrayOf(-1f, -0.4f, 1f, -0.4f, -1f)
private fun sentiment(mood: Int): Float = SENTIMENT.getOrElse(mood) { 0f }
private const val SYMPTOM_SENTIMENT = -0.4f
fun hueShift(
records: List<KilnDayRecord>,
buttons: KilnButtonSet,
starts: List<LocalDate>,
cycle: Int,
date: LocalDate
): Float {
val w = weight(records, buttons)
if (w == 0f || starts.isEmpty()) return 0f
val phase = phaseOf(date, starts, cycle) ?: return 0f
var sum = 0f
var n = 0
for (r in records) {
val p = phaseOf(LocalDate.ofEpochDay(r.epochDay), starts, cycle) ?: continue
if (circularDist(p, phase, cycle) > SMOOTH) continue
r.mood?.let { sum += sentiment(it); n++ }
val s = buttons.symptomCount(r)
if (s > 0) {
sum += SYMPTOM_SENTIMENT * s
n += s
}
}
if (n == 0) return 0f
val avg = sum / n
val confidence = n / (n + 3f)
return avg * MAX_SHIFT_DEG * w * confidence
}
private fun phaseOf(date: LocalDate, starts: List<LocalDate>, cycle: Int): Long? {
val last = starts.lastOrNull { !it.isAfter(date) } ?: return null
return Math.floorMod(ChronoUnit.DAYS.between(last, date), cycle.toLong())
}
private fun circularDist(a: Long, b: Long, cycle: Int): Long {
val d = abs(a - b)
return minOf(d, cycle - d)
}
}