///|
let output_dir = "examples/output/"

///|
let asset_path = "examples/assets/object_counting_input.png"

///|
fn write_rgb_png(path : String, img : @image.RgbImage) -> Unit {
  try! @demoio.write_bytes_to_file(path, @export.encode_rgb_png(img))
}

///|
fn write_gray_png(path : String, img : @image.GrayImage) -> Unit {
  try! @demoio.write_bytes_to_file(path, @export.encode_gray_png(img))
}

///|
fn contour_mask(
  width : Int,
  height : Int,
  contours : Array[@components.Contour],
) -> @image.GrayImage {
  let mask = try! @image.gray(width, height)
  // Debug note: contour masks make it easier to inspect whether the extracted
  // boundary pixels agree with the component count shown in the report.
  for contour in contours {
    for point in contour.points() {
      try! mask.set(point.x(), point.y(), b'\xff')
    }
  }
  mask
}

///|
fn largest_contour_area(contours : Array[@components.Contour]) -> Int {
  let mut largest = 0
  for contour in contours {
    let area = @components.contour_area(contour)
    if area > largest {
      largest = area
    }
  }
  largest
}

///|
fn count_outer_contours(contours : Array[@components.Contour]) -> Int {
  let mut count = 0
  for contour in contours {
    match @components.contour_kind(contour) {
      Outer => count = count + 1
      Hole => ()
    }
  }
  count
}

///|
fn count_hole_contours(contours : Array[@components.Contour]) -> Int {
  let mut count = 0
  for contour in contours {
    match @components.contour_kind(contour) {
      Outer => ()
      Hole => count = count + 1
    }
  }
  count
}

///|
fn longest_traced_contour(contours : Array[@components.Contour]) -> Int {
  let mut longest = 0
  for contour in contours {
    let point_count = contour.points().length()
    if point_count > longest {
      longest = point_count
    }
  }
  longest
}

///|
fn largest_approx_vertex_count(
  contours : Array[@components.Contour],
  epsilon : Double,
) -> Int {
  let mut largest = 0
  for contour in contours {
    let vertices = @components.approx_contour(contour, epsilon).length()
    if vertices > largest {
      largest = vertices
    }
  }
  largest
}

///|
fn largest_hull_vertex_count(contours : Array[@components.Contour]) -> Int {
  let mut largest = 0
  for contour in contours {
    let vertices = @components.convex_hull(contour).length()
    if vertices > largest {
      largest = vertices
    }
  }
  largest
}

///|
fn widest_rotated_rect_x100(contours : Array[@components.Contour]) -> Int {
  let mut widest = 0
  for contour in contours {
    let width = (@components.min_area_rect(contour).width() * 100.0 + 0.5).to_int()
    if width > widest {
      widest = width
    }
  }
  widest
}

///|
fn max_solidity_x1000(contours : Array[@components.Contour]) -> Int {
  let mut strongest = 0
  for contour in contours {
    let solidity = (@components.contour_solidity(contour) * 1000.0 + 0.5).to_int()
    if solidity > strongest {
      strongest = solidity
    }
  }
  strongest
}

///|
fn main {
  let input = try! @image.rgb_from_png_bytes(
    try! @demoio.read_bytes_from_file(asset_path),
  )
  let gray = try! @ops.grayscale(input)
  let baseline_binary = try! @ops.threshold(gray, 120)
  let smoothed = try! @filter.median_blur(gray, radius=1)
  let binary = try! @ops.otsu_threshold(smoothed)
  let baseline_components = try! @components.connected_components(
    baseline_binary,
    min_area=20,
  )
  let components = try! @components.connected_components(binary, min_area=20)
  let contours = try! @components.find_contours(binary)
  let outer_contours = count_outer_contours(contours)
  let hole_contours = count_hole_contours(contours)
  let contour_image = contour_mask(binary.width(), binary.height(), contours)
  let input_name = "object_counting_input.png"
  let baseline_binary_name = "object_counting_binary_v1_0.png"
  let binary_name = "object_counting_binary.png"
  let contour_name = "object_counting_contours.png"
  let overlay_name = "object_counting_overlay.svg"
  let report_name = "object_counting_report.html"
  write_rgb_png(output_dir + input_name, input)
  write_gray_png(output_dir + baseline_binary_name, baseline_binary)
  write_gray_png(output_dir + binary_name, binary)
  write_gray_png(output_dir + contour_name, contour_image)
  try! @demoio.write_string_to_file(
    output_dir + overlay_name,
    @export.svg_bounding_boxes(binary.width(), binary.height(), components),
  )
  try! @demoio.write_string_to_file(
    output_dir + report_name,
    @export.html_report(
      "MoonVision Object Counting",
      [
        "v1.0 pipeline: grayscale -> fixed threshold(120) -> connected components",
        "v1.1 pipeline: grayscale -> median blur(radius=1) -> otsu threshold -> connected components",
        "v1.3 contour analysis: find_contours(binary, connectivity=8) -> outer/hole contour hierarchy",
        "v1.0 detected objects: " + baseline_components.length().to_string(),
        "v1.1 detected objects: " + components.length().to_string(),
        "v1.3 all contours: " + contours.length().to_string(),
        "v1.3 outer contours: " + outer_contours.to_string(),
        "v1.3 hole contours: " + hole_contours.to_string(),
        "v1.4 max approx vertices (epsilon=1.0): " +
        largest_approx_vertex_count(contours, 1.0).to_string(),
        "v1.4 max hull vertices: " +
        largest_hull_vertex_count(contours).to_string(),
        "v1.4 widest rotated rect x100: " +
        widest_rotated_rect_x100(contours).to_string(),
        "v1.4 max solidity x1000: " + max_solidity_x1000(contours).to_string(),
        "largest contour area: " + largest_contour_area(contours).to_string(),
        "longest traced contour path: " +
        longest_traced_contour(contours).to_string(),
        "input asset: " + asset_path,
        "minimum area filter: 20 pixels",
      ],
      [
        @export.report_image("input asset", input_name),
        @export.report_image("v1.0 binary mask", baseline_binary_name),
        @export.report_image("v1.1 binary mask", binary_name),
        @export.report_image("v1.3 contour hierarchy mask", contour_name),
        @export.report_image("bounding box overlay", overlay_name),
      ],
    ),
  )
  println("v1.0 detected objects: " + baseline_components.length().to_string())
  println("v1.1 detected objects: " + components.length().to_string())
  println("v1.3 all contours: " + contours.length().to_string())
  println("v1.3 outer contours: " + outer_contours.to_string())
  println("v1.3 hole contours: " + hole_contours.to_string())
  println(
    "v1.4 max approx vertices (epsilon=1.0): " +
    largest_approx_vertex_count(contours, 1.0).to_string(),
  )
  println(
    "v1.4 max hull vertices: " + largest_hull_vertex_count(contours).to_string(),
  )
  println(
    "v1.4 widest rotated rect x100: " +
    widest_rotated_rect_x100(contours).to_string(),
  )
  println(
    "v1.4 max solidity x1000: " + max_solidity_x1000(contours).to_string(),
  )
  println("report: " + output_dir + report_name)
}