///|
pub struct FlowStats {
  count : Int
  mean_dx : Int
  mean_dy : Int
  mean_magnitude : Int
  max_magnitude : Int
  out_of_bounds : Int
} derive(Debug, Eq)

///|
pub fn FlowStats::from_flow(flow : Array[FlowVector], size : Size) -> FlowStats {
  let mut dx = 0
  let mut dy = 0
  let mut magnitude = 0
  let mut high = 0
  let mut outside = 0
  for vector in flow {
    dx += vector.dx
    dy += vector.dy
    let length = abs_i(vector.dx) + abs_i(vector.dy)
    magnitude += length
    high = max_i(high, length)
    if vector.x < 0 ||
      vector.y < 0 ||
      vector.x >= size.width ||
      vector.y >= size.height {
      outside += 1
    }
  }
  let count = flow.length()
  {
    count,
    mean_dx: dx / max_i(1, count),
    mean_dy: dy / max_i(1, count),
    mean_magnitude: magnitude / max_i(1, count),
    max_magnitude: high,
    out_of_bounds: outside,
  }
}

///|
pub fn flow_divergence(flow : Array[FlowVector], size : Size) -> Int {
  let mut total = 0
  let mut count = 0
  for vector in flow {
    if vector.x > 0 &&
      vector.y > 0 &&
      vector.x + 1 < size.width &&
      vector.y + 1 < size.height {
      total += vector.dx + vector.dy
      count += 1
    }
  }
  total / max_i(1, count)
}

///|
pub fn flow_curl(flow : Array[FlowVector], size : Size) -> Int {
  let mut total = 0
  let mut count = 0
  for vector in flow {
    if vector.x > 0 &&
      vector.y > 0 &&
      vector.x + 1 < size.width &&
      vector.y + 1 < size.height {
      total += vector.dy - vector.dx
      count += 1
    }
  }
  total / max_i(1, count)
}

///|
pub fn flow_magnitude_image(
  flow : Array[FlowVector],
  size : Size,
  scale : Int,
) -> GrayImage {
  let out = GrayImage::new(width=size.width, height=size.height)
  let factor = max_i(1, scale)
  for vector in flow {
    if out.in_bounds(vector.x, vector.y) {
      out.set(vector.x, vector.y, Gray::new(flow_magnitude_l1(vector) * factor))
    }
  }
  out
}

///|
pub fn flow_direction_image(flow : Array[FlowVector], size : Size) -> GrayImage {
  let out = GrayImage::new(width=size.width, height=size.height)
  for vector in flow {
    if out.in_bounds(vector.x, vector.y) {
      let horizontal = vector.dx + 255
      let vertical = vector.dy + 255
      out.set(vector.x, vector.y, Gray::new((horizontal + vertical) / 2))
    }
  }
  out
}

///|
pub fn flow_confidence(
  flow : Array[FlowVector],
  size : Size,
  max_length : Int,
) -> GrayImage {
  let out = GrayImage::new(width=size.width, height=size.height)
  let maximum = max_i(1, max_length)
  for vector in flow {
    if out.in_bounds(vector.x, vector.y) {
      let length = abs_i(vector.dx) + abs_i(vector.dy)
      out.set(vector.x, vector.y, Gray::new(255 - length * 255 / maximum))
    }
  }
  out
}

///|
pub fn flow_endpoint_error_l1(
  expected : Array[FlowVector],
  actual : Array[FlowVector],
) -> Int {
  let count = min_i(expected.length(), actual.length())
  if count == 0 {
    0
  } else {
    let mut total = 0
    for i in 0.. Int {
  let count = min_i(expected.length(), actual.length())
  if count == 0 {
    0
  } else {
    let mut total = 0
    for i in 0.. Int {
  let count = min_i(expected.length(), actual.length())
  if count == 0 {
    1000
  } else {
    let mut good = 0
    for i in 0.. RgbImage {
  let out = RgbImage::new(width=size.width, height=size.height)
  let factor = max_i(1, scale)
  for vector in flow {
    if out.in_bounds(vector.x, vector.y) {
      let red = 128 + vector.dx * factor
      let green = 128 + vector.dy * factor
      let blue = 255 - abs_i(vector.dx + vector.dy) * factor
      out.set(vector.x, vector.y, Rgb::new(r=red, g=green, b=blue))
    }
  }
  out
}

///|
pub fn flow_to_json_summary(flow : Array[FlowVector], size : Size) -> String {
  let stats = FlowStats::from_flow(flow, size)
  "{\"count\":\{stats.count},\"mean_dx\":\{stats.mean_dx},\"mean_dy\":\{stats.mean_dy},\"mean_magnitude\":\{stats.mean_magnitude},\"max_magnitude\":\{stats.max_magnitude},\"out_of_bounds\":\{stats.out_of_bounds}}"
}

///|
pub fn validate_flow(flow : Array[FlowVector], size : Size) -> ValidationReport {
  let report = ValidationReport::new()
  for vector in flow {
    if !size.is_valid() {
      report.error("flow target image size must be positive")
    }
    if vector.x < 0 ||
      vector.y < 0 ||
      vector.x >= size.width ||
      vector.y >= size.height {
      report.warning("flow origin is outside the target image")
    }
    if abs_i(vector.dx) > size.width || abs_i(vector.dy) > size.height {
      report.warning("flow displacement exceeds image extent")
    }
  }
  report
}