///|
/// Texture system using enum dispatch.
/// Textures map surface coordinates (u, v) and hit points to colors.

pub(all) enum Texture {
  Solid(Vec3)
  Checker(Texture, Texture, Double)
  Noise(Double)
  Image(Array[Vec3], Int, Int)
} derive(Debug)

pub fn Texture::solid(color~ : Vec3) -> Texture {
  Solid(color)
}

pub fn Texture::checker(even~ : Texture, odd~ : Texture, scale~ : Double) -> Texture {
  Checker(even, odd, scale)
}

pub fn Texture::noise(scale~ : Double) -> Texture {
  Noise(scale)
}

pub fn Texture::value(self : Texture, u : Double, v : Double, p : Vec3) -> Vec3 {
  match self {
    Solid(color) => color
    Checker(even, odd, scale) => {
      let sines = @math.sin(scale * p.x) * @math.sin(scale * p.y) * @math.sin(scale * p.z)
      if sines < 0.0 {
        odd.value(u, v, p)
      } else {
        even.value(u, v, p)
      }
    }
    Noise(scale) => {
      let s = scale
      let i = ((s * p.x).floor() + (s * p.y).floor() + (s * p.z).floor()).to_int()
      let r = hash_int(i, 0)
      let g = hash_int(i, 1)
      let b = hash_int(i, 2)
      {
        x: r.to_double() / 255.0,
        y: g.to_double() / 255.0,
        z: b.to_double() / 255.0,
      }
    }
    Image(data, width, height) => {
      let i = (u * width.to_double()).to_int()
      let j = ((1.0 - v) * height.to_double()).to_int()
      let ui = if i < 0 { 0 } else if i >= width { width - 1 } else { i }
      let vj = if j < 0 { 0 } else if j >= height { height - 1 } else { j }
      data[vj * width + ui]
    }
  }
}

fn hash_int(n : Int, seed : Int) -> Int {
  let mut h = n * 374761393 + seed * 668265263
  h = (h ^ (h >> 13)) * 1274126177
  h = h ^ (h >> 16)
  ((h % 256) + 256) % 256
}