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

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

///|
#cfg(target="native")
fn pdf_clip_write_i32_le(
  output : Array[Byte],
  position : Int,
  value : Int,
) -> Int raise @core.PdfError {
  if value < 0 {
    pdf_clip_raise_bad_native_encoding()
  }
  output[position] = (value & 0xff).to_byte()
  output[position + 1] = ((value >> 8) & 0xff).to_byte()
  output[position + 2] = ((value >> 16) & 0xff).to_byte()
  output[position + 3] = ((value >> 24) & 0xff).to_byte()
  position + 4
}

///|
#cfg(target="native")
fn pdf_clip_write_u64_le(
  output : Array[Byte],
  position : Int,
  value : UInt64,
) -> Int {
  let mut position = position
  for byte in value.to_le_bytes() {
    output[position] = byte
    position += 1
  }
  position
}

///|
#cfg(target="native")
fn pdf_clip_encoded_polygon_length(polygon : PdfClipPolygon) -> Int {
  let mut length = 4
  for contour in polygon.contours {
    length += 8 + contour.vertices.length() * 16
  }
  length
}

///|
#cfg(target="native")
fn pdf_clip_encode_polygon(
  polygon : PdfClipPolygon,
) -> @core.PdfBytes raise @core.PdfError {
  let output = Array::make(pdf_clip_encoded_polygon_length(polygon), b'\x00')
  let mut position = 0
  position = pdf_clip_write_i32_le(output, position, polygon.contours.length())
  for contour in polygon.contours {
    position = pdf_clip_write_i32_le(output, position, contour.hole_flag())
    position = pdf_clip_write_i32_le(
      output,
      position,
      contour.vertices.length(),
    )
    for vertex in contour.vertices {
      position = pdf_clip_write_u64_le(
        output,
        position,
        vertex.x.reinterpret_as_uint64(),
      )
      position = pdf_clip_write_u64_le(
        output,
        position,
        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 @core.PdfError {
  if position.val < 0 ||
    position.val > data.length() ||
    data.length() - position.val < 4 {
    raise EndOfInput
  }
  let index = position.val
  let value = data.unsafe_read_uint32_le(index).reinterpret_as_int()
  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 @core.PdfError {
  if position.val < 0 ||
    position.val > data.length() ||
    data.length() - position.val < 8 {
    raise EndOfInput
  }
  let index = position.val
  let value = data.unsafe_read_uint64_le(index)
  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 @core.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 @core.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 @core.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 @core.PdfError {
  pdf_clip_polygon_clip(self, subject, clip)
}

///|
/// Source-spelled compatibility wrapper for cpdfclip's native clip operation.
#cfg(target="native")
pub fn pdf_gpcml_clippolygon(
  operation : PdfClipOperation,
  subject : PdfClipPolygon,
  clip : PdfClipPolygon,
) -> PdfClipPolygon raise @core.PdfError {
  pdf_clip_polygon_clip(operation, subject, clip)
}