import { Color, Matrix3, Vector3 } from 'three'

import { clamp, saturate } from '@takram/three-geospatial'

const vectorScratch = /*#__PURE__*/ new Vector3()

// prettier-ignore
const XYZToLinearRGB = /*#__PURE__*/ new Matrix3(
  3.2404542, -1.5371385, -0.4985314,
  -0.9692660, 1.8760108, 0.0415560,
  0.0556434, -0.2040259, 1.0572252
)

export function convertTemperatureToLinearSRGBChromaticity(
  temperature: number,
  result = new Color()
): Color {
  // Convert temperature to black body chromaticity
  // See: https://google.github.io/filament/Filament.html#lighting/directlighting/lightsparameterization/colortemperature
  const T = temperature
  const T2 = T ** 2
  const u =
    (0.860117757 + 1.54118254e-4 * T + 1.28641212e-7 * T2) /
    (1 + 8.42420235e-4 * T + 7.08145163e-7 * T2)
  const v =
    (0.317398726 + 4.22806245e-5 * T + 4.20481691e-8 * T2) /
    (1 - 2.89741816e-5 * T + 1.61456053e-7 * T2)
  const x = (3 * u) / (2 * u - 8 * v + 4)
  const y = (2 * v) / (2 * u - 8 * v + 4)

  // Convert chromaticity to XYZ
  const Y = 1
  const X = y > 0 ? (x * Y) / y : 0
  const Z = y > 0 ? ((1 - x - y) * Y) / y : 0

  // Convert XYZ to linear sRGB chromaticity
  const color = vectorScratch.set(X, Y, Z).applyMatrix3(XYZToLinearRGB)
  // XYZ directly converted from spectral locus doesn't fall inside RGB.
  color.x = saturate(color.x)
  color.y = saturate(color.y)
  color.z = saturate(color.z)
  return result.setFromVector3(color.normalize())
}

// See: https://en.wikipedia.org/wiki/Color_index
function convertBVIndexToTemperature(bvIndex: number): number {
  const bv = clamp(bvIndex, -0.4, 2)
  return 4600 * (1 / (0.92 * bv + 1.7) + 1 / (0.92 * bvIndex + 0.62))
}

export function convertBVIndexToLinearSRGBChromaticity(
  bvIndex: number,
  result = new Color()
): Color {
  return convertTemperatureToLinearSRGBChromaticity(
    convertBVIndexToTemperature(bvIndex),
    result
  )
}
