///|
/// Portable shader recipes that can be planned before native Skia handles are
/// created.
pub(all) enum ShaderDescriptor {
  ColorShader(Color)
  LinearGradientShader(Point, Point, Color, Color, TileMode)
  RadialGradientShader(Point, Float, Color, Color, TileMode)
} derive(Debug, Eq)

///|
fn shader_filter_scalar_is_finite(value : Float) -> Bool {
  value == value && value - value == 0.0
}

///|
fn shader_filter_point_is_finite(point : Point) -> Bool {
  shader_filter_scalar_is_finite(point.x) &&
  shader_filter_scalar_is_finite(point.y)
}

///|
fn shader_filter_points_are_distinct(start : Point, end : Point) -> Bool {
  start.distance_to_squared(end) > 0.0
}

///|
fn shader_filter_resource_u64(value : Int) -> Bytes {
  Int::max(0, value).to_uint64().to_be_bytes()
}

///|
fn shader_filter_resource_bytes(value : Bytes) -> Bytes {
  value.length().to_uint64().to_be_bytes() + value
}

///|
fn shader_filter_color_id(color : Color) -> Bytes {
  color.argb().to_uint64().to_be_bytes()
}

///|
fn shader_filter_point_id(point : Point) -> Bytes {
  point.x.to_be_bytes() + point.y.to_be_bytes()
}

///|
fn TileMode::shader_filter_id(self : TileMode) -> Bytes {
  shader_filter_resource_u64(self.to_skia_ordinal())
}

///|
fn shader_filter_float_id(value : Float) -> Bytes {
  value.to_be_bytes()
}

///|
fn shader_filter_float_array_id(values : Array[Float]) -> Bytes {
  let mut id = shader_filter_resource_u64(values.length())
  for value in values {
    id = id + shader_filter_float_id(value)
  }
  id
}

///|
fn shader_filter_float_array_is_finite(values : Array[Float]) -> Bool {
  let mut finite = true
  for value in values {
    if !shader_filter_scalar_is_finite(value) {
      finite = false
    }
  }
  finite
}

///|
pub fn ShaderDescriptor::color(color : Color) -> ShaderDescriptor {
  ColorShader(color)
}

///|
pub fn ShaderDescriptor::linear_gradient(
  start : Point,
  end : Point,
  start_color : Color,
  end_color : Color,
  tile_mode? : TileMode = Clamp,
) -> ShaderDescriptor? {
  if shader_filter_point_is_finite(start) &&
    shader_filter_point_is_finite(end) &&
    shader_filter_points_are_distinct(start, end) {
    Some(LinearGradientShader(start, end, start_color, end_color, tile_mode))
  } else {
    None
  }
}

///|
pub fn ShaderDescriptor::radial_gradient(
  center : Point,
  radius : Float,
  inner_color : Color,
  outer_color : Color,
  tile_mode? : TileMode = Clamp,
) -> ShaderDescriptor? {
  if shader_filter_point_is_finite(center) &&
    shader_filter_scalar_is_finite(radius) &&
    radius > 0.0 {
    Some(
      RadialGradientShader(center, radius, inner_color, outer_color, tile_mode),
    )
  } else {
    None
  }
}

///|
pub fn ShaderDescriptor::is_valid(self : ShaderDescriptor) -> Bool {
  match self {
    ColorShader(_) => true
    LinearGradientShader(start, end, _, _, _) =>
      shader_filter_point_is_finite(start) &&
      shader_filter_point_is_finite(end) &&
      shader_filter_points_are_distinct(start, end)
    RadialGradientShader(center, radius, _, _, _) =>
      shader_filter_point_is_finite(center) &&
      shader_filter_scalar_is_finite(radius) &&
      radius > 0.0
  }
}

///|
fn ShaderDescriptor::resource_id(self : ShaderDescriptor) -> Bytes {
  if !self.is_valid() {
    b""
  } else {
    match self {
      ColorShader(color) =>
        b"moui_skia:shader:v1" +
        shader_filter_resource_bytes(b"color") +
        shader_filter_color_id(color)
      LinearGradientShader(start, end, start_color, end_color, tile_mode) =>
        b"moui_skia:shader:v1" +
        shader_filter_resource_bytes(b"linear-gradient") +
        shader_filter_point_id(start) +
        shader_filter_point_id(end) +
        shader_filter_color_id(start_color) +
        shader_filter_color_id(end_color) +
        tile_mode.shader_filter_id()
      RadialGradientShader(center, radius, inner_color, outer_color, tile_mode) =>
        b"moui_skia:shader:v1" +
        shader_filter_resource_bytes(b"radial-gradient") +
        shader_filter_point_id(center) +
        shader_filter_float_id(radius) +
        shader_filter_color_id(inner_color) +
        shader_filter_color_id(outer_color) +
        tile_mode.shader_filter_id()
    }
  }
}

///|
pub fn ShaderDescriptor::resource_key(
  self : ShaderDescriptor,
) -> RendererResourceKey {
  RendererResourceKey::shader(self.resource_id())
}

///|
pub fn ShaderDescriptor::resource_descriptor(
  self : ShaderDescriptor,
  byte_size? : Int64 = -1L,
) -> RendererResourceDescriptor {
  let id = self.resource_id()
  let measured_byte_size = if byte_size < 0L {
    id.length().to_int64()
  } else {
    byte_size
  }
  RendererResourceDescriptor::shader(id, byte_size=measured_byte_size)
}