import { Vector2, Vector3 } from 'three'

import { radians } from '@takram/three-geospatial'

export class DensityProfileLayer {
  width: number
  expTerm: number
  expScale: number
  linearTerm: number
  constantTerm: number

  constructor(
    width = 0,
    expTerm = 0,
    expScale = 0,
    linearTerm = 0,
    constantTerm = 0
  ) {
    this.width = width
    this.expTerm = expTerm
    this.expScale = expScale
    this.linearTerm = linearTerm
    this.constantTerm = constantTerm
  }

  copy(other: DensityProfileLayer): this {
    this.width = other.width
    this.expTerm = other.expTerm
    this.expScale = other.expScale
    this.linearTerm = other.linearTerm
    this.constantTerm = other.constantTerm
    return this
  }

  clone(): DensityProfileLayer {
    return new DensityProfileLayer().copy(this)
  }
}

export class DensityProfile {
  layers: [DensityProfileLayer, DensityProfileLayer]

  constructor(layers: [DensityProfileLayer, DensityProfileLayer]) {
    this.layers = layers
  }

  copy(other: DensityProfile): this {
    this.layers = [other.layers[0].clone(), other.layers[1].clone()]
    return this
  }

  clone(): DensityProfile {
    return new DensityProfile([this.layers[0].clone(), this.layers[1].clone()])
  }
}

const luminanceCoefficients = /*#__PURE__*/ new Vector3(0.2126, 0.7152, 0.0722)

export class AtmosphereParameters {
  worldToUnit = 0.001

  // The solar irradiance at the top of the atmosphere.
  solarIrradiance = new Vector3(1.474, 1.8504, 1.91198)

  // The sun's angular radius.
  sunAngularRadius = 0.004675

  // The distance between the planet center and the bottom of the atmosphere.
  bottomRadius = 6360000

  // The distance between the planet center and the top of the atmosphere.
  topRadius = 6420000

  // The density profile of air molecules.
  rayleighDensity = new DensityProfile([
    new DensityProfileLayer(),
    new DensityProfileLayer(0, 1, -1 / 8000)
  ])

  // The scattering coefficient of air molecules at the altitude where their
  // density is maximum.
  rayleighScattering = new Vector3(0.000005802, 0.000013558, 0.0000331)

  // The density profile of aerosols.
  mieDensity = new DensityProfile([
    new DensityProfileLayer(),
    new DensityProfileLayer(0, 1, -1 / 1200)
  ])

  // The scattering coefficient of aerosols at the altitude where their density
  // is maximum.
  mieScattering = new Vector3().setScalar(0.000003996)

  // The extinction coefficient of aerosols at the altitude where their density
  // is maximum.
  mieExtinction = new Vector3().setScalar(0.00000444)

  // The anisotropy parameter for the Cornette-Shanks phase function.
  miePhaseFunctionG = 0.8

  // The density profile of air molecules that absorb light (e.g. ozone).
  absorptionDensity = new DensityProfile([
    new DensityProfileLayer(25000, 0, 0, 1 / 15000, -2 / 3),
    new DensityProfileLayer(0, 0, 0, -1 / 15000, 8 / 3)
  ])

  // The extinction coefficient of molecules that absorb light (e.g. ozone) at
  // the altitude where their density is maximum.
  absorptionExtinction = new Vector3(0.00000065, 0.000001881, 0.000000085)

  // The average albedo of the ground.
  // https://nssdc.gsfc.nasa.gov/planetary/factsheet/earthfact.html
  groundAlbedo = new Vector3().setScalar(0.3)

  // The cosine of the maximum sun zenith angle for which atmospheric scattering
  // must be precomputed (for maximum precision, use the smallest sun zenith
  // angle yielding negligible sky light radiance values).
  minCosLight = Math.cos(radians(120))

  sunRadianceToLuminance = new Vector3(98242.786222, 69954.398112, 66475.012354)
  skyRadianceToLuminance = new Vector3(114974.91644, 71305.954816, 65310.548555)
  luminanceScale = 1 / luminanceCoefficients.dot(this.sunRadianceToLuminance)

  // Whether to store the single Mie scattering in the alpha channel of the
  // scattering texture, reducing the memory footprint on the GPU.
  combinedScatteringTextures = true

  // Whether to generate and use a separate texture for higher-order scattering
  // (n >= 2) for a better approximation of the multi-scattering occlusion.
  higherOrderScatteringTexture = true

  // Texture sizes:
  transmittanceTextureSize = new Vector2(256, 64)
  irradianceTextureSize = new Vector2(64, 16)
  multipleScatteringTextureSize = new Vector2(64, 64)
  scatteringTextureRadiusSize = 32
  scatteringTextureCosViewSize = 128
  scatteringTextureCosLightSize = 32
  scatteringTextureCosViewLightSize = 8
  scatteringTextureSize = new Vector3()

  constructor() {
    this.update()
  }

  copy(other: AtmosphereParameters): this {
    this.worldToUnit = other.worldToUnit
    this.solarIrradiance.copy(other.solarIrradiance)
    this.sunAngularRadius = other.sunAngularRadius
    this.bottomRadius = other.bottomRadius
    this.topRadius = other.topRadius
    this.rayleighDensity.copy(other.rayleighDensity)
    this.rayleighScattering.copy(other.rayleighScattering)
    this.mieDensity.copy(other.mieDensity)
    this.mieScattering.copy(other.mieScattering)
    this.mieExtinction.copy(other.mieExtinction)
    this.miePhaseFunctionG = other.miePhaseFunctionG
    this.absorptionDensity.copy(other.absorptionDensity)
    this.absorptionExtinction.copy(other.absorptionExtinction)
    this.groundAlbedo.copy(other.groundAlbedo)
    this.minCosLight = other.minCosLight
    this.sunRadianceToLuminance.copy(other.sunRadianceToLuminance)
    this.skyRadianceToLuminance.copy(other.skyRadianceToLuminance)
    this.luminanceScale = other.luminanceScale
    this.combinedScatteringTextures = other.combinedScatteringTextures
    this.transmittanceTextureSize.copy(other.transmittanceTextureSize)
    this.irradianceTextureSize.copy(other.irradianceTextureSize)
    this.multipleScatteringTextureSize.copy(other.multipleScatteringTextureSize)
    this.scatteringTextureRadiusSize = other.scatteringTextureRadiusSize
    this.scatteringTextureCosViewSize = other.scatteringTextureCosViewSize
    this.scatteringTextureCosLightSize = other.scatteringTextureCosLightSize
    this.scatteringTextureCosViewLightSize =
      other.scatteringTextureCosViewLightSize
    this.scatteringTextureSize.copy(other.scatteringTextureSize)
    return this
  }

  update(): this {
    this.scatteringTextureSize.set(
      this.scatteringTextureCosViewLightSize *
        this.scatteringTextureCosLightSize,
      this.scatteringTextureCosViewSize,
      this.scatteringTextureRadiusSize
    )
    return this
  }

  clone(): AtmosphereParameters {
    return new AtmosphereParameters().copy(this)
  }

  /** @deprecated Use minCosLight */
  get minCosSun(): number {
    return this.minCosLight
  }

  /** @deprecated Use minCosLight */
  set minCosSun(value: number) {
    this.minCosLight = value
  }

  /** @deprecated Use scatteringTextureCosLightSize */
  get scatteringTextureCosSunSize(): number {
    return this.scatteringTextureCosLightSize
  }

  /** @deprecated Use scatteringTextureCosLightSize */
  set scatteringTextureCosSunSize(value: number) {
    this.scatteringTextureCosLightSize = value
  }

  /** @deprecated Use scatteringTextureCosViewLightSize */
  get scatteringTextureCosViewSunSize(): number {
    return this.scatteringTextureCosViewLightSize
  }

  /** @deprecated Use scatteringTextureCosViewLightSize */
  set scatteringTextureCosViewSunSize(value: number) {
    this.scatteringTextureCosViewLightSize = value
  }

  /** @deprecated This option was removed. */
  // eslint-disable-next-line @typescript-eslint/class-methods-use-this
  get transmittancePrecisionLog(): boolean {
    return false
  }

  /** @deprecated This option was removed. */
  // eslint-disable-next-line @typescript-eslint/class-methods-use-this
  set transmittancePrecisionLog(value: boolean) {}
}
