///|
fn pdf_jpeg_eoi_length(data : BytesView) -> Int? {
  let mut last = 0
  for index in 0.. PdfBytes raise PdfError {
  match pdf_jpeg_eoi_length(cursor.remaining_view()) {
    Some(length) => cursor.read_bytes(length)
    None => {
      let _ = cursor.read_view(cursor.remaining_length())
      raise EndOfInput
    }
  }
}

///|
/// Reads JPEG data from a cursor through the EOI marker.
pub fn pdf_get_jpeg_data(input : ByteCursor) -> PdfBytes raise PdfError {
  pdf_cursor_read_jpeg_data(input)
}

///|
fn pdf_jpeg_get(data : BytesView, index : Int) -> Int? {
  if index >= 0 && index < data.length() {
    Some(data[index].to_int())
  } else {
    None
  }
}

///|
fn pdf_jpeg_get_or(
  data : BytesView,
  index : Int,
  error : PdfError,
) -> Int raise PdfError {
  match pdf_jpeg_get(data, index) {
    Some(value) => value
    None => raise error
  }
}

///|
fn pdf_jpeg_word_at(
  data : BytesView,
  index : Int,
  error : PdfError,
) -> Int raise PdfError {
  pdf_jpeg_get_or(data, index, error) * 256 +
  pdf_jpeg_get_or(data, index + 1, error)
}

///|
fn pdf_jpeg_has_app_header(data : BytesView) -> Bool {
  data.length() >= 4 &&
  data[0].to_int() == 0xFF &&
  data[1].to_int() == 0xD8 &&
  data[2].to_int() == 0xFF &&
  (data[3].to_int() == 0xE0 || data[3].to_int() == 0xE1)
}

///|
fn pdf_jpeg_has_signature(data : BytesView, index : Int) -> Bool raise PdfError {
  let first = pdf_jpeg_get_or(data, index + 2, BadJPEGString)
  let second = pdf_jpeg_get_or(data, index + 3, BadJPEGString)
  let third = pdf_jpeg_get_or(data, index + 4, BadJPEGString)
  let fourth = pdf_jpeg_get_or(data, index + 5, BadJPEGString)
  let zero = pdf_jpeg_get_or(data, index + 6, BadJPEGString)
  (
    first == 0x4A &&
    second == 0x46 &&
    third == 0x49 &&
    fourth == 0x46 &&
    zero == 0
  ) ||
  (
    first == 0x45 &&
    second == 0x78 &&
    third == 0x69 &&
    fourth == 0x66 &&
    zero == 0
  )
}

///|
fn pdf_jpeg_dimensions_inner(data : BytesView) -> (Int, Int) raise PdfError {
  if !pdf_jpeg_has_app_header(data) {
    raise BadJPEGHeader
  }
  let mut index = 4
  if !pdf_jpeg_has_signature(data, index) {
    raise BadJPEGString
  }
  let mut block_length = pdf_jpeg_word_at(data, index, JPEGDimensionsExpected)
  while index < data.length() {
    index += block_length
    if index >= data.length() {
      raise JPEGDimensionsExpected
    }
    if pdf_jpeg_get_or(data, index, JPEGDimensionsExpected) != 0xFF {
      raise InvalidJPEGBlock
    }
    if pdf_jpeg_get_or(data, index + 1, JPEGDimensionsExpected) == 0xC0 {
      let height = pdf_jpeg_word_at(data, index + 5, JPEGDimensionsExpected)
      let width = pdf_jpeg_word_at(data, index + 7, JPEGDimensionsExpected)
      return (width, height)
    }
    index += 2
    block_length = pdf_jpeg_word_at(data, index, JPEGDimensionsExpected)
  }
  raise JPEGDimensionsExpected
}

///|
/// Return the width and height of a baseline JFIF/Exif JPEG byte stream.
///
/// This matches cpdf's parser: the image must start with SOI plus APP0/APP1
/// JFIF or Exif metadata, and dimensions are read from the first SOF0 block.
/// Progressive JPEGs and JPEGs without SOF0 raise `PdfError::JPEGDimensionsExpected`.
pub fn pdf_jpeg_dimensions_view(data : BytesView) -> (Int, Int) raise PdfError {
  pdf_jpeg_dimensions_inner(data)
}

///|
/// Return the width and height of a baseline JFIF/Exif JPEG byte stream.
pub fn pdf_jpeg_dimensions(data : PdfBytes) -> (Int, Int) raise PdfError {
  pdf_jpeg_dimensions_view(data)
}

///|
fn pdf_jpeg_marker_without_segment_length(marker : Int) -> Bool {
  marker == 0x01 || (marker >= 0xD0 && marker <= 0xD9)
}

///|
fn pdf_jpeg_marker_has_dimensions(marker : Int) -> Bool {
  match marker {
    0xC0
    | 0xC1
    | 0xC2
    | 0xC3
    | 0xC5
    | 0xC6
    | 0xC7
    | 0xC9
    | 0xCA
    | 0xCB
    | 0xCD
    | 0xCE
    | 0xCF => true
    _ => false
  }
}

///|
fn pdf_jpeg_next_marker(
  data : BytesView,
  index : Int,
) -> (Int, Int) raise PdfError {
  let mut cursor = index
  while cursor < data.length() && data[cursor].to_int() != 0xFF {
    cursor += 1
  }
  if cursor >= data.length() {
    raise JPEGDimensionsExpected
  }
  while cursor < data.length() && data[cursor].to_int() == 0xFF {
    cursor += 1
  }
  if cursor >= data.length() {
    raise JPEGDimensionsExpected
  }
  (data[cursor].to_int(), cursor + 1)
}

///|
fn pdf_jpeg_backup_dimensions_inner(
  data : BytesView,
) -> (Int, Int) raise PdfError {
  if data.length() < 2 || data[0].to_int() != 0xFF || data[1].to_int() != 0xD8 {
    raise BadJPEGHeader
  }
  let mut index = 2
  while index < data.length() {
    let (marker, segment_start) = pdf_jpeg_next_marker(data, index)
    if marker == 0xD9 {
      raise JPEGDimensionsExpected
    }
    if pdf_jpeg_marker_without_segment_length(marker) {
      index = segment_start
    } else {
      let segment_length = pdf_jpeg_word_at(
        data,
        segment_start,
        JPEGDimensionsExpected,
      )
      if segment_length < 2 {
        raise InvalidJPEGBlock
      }
      if segment_start + segment_length > data.length() {
        raise JPEGDimensionsExpected
      }
      if pdf_jpeg_marker_has_dimensions(marker) {
        if segment_length < 7 {
          raise JPEGDimensionsExpected
        }
        let height = pdf_jpeg_word_at(
          data,
          segment_start + 3,
          JPEGDimensionsExpected,
        )
        let width = pdf_jpeg_word_at(
          data,
          segment_start + 5,
          JPEGDimensionsExpected,
        )
        return (width, height)
      }
      index = segment_start + segment_length
    }
  }
  raise JPEGDimensionsExpected
}

///|
/// Return JPEG dimensions by scanning any SOF marker with width and height.
///
/// This is the pure byte parser used by the cpdfjpeg `backup_jpeg_dimensions`
/// file wrapper. It accepts progressive JPEG SOF2 data that cpdf's primary
/// `jpeg_dimensions` parser rejects.
pub fn pdf_jpeg_backup_dimensions_view(
  data : BytesView,
) -> (Int, Int) raise PdfError {
  pdf_jpeg_backup_dimensions_inner(data)
}

///|
/// Return JPEG dimensions by scanning any SOF marker with width and height.
pub fn pdf_jpeg_backup_dimensions(data : PdfBytes) -> (Int, Int) raise PdfError {
  pdf_jpeg_backup_dimensions_view(data)
}