package net.torvald.colourutil

import com.jme3.math.FastMath
import com.badlogic.gdx.graphics.Color
import net.torvald.colourutil.CIELabUtil.toXYZ
import net.torvald.colourutil.CIEXYZUtil.toColor
import net.torvald.colourutil.CIEXYZUtil.toRGB
import net.torvald.colourutil.CIEXYZUtil.toXYZ
import net.torvald.colourutil.CIELuvUtil.toLuv
import net.torvald.colourutil.CIELuvUtil.toXYZ

/**
 * A modification of CIEXYZ that is useful for additive mixtures of lights.
 *
 * reference: http://www.brucelindbloom.com/index.html?Equations.html
 *
 * If you're using Mac, you can play around with this colour space with
 * ColorSync Utility's calculator.
 *
 * Created by minjaesong on 2016-09-06.
 */
object CIELuvUtil {

    fun Color.brighterLuv(scale: Float): Color {
        val brighten = scale + 1f

        val luv = this.toLuv()
        luv.L *= brighten
        return luv.toColor()
    }

    fun Color.darkerLuv(scale: Float): Color {
        val darken = 1f - scale

        val luv = this.toLuv()
        luv.L *= darken
        return luv.toColor()
    }

    /** Tend to have more vivid (or saturated) colour */
    fun getGradient(scale: Float, fromCol: Color, toCol: Color): Color {
        val from = fromCol.toLuv()
        val to = toCol.toLuv()
        val newL = FastMath.interpolateLinear(scale, from.L, to.L)
        val newU = FastMath.interpolateLinear(scale, from.u, to.u)
        val newV = FastMath.interpolateLinear(scale, from.v, to.v)
        val newAlpha = FastMath.interpolateLinear(scale, from.alpha, to.alpha)

        return CIELab(newL, newU, newV, newAlpha).toColor()
    }

    /**
     * Alpha value will be overwritten to 1.0
     */
    infix fun Color.additiveLuv(other: Color): Color {
        val rgb = RGB(r, g, b) additiveLuv RGB(other.r, other.g, other.b)
        return Color(rgb.r, rgb.g, rgb.b, a)
    }

    /**
     * Alpha value will be overwritten to 1.0
     */
    infix fun RGB.additiveLuv(other: RGB): RGB {
        val thisLuv = this.toXYZ().toLuv()
        val otherLuv = other.toXYZ().toLuv()

        val newL = 1f - (1f - thisLuv.L) * (1 - otherLuv.L)
        val newU = thisLuv.u.times(otherLuv.L / newL) + otherLuv.u.times(otherLuv.L).times(1f - thisLuv.L).div(newL)
        val newV = thisLuv.v.times(otherLuv.L / newL) + otherLuv.v.times(otherLuv.L).times(1f - thisLuv.L).div(newL)

        return CIELuv(newL, newU, newV).toRGB()
    }

    fun CIEXYZ.toLuv(): CIELuv {
        val yRef = Y / D65.Y
        val uPrime = 4f * X / (X + 15f * Y + 3f * Z)
        val vPrime = 9f * Y / (X + 15f * Y + 3f * Z)
        val uRefPrime = 4f * D65.X / (D65.X + 15f * D65.Y + 3f * D65.Z)
        val vRefPrime = 9f * D65.Y / (D65.X + 15f * D65.Y + 3f * D65.Z)

        val L = if (yRef > epsilon)
            116f * yRef.cbrt() - 16f
        else
            kappa * yRef

        val u = 13f * L * (uPrime - uRefPrime)
        val v = 13f * L * (vPrime - vRefPrime)

        return CIELuv(L, u, v, alpha)
    }

    fun CIELuv.toXYZ(): CIEXYZ {
        val Y = if (L > kappa * epsilon)
            L.plus(16f).div(116f).cube()
        else
            L.div(kappa)
        val uRef = 4f * D65.X / (D65.X + 15f * D65.Y + 3f * D65.Z)
        val vRef = 9f * D65.Y / (D65.X + 15f * D65.Y + 3f * D65.Z)
        val a = (1f / 3f) * (52.times(L) / u.plus(13 * L * uRef)).minus(1f)
        val b = -5f * Y
        val c = -1f / 3f
        val d = Y * (39f.times(L) / v.plus(13f * L * vRef)).minus(5f)
        val X = (d - b) / (a - c)
        val Z = X * a + b

        return CIEXYZ(X, Y, Z, alpha)
    }

    private fun Float.cbrt() = FastMath.pow(this, 1f / 3f)
    private fun Float.cube() = this * this * this
}

fun Color.toLuv() = this.toXYZ().toLuv()
fun CIELuv.toRGB() = this.toXYZ().toRGB()
fun CIELuv.toColor() = this.toXYZ().toColor()

/**
 * Range:
 * L: 0-100.0
 * u, v: -100+ - 100+
 * (Hundred-based-plus)
 */
data class CIELuv(var L: Float = 0f, var u: Float = 0f, var v: Float = 0f, var alpha: Float = 1f)
