// Copyright 2025 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

///|
/// CFF2 (OpenType `CFF2` table) outline extraction.
///
/// This parses the header/top-dict and evaluates Type2 charstrings.
///
/// Notes:
/// - Variation operators (vsindex/blend) are supported when VarStore is present.

///|
fn cff2_read_u16_be(view : BytesView, offset : Int) -> Int? {
  type2_read_u16_be(view, offset)
}

///|
fn cff2_fdselect_index(
  data : BytesView,
  offset : Int,
  glyph_index : Int,
) -> Int? {
  if offset < 0 || offset >= data.length() {
    return None
  }
  let select = data[offset:data.length()]
  if select.length() < 1 {
    return None
  }
  let format = select.at(0).to_int()
  match format {
    0 =>
      if glyph_index < 0 || 1 + glyph_index >= select.length() {
        None
      } else {
        Some(select.at(1 + glyph_index).to_int())
      }
    3 =>
      if select.length() < 3 {
        None
      } else {
        let n_ranges = cff2_read_u16_be(select, 1).unwrap_or(0)
        if n_ranges <= 0 {
          return None
        }
        let ranges_base = 3
        let rec_size = 3
        let sentinel_off = ranges_base + n_ranges * rec_size
        if sentinel_off + 2 > select.length() {
          return None
        }
        let sentinel = cff2_read_u16_be(select, sentinel_off).unwrap_or(0)
        if glyph_index < 0 || glyph_index >= sentinel {
          return None
        }
        let mut r = 0
        while r < n_ranges {
          let rec = ranges_base + r * rec_size
          if rec + 3 > select.length() {
            break
          }
          let first = cff2_read_u16_be(select, rec).unwrap_or(0)
          let fd = select.at(rec + 2).to_int()
          let next_first = if r + 1 < n_ranges {
            cff2_read_u16_be(select, rec + rec_size).unwrap_or(sentinel)
          } else {
            sentinel
          }
          if glyph_index >= first && glyph_index < next_first {
            return Some(fd)
          }
          r = r + 1
        }
        None
      }
    _ => None
  }
}

///|
fn cff2_outline_path_hinted(
  font : @moon_skrifa.FontRef,
  gid : @moon_skrifa.GlyphId,
  coords : ArrayView[@moon_skrifa.NormalizedCoord],
  size : @moon_skrifa.Size,
) -> Array[PathElement]? {
  let cff2 = match font.table(TAG_CFF2) {
    None => return None
    Some(v) => v
  }
  if cff2.length() < 5 {
    return None
  }
  let major = cff2.at(0).to_int()
  let minor = cff2.at(1).to_int()
  if major != 2 || minor != 0 {
    return None
  }
  let header_size = cff2.at(2).to_int()
  let top_len = match cff2_read_u16_be(cff2, 3) {
    None => return None
    Some(v) => v
  }
  if header_size < 5 || header_size > cff2.length() {
    return None
  }
  let top_start = header_size
  let top_end = top_start + top_len
  if top_len < 0 || top_end < top_start || top_end > cff2.length() {
    return None
  }
  let top_dict = cff2[top_start:top_end]
  let dict = cff_dict_parse(top_dict)
  let charstrings_off = match dict.charstrings_offset {
    None => return None
    Some(v) => v
  }
  let blend_state = match dict.var_store_offset {
    None => None
    Some(off0) => {
      // ItemVariationStore is preceded by a 2-byte length.
      let off = off0 + 2
      if off < 0 || off > cff2.length() {
        None
      } else {
        type2_blend_state_new(cff2, off, coords)
      }
    }
  }
  if charstrings_off < 0 || charstrings_off > cff2.length() {
    return None
  }
  let global_off = top_end
  if global_off > cff2.length() {
    return None
  }
  let global_ix = match type2_index2_read(cff2[global_off:cff2.length()]) {
    None => return None
    Some(ix) => ix
  }
  let char_ix = match type2_index2_read(cff2[charstrings_off:cff2.length()]) {
    None => return None
    Some(ix) => ix
  }
  let glyph_index = gid.to_uint64().to_int()
  if glyph_index < 0 || glyph_index >= char_ix.count {
    return None
  }
  let charstring = match type2_index_get(char_ix, glyph_index) {
    None => return None
    Some(v) => v
  }
  if charstring.length() == 0 {
    return Some(Array::new())
  }
  let upem = outline_units_per_em(font).to_int()
  let hint_scale = match cff_hint_scale_for_hinting(size, upem) {
    None => return None
    Some(v) => v
  }

  // Resolve private dict from FDArray/FDSelect if present, else use top dict.
  let mut private_size : Int? = dict.private_size
  let mut private_off : Int? = dict.private_offset
  if dict.fd_array_offset is Some(fd_array_off) {
    let fd_ix = match type2_index2_read(cff2[fd_array_off:cff2.length()]) {
      None => return None
      Some(ix) => ix
    }
    let fd_select_ix = match dict.fd_select_offset {
      None => 0
      Some(sel_off) =>
        cff2_fdselect_index(cff2, sel_off, glyph_index).unwrap_or(0)
    }
    let fd_data = match type2_index_get(fd_ix, fd_select_ix) {
      None => return None
      Some(v) => v
    }
    let fd_dict = cff_dict_parse(fd_data)
    private_size = fd_dict.private_size
    private_off = fd_dict.private_offset
  }
  // Private DICT + local Subrs (optional). Note: Private DICT may be present
  // with size==0 (valid; means all defaults).
  let mut poff : Int? = None
  let mut priv_info = CffPrivateDictHintInfo::default()
  match (private_size, private_off) {
    (Some(psize), Some(poff0)) => {
      poff = Some(poff0)
      if psize < 0 || poff0 < 0 || poff0 + psize > cff2.length() {
        return None
      }
      let private_dict = cff2[poff0:poff0 + psize]
      priv_info = cff_private_dict_hint_info(private_dict, blend_state)
    }
    _ => ()
  }
  match blend_state {
    None => ()
    Some(bs) =>
      if priv_info.store_index != 0 {
        Type2BlendState::set_store_index(bs, priv_info.store_index) |> ignore
      }
  }
  let local_ix : Type2Index? = match (poff, priv_info.subrs_offset) {
    (Some(poff0), Some(subrs_off)) => {
      let subrs_abs = poff0 + subrs_off
      if subrs_abs < 0 || subrs_abs > cff2.length() {
        None
      } else {
        type2_index2_read(cff2[subrs_abs:cff2.length()])
      }
    }
    _ => None
  }
  let hint_state = HintState(priv_info.hint_params, hint_scale)
  type2_path_hinted(
    charstring,
    Some(global_ix),
    local_ix,
    blend_state,
    hint_state,
    None,
  )
}

///|

///|
fn cff2_outline_path(
  font : @moon_skrifa.FontRef,
  gid : @moon_skrifa.GlyphId,
  coords : ArrayView[@moon_skrifa.NormalizedCoord],
) -> Array[PathElement]? {
  let cff2 = match font.table(TAG_CFF2) {
    None => return None
    Some(v) => v
  }
  if cff2.length() < 5 {
    return None
  }
  let major = cff2.at(0).to_int()
  let minor = cff2.at(1).to_int()
  if major != 2 || minor != 0 {
    return None
  }
  let header_size = cff2.at(2).to_int()
  let top_len = match cff2_read_u16_be(cff2, 3) {
    None => return None
    Some(v) => v
  }
  if header_size < 5 || header_size > cff2.length() {
    return None
  }
  let top_start = header_size
  let top_end = top_start + top_len
  if top_len < 0 || top_end < top_start || top_end > cff2.length() {
    return None
  }
  let top_dict = cff2[top_start:top_end]
  let dict = cff_dict_parse(top_dict)
  let charstrings_off = match dict.charstrings_offset {
    None => return None
    Some(v) => v
  }
  let blend_state = match dict.var_store_offset {
    None => None
    Some(off) =>
      if off < 0 || off > cff2.length() {
        None
      } else {
        type2_blend_state_new(cff2, off, coords)
      }
  }
  if charstrings_off < 0 || charstrings_off > cff2.length() {
    return None
  }
  let global_off = top_end
  if global_off > cff2.length() {
    return None
  }
  let global_ix = match type2_index2_read(cff2[global_off:cff2.length()]) {
    None => return None
    Some(ix) => ix
  }
  // CharStrings INDEX2 is at the offset from Top DICT.
  let char_ix = match type2_index2_read(cff2[charstrings_off:cff2.length()]) {
    None => return None
    Some(ix) => ix
  }
  let glyph_index = gid.to_uint64().to_int()
  if glyph_index < 0 || glyph_index >= char_ix.count {
    return None
  }
  let charstring = match type2_index_get(char_ix, glyph_index) {
    None => return None
    Some(v) => v
  }
  if charstring.length() == 0 {
    return Some(Array::new())
  }
  type2_path(charstring, Some(global_ix), None, blend_state)
}

///|
fn cff2_glyph_count(font : @moon_skrifa.FontRef) -> Int? {
  let cff2 = match font.table(TAG_CFF2) {
    None => return None
    Some(v) => v
  }
  if cff2.length() < 5 {
    return None
  }
  let major = cff2.at(0).to_int()
  let minor = cff2.at(1).to_int()
  if major != 2 || minor != 0 {
    return None
  }
  let header_size = cff2.at(2).to_int()
  let top_len = match cff2_read_u16_be(cff2, 3) {
    None => return None
    Some(v) => v
  }
  if header_size < 5 || header_size > cff2.length() {
    return None
  }
  let top_start = header_size
  let top_end = top_start + top_len
  if top_len < 0 || top_end < top_start || top_end > cff2.length() {
    return None
  }
  let top_dict = cff2[top_start:top_end]
  let dict = cff_dict_parse(top_dict)
  let charstrings_off = match dict.charstrings_offset {
    None => return None
    Some(v) => v
  }
  if charstrings_off < 0 || charstrings_off > cff2.length() {
    return None
  }
  let char_ix = match type2_index2_read(cff2[charstrings_off:cff2.length()]) {
    None => return None
    Some(ix) => ix
  }
  Some(char_ix.count)
}