///|
#cfg(target="native")
#borrow(subject, clip, status)
extern "C" fn pdf_clip_polygon_clip_ffi(
  subject : PdfBytes,
  clip : PdfBytes,
  operation : Int,
  status : Ref[Int],
) -> PdfBytes = "pdflite_gpc_clip"

///|
#cfg(target="native")
fn pdf_clip_raise_bad_native_encoding() -> Unit raise PdfError {
  raise BadNumberArgument("pdf_clip_polygon_clip: malformed native polygon")
}

///|
#cfg(target="native")
fn pdf_clip_push_i32_le(
  output : Array[Byte],
  value : Int,
) -> Unit raise PdfError {
  if value < 0 {
    pdf_clip_raise_bad_native_encoding()
  }
  output.push((value & 0xff).to_byte())
  output.push(((value >> 8) & 0xff).to_byte())
  output.push(((value >> 16) & 0xff).to_byte())
  output.push(((value >> 24) & 0xff).to_byte())
}

///|
#cfg(target="native")
fn pdf_clip_push_u64_le(output : Array[Byte], value : UInt64) -> Unit {
  for byte in value.to_le_bytes() {
    output.push(byte)
  }
}

///|
#cfg(target="native")
fn pdf_clip_encode_polygon(polygon : PdfClipPolygon) -> PdfBytes raise PdfError {
  let output : Array[Byte] = []
  pdf_clip_push_i32_le(output, polygon.contours.length())
  for contour in polygon.contours {
    pdf_clip_push_i32_le(output, contour.hole_flag())
    pdf_clip_push_i32_le(output, contour.vertices.length())
    for vertex in contour.vertices {
      pdf_clip_push_u64_le(output, vertex.x.reinterpret_as_uint64())
      pdf_clip_push_u64_le(output, vertex.y.reinterpret_as_uint64())
    }
  }
  Bytes::from_array(output)
}

///|
#cfg(target="native")
fn pdf_clip_read_i32_le(
  data : BytesView,
  position : Ref[Int],
) -> Int raise PdfError {
  if position.val < 0 ||
    position.val > data.length() ||
    data.length() - position.val < 4 {
    raise EndOfInput
  }
  let index = position.val
  let value = data[index].to_int() |
    (data[index + 1].to_int() << 8) |
    (data[index + 2].to_int() << 16) |
    (data[index + 3].to_int() << 24)
  position.val += 4
  if value < 0 {
    pdf_clip_raise_bad_native_encoding()
  }
  value
}

///|
#cfg(target="native")
fn pdf_clip_read_u64_le(
  data : BytesView,
  position : Ref[Int],
) -> UInt64 raise PdfError {
  if position.val < 0 ||
    position.val > data.length() ||
    data.length() - position.val < 8 {
    raise EndOfInput
  }
  let index = position.val
  let mut value : UInt64 = 0
  for offset in 0..<8 {
    value = value | (data[index + offset].to_uint64() << (offset * 8))
  }
  position.val += 8
  value
}

///|
#cfg(target="native")
fn pdf_clip_decode_count_for_remaining_bytes(
  count : Int,
  bytes_per_item : Int,
  data : BytesView,
  position : Ref[Int],
) -> Unit raise PdfError {
  if count < 0 ||
    bytes_per_item <= 0 ||
    count > (data.length() - position.val) / bytes_per_item {
    pdf_clip_raise_bad_native_encoding()
  }
}

///|
#cfg(target="native")
fn pdf_clip_decode_polygon(data : BytesView) -> PdfClipPolygon raise PdfError {
  let position = Ref::{ val: 0 }
  let contour_count = pdf_clip_read_i32_le(data, position)
  pdf_clip_decode_count_for_remaining_bytes(contour_count, 8, data, position)
  let contours : Array[PdfClipContour] = []
  for _ in 0.. PdfClipPolygon raise PdfError {
  let subject_bytes = pdf_clip_encode_polygon(subject)
  let clip_bytes = pdf_clip_encode_polygon(clip)
  let status = Ref::{ val: 0 }
  let result = pdf_clip_polygon_clip_ffi(
    subject_bytes,
    clip_bytes,
    operation.gpc_code(),
    status,
  )
  if status.val != 1 {
    raise BadNumberArgument("pdf_clip_polygon_clip: native GPC failure")
  }
  pdf_clip_decode_polygon(result)
}

///|
/// Clip `subject` by `clip` using this cpdf/GPC operation.
#cfg(target="native")
pub fn PdfClipOperation::clip_polygons(
  self : PdfClipOperation,
  subject : PdfClipPolygon,
  clip : PdfClipPolygon,
) -> PdfClipPolygon raise PdfError {
  pdf_clip_polygon_clip(self, subject, clip)
}