///|
/// Read the image dimensions from a JPEG 2000 (JP2) file without decoding the
/// code stream. The dimensions live in the `ihdr` (Image Header) box inside the
/// top-level `jp2h` header box.

///|
pub fn jp2_dimensions(bytes : Bytes) -> (Int32, Int32)? {
  let end = bytes.length()
  // The file must start with the JPEG 2000 signature box (type "jP  ") whose
  // payload is the fixed 0x0D0A870A signature.
  if end < 12 ||
    !box_type_is(bytes, 4, b"jP  ") ||
    bytes[8] != b'\x0D' ||
    bytes[9] != b'\x0A' ||
    bytes[10] != b'\x87' ||
    bytes[11] != b'\x0A' {
    return None
  }

  let (jp2h_start, jp2h_end) = match find_box(bytes, 0, end, b"jp2h") {
    Some(value) => value
    None => return None
  }
  // `jp2h` is a plain box (no version/flags), so its children start right away.
  let (ihdr_start, ihdr_end) = match
    find_box(bytes, jp2h_start, jp2h_end, b"ihdr") {
    Some(value) => value
    None => return None
  }
  // `ihdr` payload: height(4) width(4) components(2) bpc(1) compression(1) ...
  if ihdr_start + 8 > ihdr_end {
    return None
  }
  let height = be_u32(bytes, ihdr_start)
  let width = be_u32(bytes, ihdr_start + 4)
  if width <= 0L ||
    height <= 0L ||
    width >= 2_147_483_648L ||
    height >= 2_147_483_648L {
    return None
  }
  Some((Int64::to_int(width), Int64::to_int(height)))
}

///|
async fn read_jp2_dimensions(path : String, size : Int64) -> (Int32, Int32)? {
  if size < 12L {
    return None
  }
  // The `jp2h`/`ihdr` boxes normally sit near the front of the file; bound the
  // read to keep memory use predictable.
  let header_length = if size > 4_194_304L {
    4_194_304
  } else {
    Int64::to_int(size)
  }
  let file = @fs.open(path, mode=@fs.ReadOnly)
  defer file.close()
  let header = file.read_exactly_at(header_length, position=0L)
  jp2_dimensions(header)
}

///|
test "reads JP2 dimensions from the jp2h/ihdr boxes" {
  let ihdr : Bytes = b"\x00\x00\x00\x16ihdr\x00\x00\x00\x18\x00\x00\x00\x20\x00\x03\x08\x07\x00\x00"
  let jp2h : Bytes = b"\x00\x00\x00\x1Ejp2h" + ihdr
  let bytes : Bytes = b"\x00\x00\x00\x0CjP  \x0D\x0A\x87\x0A" +
    b"\x00\x00\x00\x14ftypjp2 \x00\x00\x00\x00jp2 " +
    jp2h
  assert_true(jp2_dimensions(bytes) == Some((32, 24)))
}

///|
test "rejects JP2 files that are not JPEG 2000 or lack the ihdr box" {
  assert_true(jp2_dimensions(b"not a jp2") is None)
  let no_ihdr : Bytes = b"\x00\x00\x00\x0CjP  \x0D\x0A\x87\x0A" +
    b"\x00\x00\x00\x10jp2h\x00\x00\x00\x08free"
  assert_true(jp2_dimensions(no_ihdr) is None)
  let bad_signature : Bytes = b"\x00\x00\x00\x0CjP  \x0D\x0A\x87\x0B" +
    b"\x00\x00\x00\x10jp2h\x00\x00\x00\x08free"
  assert_true(jp2_dimensions(bad_signature) is None)
}