package wayland

import (
	"math"
)

type GammaRamp struct {
	Red   []uint16
	Green []uint16
	Blue  []uint16
}

type rgb struct {
	r, g, b float64
}

type xyz struct {
	x, y, z float64
}

func illuminantD(temp int) (float64, float64, bool) {
	var x float64
	switch {
	case temp >= 2500 && temp <= 7000:
		t := float64(temp)
		x = 0.244063 + 0.09911e3/t + 2.9678e6/(t*t) - 4.6070e9/(t*t*t)
	case temp > 7000 && temp <= 25000:
		t := float64(temp)
		x = 0.237040 + 0.24748e3/t + 1.9018e6/(t*t) - 2.0064e9/(t*t*t)
	default:
		return 0, 0, false
	}
	y := -3*(x*x) + 2.870*x - 0.275
	return x, y, true
}

func planckianLocus(temp int) (float64, float64, bool) {
	var x, y float64
	switch {
	case temp >= 1667 && temp <= 4000:
		t := float64(temp)
		x = -0.2661239e9/(t*t*t) - 0.2343589e6/(t*t) + 0.8776956e3/t + 0.179910
		if temp <= 2222 {
			y = -1.1064814*(x*x*x) - 1.34811020*(x*x) + 2.18555832*x - 0.20219683
		} else {
			y = -0.9549476*(x*x*x) - 1.37418593*(x*x) + 2.09137015*x - 0.16748867
		}
	case temp > 4000 && temp < 25000:
		t := float64(temp)
		x = -3.0258469e9/(t*t*t) + 2.1070379e6/(t*t) + 0.2226347e3/t + 0.240390
		y = 3.0817580*(x*x*x) - 5.87338670*(x*x) + 3.75112997*x - 0.37001483
	default:
		return 0, 0, false
	}
	return x, y, true
}

func srgbGamma(value, gamma float64) float64 {
	if value <= 0.0031308 {
		return 12.92 * value
	}
	return math.Pow(1.055*value, 1.0/gamma) - 0.055
}

func clamp01(v float64) float64 {
	switch {
	case v > 1.0:
		return 1.0
	case v < 0.0:
		return 0.0
	default:
		return v
	}
}

func xyzToSRGB(c xyz) rgb {
	return rgb{
		r: srgbGamma(clamp01(3.2404542*c.x-1.5371385*c.y-0.4985314*c.z), 2.2),
		g: srgbGamma(clamp01(-0.9692660*c.x+1.8760108*c.y+0.0415560*c.z), 2.2),
		b: srgbGamma(clamp01(0.0556434*c.x-0.2040259*c.y+1.0572252*c.z), 2.2),
	}
}

func normalizeRGB(c *rgb) {
	maxw := math.Max(c.r, math.Max(c.g, c.b))
	if maxw > 0 {
		c.r /= maxw
		c.g /= maxw
		c.b /= maxw
	}
}

func calcWhitepoint(temp int) rgb {
	if temp == 6500 {
		return rgb{r: 1.0, g: 1.0, b: 1.0}
	}

	var wp xyz

	switch {
	case temp >= 25000:
		x, y, _ := illuminantD(25000)
		wp.x = x
		wp.y = y
	case temp >= 4000:
		x, y, _ := illuminantD(temp)
		wp.x = x
		wp.y = y
	case temp >= 2500:
		x1, y1, _ := illuminantD(temp)
		x2, y2, _ := planckianLocus(temp)
		factor := float64(4000-temp) / 1500.0
		sineFactor := (math.Cos(math.Pi*factor) + 1.0) / 2.0
		wp.x = x1*sineFactor + x2*(1.0-sineFactor)
		wp.y = y1*sineFactor + y2*(1.0-sineFactor)
	default:
		t := temp
		if t < 1667 {
			t = 1667
		}
		x, y, _ := planckianLocus(t)
		wp.x = x
		wp.y = y
	}

	wp.z = 1.0 - wp.x - wp.y

	wpRGB := xyzToSRGB(wp)
	normalizeRGB(&wpRGB)
	return wpRGB
}

func GenerateGammaRamp(size uint32, temp int, gamma float64) GammaRamp {
	ramp := GammaRamp{
		Red:   make([]uint16, size),
		Green: make([]uint16, size),
		Blue:  make([]uint16, size),
	}

	wp := calcWhitepoint(temp)

	for i := uint32(0); i < size; i++ {
		val := float64(i) / float64(size-1)
		ramp.Red[i] = uint16(clamp01(math.Pow(val*wp.r, 1.0/gamma)) * 65535.0)
		ramp.Green[i] = uint16(clamp01(math.Pow(val*wp.g, 1.0/gamma)) * 65535.0)
		ramp.Blue[i] = uint16(clamp01(math.Pow(val*wp.b, 1.0/gamma)) * 65535.0)
	}

	return ramp
}

func GenerateIdentityRamp(size uint32) GammaRamp {
	ramp := GammaRamp{
		Red:   make([]uint16, size),
		Green: make([]uint16, size),
		Blue:  make([]uint16, size),
	}

	for i := uint32(0); i < size; i++ {
		val := uint16((float64(i) / float64(size-1)) * 65535.0)
		ramp.Red[i] = val
		ramp.Green[i] = val
		ramp.Blue[i] = val
	}

	return ramp
}
