// 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.

///|
/// Support for accessing glyph names.
///
/// Ported from `fontations/skrifa/src/glyph_name.rs` (Apache-2.0 OR MIT).
const GLYPH_TAG_MAXP : UInt = 0x6D617870 // "maxp"

///|
const GLYPH_TAG_POST : UInt = 0x706F7374 // "post"

///|
const GLYPH_TAG_CFF : UInt = 0x43464620 // "CFF "

///|
const MAX_GLYPH_NAME_LEN : Int = 63

///|
pub(all) enum GlyphNameSource {
  Post
  Cff
  Synthesized
}

///|
pub struct GlyphName {
  priv name : String
  priv synthesized : Bool
}

///|
pub fn GlyphName::as_str(self : GlyphName) -> String {
  self.name
}

///|
pub fn GlyphName::is_synthesized(self : GlyphName) -> Bool {
  self.synthesized
}

///|
fn glyph_name_truncate(s : String) -> String {
  // Glyph names are expected to be ASCII; truncate by code units.
  let out : Array[Char] = Array::new(capacity=MAX_GLYPH_NAME_LEN)
  let mut n = 0
  for c in s {
    if n >= MAX_GLYPH_NAME_LEN {
      break
    }
    out.push(c)
    n = n + 1
  }
  String::from_array(out.op_as_view())
}

///|
fn glyph_name_latin1_string(bytes : BytesView) -> String {
  let chars : Array[Char] = Array::new()
  for b in bytes.iter() {
    chars.push(b.to_int().unsafe_to_char())
  }
  String::from_array(chars.op_as_view())
}

///|
fn glyph_name_synthesize(gid : GlyphId) -> GlyphName {
  { name: glyph_name_truncate("gid" + gid.to_string()), synthesized: true }
}

///|
fn glyph_name_read_u16_be(view : BytesView, offset : Int) -> Int? {
  if offset < 0 || offset + 2 > view.length() {
    None
  } else {
    let b0 = view.at(offset).to_int()
    let b1 = view.at(offset + 1).to_int()
    Some((b0 << 8) | b1)
  }
}

///|
fn glyph_name_read_u32_be(view : BytesView, offset : Int) -> UInt? {
  if offset < 0 || offset + 4 > view.length() {
    None
  } else {
    let b0 = view.at(offset).to_uint()
    let b1 = view.at(offset + 1).to_uint()
    let b2 = view.at(offset + 2).to_uint()
    let b3 = view.at(offset + 3).to_uint()
    Some((b0 << 24) | (b1 << 16) | (b2 << 8) | b3)
  }
}

///|
fn glyph_name_read_u8(view : BytesView, offset : Int) -> Int? {
  if offset < 0 || offset + 1 > view.length() {
    None
  } else {
    Some(view.at(offset).to_int())
  }
}

///|
fn glyph_name_read_maxp_num_glyphs(font : FontRef) -> Int {
  match font.table(GLYPH_TAG_MAXP) {
    None => 0
    Some(maxp) => glyph_name_read_u16_be(maxp, 4).unwrap_or(0)
  }
}

///|
fn glyph_name_post_v2_get(post : BytesView, gid : Int) -> String? {
  let version = glyph_name_read_u32_be(post, 0).unwrap_or(0)
  if version == 0x00010000 {
    if gid < 0 || gid >= glyph_name_post_standard_names.length() {
      return None
    }
    return Some(glyph_name_post_standard_names.at(gid))
  }
  // post v2.0: version(4) .. header(32) numberOfGlyphs(u16) glyphNameIndex[u16] stringData[]
  if version != 0x00020000 || post.length() < 34 {
    return None
  }
  let number_of_glyphs = glyph_name_read_u16_be(post, 32).unwrap_or(-1)
  if number_of_glyphs <= 0 || gid < 0 || gid >= number_of_glyphs {
    return None
  }
  let names_base = 34
  let names_end = names_base + number_of_glyphs * 2
  if names_end < names_base || names_end > post.length() {
    return None
  }
  let name_index = glyph_name_read_u16_be(post, names_base + gid * 2).unwrap_or(
    -1,
  )
  if name_index < 0 {
    return None
  }
  if name_index < glyph_name_post_standard_names.length() {
    return Some(glyph_name_post_standard_names.at(name_index))
  }
  let mut want = name_index - glyph_name_post_standard_names.length()
  let mut off = names_end
  while off < post.length() {
    let len = post.at(off).to_int()
    off = off + 1
    if len < 0 || off + len > post.length() {
      return None
    }
    if want == 0 {
      let bytes = post[off:off + len]
      return Some(glyph_name_truncate(glyph_name_latin1_string(bytes)))
    }
    want = want - 1
    off = off + len
  }
  None
}

///|
priv struct CffIndex1 {
  data : BytesView
  count : Int
  off_size : Int
  offsets_base : Int
  objects_base : Int
}

///|
fn cff_index1_read(data : BytesView) -> CffIndex1? {
  let count = glyph_name_read_u16_be(data, 0).unwrap_or(-1)
  if count < 0 {
    return None
  }
  if count == 0 {
    Some({ data, count: 0, off_size: 0, offsets_base: 2, objects_base: 2 })
  } else {
    if data.length() < 3 {
      return None
    }
    let off_size = data.at(2).to_int()
    if off_size <= 0 || off_size > 4 {
      return None
    }
    let offsets_base = 3
    let objects_base = offsets_base + (count + 1) * off_size
    if objects_base > data.length() {
      return None
    }
    Some({ data, count, off_size, offsets_base, objects_base })
  }
}

///|
fn cff_index1_offset(ix : CffIndex1, index : Int) -> Int? {
  if ix.count == 0 {
    if index == 0 {
      Some(0)
    } else {
      None
    }
  } else {
    if index < 0 || index > ix.count {
      return None
    }
    let pos = ix.offsets_base + index * ix.off_size
    if pos < 0 || pos + ix.off_size > ix.data.length() {
      return None
    }
    let mut v = 0
    for i in 0.. Int? {
  if ix.count == 0 {
    Some(2)
  } else {
    match cff_index1_offset(ix, ix.count) {
      None => None
      Some(end_off) => Some(ix.objects_base + end_off)
    }
  }
}

///|
fn cff_index1_get(ix : CffIndex1, index : Int) -> BytesView? {
  if index < 0 || index >= ix.count {
    return None
  }
  let start = cff_index1_offset(ix, index).unwrap_or(-1)
  let end = cff_index1_offset(ix, index + 1).unwrap_or(-1)
  let s = ix.objects_base + start
  let e = ix.objects_base + end
  if start < 0 || end < start || s < 0 || e > ix.data.length() {
    None
  } else {
    Some(ix.data[s:e])
  }
}

///|
priv struct CffDictOffsets {
  mut charset_offset : Int?
  mut charstrings_offset : Int?
}

///|
fn cff_dict_parse_offsets(data : BytesView) -> CffDictOffsets {
  // Minimal DICT parsing: only integer operands and ops 15 (charset), 17 (charstrings).
  let info = CffDictOffsets::{ charset_offset: None, charstrings_offset: None }
  let stack : Array[Int] = Array::new()
  let mut pos = 0
  while pos < data.length() {
    let b0 = data.at(pos).to_int()
    pos = pos + 1
    if b0 == 28 || b0 == 29 || (b0 >= 32 && b0 <= 254) {
      let parsed : (Int, Int)? = match b0 {
        28 =>
          match glyph_name_read_u16_be(data, pos) {
            None => None
            Some(u) =>
              Some((if u >= 0x8000 { u - 0x10000 } else { u }, pos + 2))
          }
        29 =>
          match glyph_name_read_u32_be(data, pos) {
            None => None
            Some(u) => Some((u.reinterpret_as_int(), pos + 4))
          }
        32..=246 => Some((b0 - 139, pos))
        247..=250 =>
          if pos >= data.length() {
            None
          } else {
            let b1 = data.at(pos).to_int()
            Some(((b0 - 247) * 256 + b1 + 108, pos + 1))
          }
        251..=254 =>
          if pos >= data.length() {
            None
          } else {
            let b1 = data.at(pos).to_int()
            Some((-(b0 - 251) * 256 - b1 - 108, pos + 1))
          }
        _ => None
      }
      match parsed {
        None => break
        Some((v, p)) => {
          stack.push(v)
          pos = p
        }
      }
      continue
    }
    let op = if b0 == 12 {
      if pos >= data.length() {
        break
      }
      let b1 = data.at(pos).to_int()
      pos = pos + 1
      1200 + b1
    } else {
      b0
    }
    match op {
      15 =>
        if stack.length() >= 1 {
          info.charset_offset = Some(stack.at(stack.length() - 1))
        }
      17 =>
        if stack.length() >= 1 {
          info.charstrings_offset = Some(stack.at(stack.length() - 1))
        }
      _ => ()
    }
    stack.clear()
  }
  info
}

///|
fn glyph_name_cff_sid(cff : BytesView, charset_off : Int, gid : Int) -> Int? {
  if gid < 0 || charset_off < 0 || charset_off >= cff.length() {
    return None
  }
  if gid == 0 {
    return Some(0)
  }
  let format = cff.at(charset_off).to_int()
  match format {
    0 => {
      let sid_pos = charset_off + 1 + (gid - 1) * 2
      glyph_name_read_u16_be(cff, sid_pos)
    }
    1 => {
      let mut pos = charset_off + 1
      let mut cur_gid = 1
      while cur_gid <= gid {
        let first = glyph_name_read_u16_be(cff, pos).unwrap_or(-1)
        let n_left = glyph_name_read_u8(cff, pos + 2).unwrap_or(-1)
        if first < 0 || n_left < 0 {
          return None
        }
        let end_gid = cur_gid + n_left
        if gid <= end_gid {
          return Some(first + (gid - cur_gid))
        }
        cur_gid = end_gid + 1
        pos = pos + 3
      }
      None
    }
    2 => {
      let mut pos = charset_off + 1
      let mut cur_gid = 1
      while cur_gid <= gid {
        let first = glyph_name_read_u16_be(cff, pos).unwrap_or(-1)
        let n_left = glyph_name_read_u16_be(cff, pos + 2).unwrap_or(-1)
        if first < 0 || n_left < 0 {
          return None
        }
        let end_gid = cur_gid + n_left
        if gid <= end_gid {
          return Some(first + (gid - cur_gid))
        }
        cur_gid = end_gid + 1
        pos = pos + 4
      }
      None
    }
    _ => None
  }
}

///|
fn glyph_name_cff_get(cff : BytesView, gid : Int) -> String? {
  if cff.length() < 4 {
    return None
  }
  let hdr_size = cff.at(2).to_int()
  if hdr_size < 4 || hdr_size > cff.length() {
    return None
  }
  let name_ix = cff_index1_read(cff[hdr_size:cff.length()]).unwrap_or({
    data: cff,
    count: 0,
    off_size: 0,
    offsets_base: 0,
    objects_base: 0,
  })
  let name_size = cff_index1_total_size(name_ix).unwrap_or(-1)
  if name_size < 0 {
    return None
  }
  let top_base = hdr_size + name_size
  let top_ix = cff_index1_read(cff[top_base:cff.length()]).unwrap_or({
    data: cff,
    count: 0,
    off_size: 0,
    offsets_base: 0,
    objects_base: 0,
  })
  let top_size = cff_index1_total_size(top_ix).unwrap_or(-1)
  if top_ix.count <= 0 || top_size < 0 {
    return None
  }
  let top_dict = cff_index1_get(top_ix, 0).unwrap_or(cff[0:0])
  let dict = cff_dict_parse_offsets(top_dict)
  let charset_off = dict.charset_offset.unwrap_or(-1)
  let charstrings_off = dict.charstrings_offset.unwrap_or(-1)
  if charset_off < 0 || charstrings_off < 0 || charstrings_off >= cff.length() {
    return None
  }
  // Skip String INDEX, but parse it to resolve custom SIDs.
  let string_base = top_base + top_size
  let str_ix = match cff_index1_read(cff[string_base:cff.length()]) {
    None => return None
    Some(ix) => ix
  }
  let str_size = match cff_index1_total_size(str_ix) {
    None => return None
    Some(v) => v
  }
  let global_base = string_base + str_size
  let global_ix = match cff_index1_read(cff[global_base:cff.length()]) {
    None => return None
    Some(ix) => ix
  }
  let global_size = match cff_index1_total_size(global_ix) {
    None => return None
    Some(v) => v
  }
  global_size |> ignore
  let char_ix = match cff_index1_read(cff[charstrings_off:cff.length()]) {
    None => return None
    Some(ix) => ix
  }
  let glyph_count = char_ix.count
  if gid < 0 || gid >= glyph_count {
    return None
  }
  let sid = match glyph_name_cff_sid(cff, charset_off, gid) {
    None => return None
    Some(v) => v
  }
  if sid < 0 {
    return None
  }
  if sid < glyph_name_cff_standard_strings.length() {
    return Some(glyph_name_truncate(glyph_name_cff_standard_strings.at(sid)))
  }
  let custom_idx = sid - glyph_name_cff_standard_strings.length()
  let bytes = match cff_index1_get(str_ix, custom_idx) {
    None => return None
    Some(v) => v
  }
  Some(glyph_name_truncate(glyph_name_latin1_string(bytes)))
}

///|
priv enum GlyphNamesInner {
  Post(BytesView, Int)
  Cff(BytesView, Int)
  Synthesized(Int)
}

///|
pub struct GlyphNames {
  priv inner : GlyphNamesInner
}

///|
pub fn GlyphNames::GlyphNames(font : FontRef) -> GlyphNames {
  let num_glyphs = glyph_name_read_maxp_num_glyphs(font)
  match font.table(GLYPH_TAG_POST) {
    None => ()
    Some(post) =>
      // Prefer `post` if it resolves at least one glyph name.
      if glyph_name_post_v2_get(post, 0) is Some(_) {
        return { inner: Post(post, num_glyphs) }
      }
  }
  match font.table(GLYPH_TAG_CFF) {
    None => ()
    Some(cff) =>
      if glyph_name_cff_get(cff, 0) is Some(_) {
        return { inner: Cff(cff, num_glyphs) }
      }
  }
  { inner: Synthesized(num_glyphs) }
}

///|
pub fn GlyphNames::source(self : GlyphNames) -> GlyphNameSource {
  match self.inner {
    Post(_, _) => Post
    Cff(_, _) => Cff
    Synthesized(_) => Synthesized
  }
}

///|
pub fn GlyphNames::num_glyphs(self : GlyphNames) -> Int {
  match self.inner {
    Post(_, n) | Cff(_, n) | Synthesized(n) => n
  }
}

///|
pub fn GlyphNames::get(self : GlyphNames, glyph_id : GlyphId) -> GlyphName? {
  let gid = glyph_id.to_uint64().to_int()
  if gid < 0 || gid >= self.num_glyphs() {
    return None
  }
  let name : String? = match self.inner {
    Post(post, _) => glyph_name_post_v2_get(post, gid)
    Cff(cff, _) => glyph_name_cff_get(cff, gid)
    Synthesized(_) => None
  }
  match name {
    None => Some(glyph_name_synthesize(glyph_id))
    Some(s) =>
      if s.is_empty() {
        Some(glyph_name_synthesize(glyph_id))
      } else {
        Some({ name: s, synthesized: false })
      }
  }
}

///|
pub fn FontRef::glyph_names(self : FontRef) -> GlyphNames {
  GlyphNames(self)
}