///|
priv enum PdfTextCMapSection {
  PdfTextCMapNone
  PdfTextCMapCIDChar
  PdfTextCMapCIDRange
  PdfTextCMapNotDefChar
  PdfTextCMapNotDefRange
  PdfTextCMapCodeSpace
}

///|
fn pdf_text_push_cmap_cidchar_entries(
  entries : Array[(Int, Int)],
  line : BytesView,
) -> Unit {
  let mut index = 0
  while index < line.length() {
    match pdf_text_parse_cmap_hex_group_at(line, index) {
      Some((source, after_source)) =>
        match pdf_text_parse_cmap_ascii_int_at(line, after_source) {
          Some((cid, after_cid)) => {
            match pdf_text_bytes_to_int(source) {
              Some(code) => entries.push((code, cid))
              None => ()
            }
            index = after_cid
          }
          None => break
        }
      None => break
    }
  }
}

///|
fn pdf_text_push_cmap_cidrange_entries(
  entries : Array[(Int, Int)],
  line : BytesView,
) -> Unit {
  let mut index = 0
  while index < line.length() {
    match pdf_text_parse_cmap_hex_group_at(line, index) {
      Some((source, after_source)) =>
        match pdf_text_parse_cmap_hex_group_at(line, after_source) {
          Some((stop, after_stop)) =>
            match pdf_text_parse_cmap_ascii_int_at(line, after_stop) {
              Some((cid, after_cid)) => {
                match
                  (pdf_text_bytes_to_int(source), pdf_text_bytes_to_int(stop)) {
                  (Some(first), Some(last)) if first <= last =>
                    for code in first..<=last {
                      entries.push((code, cid + code - first))
                    }
                  _ => ()
                }
                index = after_cid
              }
              None => break
            }
          None => break
        }
      None => break
    }
  }
}

///|
fn pdf_text_push_cmap_codespace_entries(
  entries : Array[PdfCMapCodeSpace],
  line : BytesView,
) -> Unit {
  let mut index = 0
  while index < line.length() {
    match pdf_text_parse_cmap_hex_group_at(line, index) {
      Some((source, after_source)) =>
        match pdf_text_parse_cmap_hex_group_at(line, after_source) {
          Some((stop, after_stop)) => {
            match (pdf_text_bytes_to_int(source), pdf_text_bytes_to_int(stop)) {
              (Some(first), Some(last)) if first <= last &&
                source.length() == stop.length() =>
                entries.push({ length: source.length(), first, last })
              _ => ()
            }
            index = after_stop
          }
          None => break
        }
      None => break
    }
  }
}

///|
fn pdf_text_parse_cmap_cid_sections(data : BytesView) -> Array[(Int, Int)]? {
  let cursor = byte_cursor_of_view(data)
  let entries : Array[(Int, Int)] = Array::new(capacity=data.length() / 4)
  let mut section = PdfTextCMapNone
  for raw_line in cursor.read_line_views() {
    let uncommented = pdf_text_cmap_line_without_comment(raw_line)
    for raw_segment in pdf_text_cmap_section_line_views(uncommented) {
      let mut line = raw_segment
      match pdf_text_after_ascii(raw_segment, "begincidchar") {
        Some(rest) => {
          section = PdfTextCMapCIDChar
          line = rest
        }
        None =>
          match pdf_text_after_ascii(raw_segment, "begincidrange") {
            Some(rest) => {
              section = PdfTextCMapCIDRange
              line = rest
            }
            None => ()
          }
      }
      match section {
        PdfTextCMapCIDChar => pdf_text_push_cmap_cidchar_entries(entries, line)
        PdfTextCMapCIDRange =>
          pdf_text_push_cmap_cidrange_entries(entries, line)
        _ => ()
      }
      if pdf_text_line_contains_ascii(line, "endcidchar") &&
        section is PdfTextCMapCIDChar {
        section = PdfTextCMapNone
      }
      if pdf_text_line_contains_ascii(line, "endcidrange") &&
        section is PdfTextCMapCIDRange {
        section = PdfTextCMapNone
      }
    }
  }
  if entries.length() == 0 {
    None
  } else {
    Some(entries)
  }
}

///|
fn pdf_text_parse_cmap_notdef_sections(data : BytesView) -> Array[(Int, Int)]? {
  let cursor = byte_cursor_of_view(data)
  let entries : Array[(Int, Int)] = Array::new(capacity=data.length() / 4)
  let mut section = PdfTextCMapNone
  for raw_line in cursor.read_line_views() {
    let uncommented = pdf_text_cmap_line_without_comment(raw_line)
    for raw_segment in pdf_text_cmap_section_line_views(uncommented) {
      let mut line = raw_segment
      match pdf_text_after_ascii(raw_segment, "beginnotdefchar") {
        Some(rest) => {
          section = PdfTextCMapNotDefChar
          line = rest
        }
        None =>
          match pdf_text_after_ascii(raw_segment, "beginnotdefrange") {
            Some(rest) => {
              section = PdfTextCMapNotDefRange
              line = rest
            }
            None => ()
          }
      }
      match section {
        PdfTextCMapNotDefChar =>
          pdf_text_push_cmap_cidchar_entries(entries, line)
        PdfTextCMapNotDefRange =>
          pdf_text_push_cmap_cidrange_entries(entries, line)
        _ => ()
      }
      if pdf_text_line_contains_ascii(line, "endnotdefchar") &&
        section is PdfTextCMapNotDefChar {
        section = PdfTextCMapNone
      }
      if pdf_text_line_contains_ascii(line, "endnotdefrange") &&
        section is PdfTextCMapNotDefRange {
        section = PdfTextCMapNone
      }
    }
  }
  if entries.length() == 0 {
    None
  } else {
    Some(entries)
  }
}

///|
fn pdf_text_parse_cmap_codespace_sections(
  data : BytesView,
) -> Array[PdfCMapCodeSpace]? {
  let cursor = byte_cursor_of_view(data)
  let entries : Array[PdfCMapCodeSpace] = Array::new(capacity=data.length() / 4)
  let mut section = PdfTextCMapNone
  for raw_line in cursor.read_line_views() {
    let uncommented = pdf_text_cmap_line_without_comment(raw_line)
    for raw_segment in pdf_text_cmap_section_line_views(uncommented) {
      let mut line = raw_segment
      match pdf_text_after_ascii(raw_segment, "begincodespacerange") {
        Some(rest) => {
          section = PdfTextCMapCodeSpace
          line = rest
        }
        None => ()
      }
      match section {
        PdfTextCMapCodeSpace =>
          pdf_text_push_cmap_codespace_entries(entries, line)
        _ => ()
      }
      if pdf_text_line_contains_ascii(line, "endcodespacerange") &&
        section is PdfTextCMapCodeSpace {
        section = PdfTextCMapNone
      }
    }
  }
  if entries.length() == 0 {
    None
  } else {
    Some(entries)
  }
}

///|
fn pdf_text_parse_cmap_cids(data : BytesView) -> Array[(Int, Int)]? {
  if pdf_text_cmap_contains_whitespace(data) {
    let parsed = pdf_text_parse_cmap_cid_sections(data)
    if pdf_text_cmap_has_split_marker(data) {
      let compact = pdf_text_cmap_without_whitespace(data)
      let compact_parsed = pdf_text_parse_cmap_cid_sections(compact)
      match (parsed, compact_parsed) {
        (Some(original_entries), Some(compact_entries)) =>
          if compact_entries.length() > original_entries.length() {
            Some(compact_entries)
          } else {
            Some(original_entries)
          }
        (None, Some(compact_entries)) => Some(compact_entries)
        _ => parsed
      }
    } else {
      parsed
    }
  } else {
    pdf_text_parse_cmap_cid_sections(data)
  }
}

///|
fn pdf_text_parse_cmap_notdefs(data : BytesView) -> Array[(Int, Int)]? {
  if pdf_text_cmap_contains_whitespace(data) {
    let parsed = pdf_text_parse_cmap_notdef_sections(data)
    if pdf_text_cmap_has_split_marker(data) {
      let compact = pdf_text_cmap_without_whitespace(data)
      let compact_parsed = pdf_text_parse_cmap_notdef_sections(compact)
      match (parsed, compact_parsed) {
        (Some(original_entries), Some(compact_entries)) =>
          if compact_entries.length() > original_entries.length() {
            Some(compact_entries)
          } else {
            Some(original_entries)
          }
        (None, Some(compact_entries)) => Some(compact_entries)
        _ => parsed
      }
    } else {
      parsed
    }
  } else {
    pdf_text_parse_cmap_notdef_sections(data)
  }
}

///|
fn pdf_text_parse_cmap_codespaces(data : BytesView) -> Array[PdfCMapCodeSpace]? {
  if pdf_text_cmap_contains_whitespace(data) {
    let parsed = pdf_text_parse_cmap_codespace_sections(data)
    if pdf_text_cmap_has_split_marker(data) {
      let compact = pdf_text_cmap_without_whitespace(data)
      let compact_parsed = pdf_text_parse_cmap_codespace_sections(compact)
      match (parsed, compact_parsed) {
        (Some(original_entries), Some(compact_entries)) =>
          if compact_entries.length() > original_entries.length() {
            Some(compact_entries)
          } else {
            Some(original_entries)
          }
        (None, Some(compact_entries)) => Some(compact_entries)
        _ => parsed
      }
    } else {
      parsed
    }
  } else {
    pdf_text_parse_cmap_codespace_sections(data)
  }
}