package ocelot.desktop.color

import ocelot.desktop.util.NumberUtils.ExtendedFloat
import ocelot.desktop.geometry.Vector3D

import java.nio.ByteBuffer

case class RGBAColorNorm(r: Float, g: Float, b: Float, a: Float = 1f) extends Color {
  require(0 <= r && r <= 1.0f, "Invalid RED channel")
  require(0 <= g && g <= 1.0f, "Invalid GREEN channel")
  require(0 <= b && b <= 1.0f, "Invalid BLUE channel")
  require(0 <= a && a <= 1.0f, "Invalid ALPHA channel")

  def components: Array[Float] = Array(r, g, b, a)

  def rgbVector: Vector3D = Vector3D(r, g, b)

  def mapRgb(f: Float => Float): RGBAColorNorm = copy(r = f(r), g = f(g), b = f(b))

  def mapA(f: Float => Float): RGBAColorNorm = copy(a = f(a))

  private final def componentToLinear(x: Float): Float = {
    if (x <= 0.0404482362771082)
      x / 12.92f
    else
      math.pow((x + 0.055) / 1.055, 2.4).toFloat
  }

  def toLinear: RGBAColorNorm = {
    RGBAColorNorm(componentToLinear(r), componentToLinear(g), componentToLinear(b), a)
  }

  override def toInt: IntColor = toRGBA.toInt

  override def toRGBA: RGBAColor = RGBAColor((r * 255).toShort, (g * 255).toShort, (b * 255).toShort, (a * 255).toShort)

  override def toRGBANorm: RGBAColorNorm = this

  override def toHSVA: HSVAColor = {
    val max = r.max(g).max(b)
    val min = r.min(g).min(b)

    var hue = if (max == min)
      0
    else if (max == r)
      60 * (0 + (g - b) / (max - min))
    else if (max == g)
      60 * (2 + (b - r) / (max - min))
    else
      60 * (4 + (r - g) / (max - min))

    if (hue < 0) hue += 360

    val saturation = if (max == min)
      0
    else
      (max - min) / max

    val value = max

    HSVAColor(hue, saturation, value, a)
  }

  def lerp(dst: RGBAColorNorm, t: Float): RGBAColorNorm = {
    RGBAColorNorm(
      r.lerp(dst.r, t),
      g.lerp(dst.g, t),
      b.lerp(dst.b, t),
      a.lerp(dst.a, t),
    )
  }

  def withAlpha(alpha: Float): RGBAColorNorm = RGBAColorNorm(r, g, b, alpha)

  // ʕ•ᴥ•ʔ
  def put(buffer: ByteBuffer): Unit = {
    buffer.putFloat(r)
    buffer.putFloat(g)
    buffer.putFloat(b)
    buffer.putFloat(a)
  }

  def putLinear(buffer: ByteBuffer): Unit = {
    buffer.putFloat(componentToLinear(r))
    buffer.putFloat(componentToLinear(g))
    buffer.putFloat(componentToLinear(b))
    buffer.putFloat(componentToLinear(a))
  }
}
