/**
 * Utilities related to colors.
 */

package theorycrafter.utils

import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import kotlin.math.abs


/**
 * Returns a color with the same hue and saturation, but with its luminance adjusted by the given function.
 */
private fun Color.adjustLightness(adjuster: (Float) -> Float): Color {
    val argb = toArgb()
    val alpha = (argb shr 24) and 0xFF
    val hsl = FloatArray(3)
    rgbToHsl(argb, hsl)
    hsl[2] = adjuster(hsl[2]).coerceIn(0f, 1f)
    val rgb = hslToRgb(hsl)
    return Color((alpha shl 24) or rgb)
}


/**
 * Converts the given RGB values (each in range between 0 and 255) to HSL values.
 *
 * @param rgb The input RGB values, packed into an Int.
 * @param hsl The array to put the output HSL values in.
 */
fun rgbToHsl(rgb: Int, hsl: FloatArray) {
    val rNorm = ((rgb shr 16) and 0xFF) / 255f
    val gNorm = ((rgb shr 8) and 0xFF) / 255f
    val bNorm = (rgb and 0xFF) / 255f

    val cMax = maxOf(rNorm, gNorm, bNorm)
    val cMin = minOf(rNorm, gNorm, bNorm)
    val delta = cMax - cMin

    var hue = 0f
    var saturation = 0f
    val lightness = (cMax + cMin) / 2f

    if (delta != 0f) {
        saturation = if (lightness < 0.5f) delta / (cMax + cMin) else delta / (2f - cMax - cMin)

        when (cMax) {
            rNorm -> hue = ((gNorm - bNorm) / delta) % 6f
            gNorm -> hue = ((bNorm - rNorm) / delta) + 2f
            bNorm -> hue = ((rNorm - gNorm) / delta) + 4f
        }

        hue *= 60f
        if (hue < 0.0) hue += 360f
    }

    hsl[0] = hue
    hsl[1] = saturation
    hsl[2] = lightness
}


/**
 * Converts the given HSL values to RGB values.
 *
 * @param hsl The input HSL values.
 */
fun hslToRgb(hsl: FloatArray): Int {
    val (h, s, l) = hsl
    val c = (1f - abs(2f * l - 1f)) * s
    val x = c * (1f - abs(((h / 60f) % 2f) - 1f))
    val m = l - (c / 2f)

    val r: Float
    val g: Float
    val b: Float

    when {
        (h < 60.0) -> { r = c; g = x; b = 0f }
        (h < 120.0) -> { r = x; g = c; b = 0f }
        (h < 180.0) -> { r = 0f; g = c; b = x }
        (h < 240.0) -> { r = 0f; g = x; b = c }
        (h < 300.0) -> { r = x; g = 0f; b = c }
        else -> { r = c; g = 0f; b = x }
    }

    return (((r + m) * 255f).toInt() shl 16) or
            (((g + m) * 255f).toInt() shl 8) or
            ((b + m) * 255f).toInt()
}


/**
 * Returns a color darker than `this`.
 */
fun Color.darker(factor: Float = 0.1f) = adjustLightness { it * (1 - factor).coerceAtLeast(0f) }


/**
 * Returns a color lighter than `this`.
 */
fun Color.lighter(factor: Float = 0.1f) = adjustLightness { lightness ->
    1 - (1 - lightness) * (1 - factor).coerceAtLeast(0f)
}