///|
fn streamplot_plot_size(axes : Axes) -> (Double, Double) {
  let figure = axes.figure()
  let (width, height) = match axes.rect() {
    Some(rect) => (rect.width(), rect.height())
    None =>
      (
        Double::from_int(figure.width() - figure.margin() * 2),
        Double::from_int(figure.height() - figure.margin() * 2),
      )
  }
  (width, height)
}

///|
fn streamplot_arrow_data_point(
  tail : @core.Point,
  screen_x : Double,
  screen_y : Double,
  x_pixels : Double,
  y_pixels : Double,
) -> @core.Point {
  @core.point(tail.x() + screen_x / x_pixels, tail.y() - screen_y / y_pixels)
}

///|
fn streamplot_arrow_vertices(
  tail : @core.Point,
  tip : @core.Point,
  direction_x : Double,
  direction_y : Double,
  head_length : Double,
  head_width : Double,
  x_pixels : Double,
  y_pixels : Double,
) -> (@core.Point, @core.Point, @core.Point) {
  let screen_length = (direction_x * direction_x + direction_y * direction_y).sqrt()
  let base_screen_x = direction_x - direction_x / screen_length * head_length
  let base_screen_y = direction_y - direction_y / screen_length * head_length
  let perpendicular_x = -direction_y / screen_length * head_width / 2.0
  let perpendicular_y = direction_x / screen_length * head_width / 2.0
  let left = streamplot_arrow_data_point(
    tail,
    base_screen_x + perpendicular_x,
    base_screen_y + perpendicular_y,
    x_pixels,
    y_pixels,
  )
  let right = streamplot_arrow_data_point(
    tail,
    base_screen_x - perpendicular_x,
    base_screen_y - perpendicular_y,
    x_pixels,
    y_pixels,
  )
  (left, tip, right)
}

///|
fn streamplot_arrowhead(
  tail : @core.Point,
  tip : @core.Point,
  direction_x : Double,
  direction_y : Double,
  head_length : Double,
  head_width : Double,
  x_pixels : Double,
  y_pixels : Double,
  style : @core.Style,
) -> Artist {
  let (left, _, right) = streamplot_arrow_vertices(
    tail, tip, direction_x, direction_y, head_length, head_width, x_pixels, y_pixels,
  )
  Artist::Polygon([left, tip, right], style)
}

///|
fn streamplot_open_arrowhead(
  tail : @core.Point,
  tip : @core.Point,
  direction_x : Double,
  direction_y : Double,
  head_length : Double,
  head_width : Double,
  x_pixels : Double,
  y_pixels : Double,
  style : @core.Style,
) -> Artist {
  let (left, _, right) = streamplot_arrow_vertices(
    tail, tip, direction_x, direction_y, head_length, head_width, x_pixels, y_pixels,
  )
  Artist::Line([left, tip, right], style)
}

///|
fn streamplot_arrow_bar(
  endpoint : @core.Point,
  direction_x : Double,
  direction_y : Double,
  width : Double,
  x_pixels : Double,
  y_pixels : Double,
  style : @core.Style,
) -> Artist {
  let screen_length = (direction_x * direction_x + direction_y * direction_y).sqrt()
  let perpendicular_x = -direction_y / screen_length * width / 2.0
  let perpendicular_y = direction_x / screen_length * width / 2.0
  let left = streamplot_arrow_data_point(
    endpoint, perpendicular_x, perpendicular_y, x_pixels, y_pixels,
  )
  let right = streamplot_arrow_data_point(
    endpoint,
    -perpendicular_x,
    -perpendicular_y,
    x_pixels,
    y_pixels,
  )
  Artist::Line([left, right], style)
}

///|
/// Add arrows at equal arc-length fractions of one streamline.
///
/// Arrowhead dimensions are computed in display units, matching Matplotlib's
/// `mutation_scale=10*arrowsize`, then converted back to data coordinates for
/// the backend-independent scene graph.
fn add_stream_arrows(
  axes : Axes,
  line : Array[@core.Point],
  count : Int32,
  color : String,
  linewidth : Double,
  arrow_size : Double,
  arrowstyle : String,
  x_span : Double,
  y_span : Double,
  xs : Array[Double],
  ys : Array[Double],
  linewidth_grid : Array[Array[Double]]?,
  color_grid : Array[Array[Double]]?,
  color_norm : @colors.ColorNorm?,
  color_map : @colors.ColorMap?,
) -> Array[Artist] raise @core.DataError {
  let result : Array[Artist] = []
  if count <= 0 || line.length() < 2 {
    return result
  }
  let distances = Array(capacity=line.length())
  distances.push(0.0)
  for index in 1.. units_y {
      x_pixels = units_y
      y_pixels = units_y * aspect
    } else {
      y_pixels = units_x * aspect
    }
  }
  for arrow_index in 1..<=count {
    let target = total *
      Double::from_int(arrow_index) /
      Double::from_int(count + 1)
    let mut segment = distances.length() - 2
    for index in 1..= target {
        segment = index - 1
        break
      }
    }
    let tail = line[segment]
    let next = line[Int32::min(segment + 1, line.length() - 1)]
    let (arrow_color, arrow_width) = match
      stream_style_at(
        xs, ys, tail, linewidth, color, linewidth_grid, color_grid, color_norm, color_map,
      ) {
      Some(value) => value
      None => continue
    }
    let line_style = @core.style(arrow_color, width=arrow_width)
    let direction_x = (next.x() - tail.x()) * x_pixels / 2.0
    let direction_y = -(next.y() - tail.y()) * y_pixels / 2.0
    let screen_length = (direction_x * direction_x + direction_y * direction_y).sqrt()
    if screen_length == 0.0 {
      continue
    }
    let tip = streamplot_arrow_data_point(
      tail, direction_x, direction_y, x_pixels, y_pixels,
    )
    let requested_head = 10.0 * arrow_size
    let head_length = Double::min(requested_head, screen_length)
    let head_width = head_length * 0.65
    let (start_head, end_head, start_filled, end_filled, start_bar, end_bar) = match
      arrowstyle {
      "<-" => (true, false, false, false, false, false)
      "->" => (false, true, false, false, false, false)
      "<->" => (true, true, false, false, false, false)
      "<|-" => (true, false, true, false, false, false)
      "-|>" => (false, true, false, true, false, false)
      "<|-|>" => (true, true, true, true, false, false)
      "]-" => (false, false, false, false, true, false)
      "-[" => (false, false, false, false, false, true)
      "]-[" => (false, false, false, false, true, true)
      "|-|" => (false, false, false, false, true, true)
      // FancyArrowPatch's named styles are curved path styles in
      // Matplotlib.  A filled display-space wedge is the closest scene
      // representation available without introducing a path artist.
      "fancy" | "simple" | "wedge" => (false, true, false, true, false, false)
      _ => (false, false, false, false, false, false)
    }
    let base_screen_x = direction_x - direction_x / screen_length * head_length
    let base_screen_y = direction_y - direction_y / screen_length * head_length
    let base_end = streamplot_arrow_data_point(
      tail, base_screen_x, base_screen_y, x_pixels, y_pixels,
    )
    let reverse_direction_x = -direction_x
    let reverse_direction_y = -direction_y
    let reverse_base_screen_x = reverse_direction_x -
      reverse_direction_x / screen_length * head_length
    let reverse_base_screen_y = reverse_direction_y -
      reverse_direction_y / screen_length * head_length
    let base_start = streamplot_arrow_data_point(
      tip, reverse_base_screen_x, reverse_base_screen_y, x_pixels, y_pixels,
    )
    let shaft_start = if start_head { base_start } else { tail }
    let shaft_end = if end_head { base_end } else { tip }
    let line_artist = Artist::Line([shaft_start, shaft_end], line_style)
    axes.push_artist(line_artist)
    if arrowstyle == "-" {
      result.push(line_artist)
    }
    if arrowstyle != "-" && head_width > 0.0 && head_length > 0.0 {
      if start_head {
        let head_style = @core.style(
          arrow_color,
          width=arrow_width,
          fill=if start_filled { arrow_color } else { "" },
        )
        let head = if start_filled {
          streamplot_arrowhead(
            tip, tail, reverse_direction_x, reverse_direction_y, head_length, head_width,
            x_pixels, y_pixels, head_style,
          )
        } else {
          streamplot_open_arrowhead(
            tip, tail, reverse_direction_x, reverse_direction_y, head_length, head_width,
            x_pixels, y_pixels, head_style,
          )
        }
        axes.push_artist(head)
        result.push(head)
      }
      if end_head {
        let head_style = @core.style(
          arrow_color,
          width=arrow_width,
          fill=if end_filled { arrow_color } else { "" },
        )
        let head = if end_filled {
          streamplot_arrowhead(
            tail, tip, direction_x, direction_y, head_length, head_width, x_pixels,
            y_pixels, head_style,
          )
        } else {
          streamplot_open_arrowhead(
            tail, tip, direction_x, direction_y, head_length, head_width, x_pixels,
            y_pixels, head_style,
          )
        }
        axes.push_artist(head)
        result.push(head)
      }
    }
    if start_bar {
      let bar = streamplot_arrow_bar(
        tail, direction_x, direction_y, head_width, x_pixels, y_pixels, line_style,
      )
      axes.push_artist(bar)
      result.push(bar)
    }
    if end_bar {
      let bar = streamplot_arrow_bar(
        tip, direction_x, direction_y, head_width, x_pixels, y_pixels, line_style,
      )
      axes.push_artist(bar)
      result.push(bar)
    }
  }
  result
}