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

///|
let op_charstrings : Int = 0x11

///|
let op_encoding : Int = 0x10

///|
let op_private : Int = 0x12

///|
let op_subrs : Int = 0x13

///|
let op_charset : Int = 0x0F

///|
let op_ros : Int = 0x0C1E

///|
let op_fd_array : Int = 0x0C24

///|
let op_fd_select : Int = 0x0C25

///|
pub struct Cff1Table {
  data : Bytes
  name_index : CffIndex
  top_dict : CffDict
  string_index : CffIndex
  global_subrs : CffIndex
  charstrings : CffIndex
  private_dict : CffDict?
  local_subrs : CffIndex?
  charstrings_offset : Int
  private_offset : Int
  private_size : Int
  charset_offset : Int
  charset_map : Map[Int, Int]?
} derive(Eq, Show, ToJson)

///|
pub fn Cff1Table::parse(data : BytesView) -> Result[Cff1Table, CffError] {
  if data.length() < 4 {
    return Err(UnexpectedEof)
  }
  let header_size = data[2].to_int()
  if header_size < 4 || header_size > data.length() {
    return Err(InvalidFormat)
  }
  let mut pos = header_size
  let (name_index, next_pos) = match parse_cff1_index_at(data, pos) {
    Err(err) => return Err(err)
    Ok(value) => value
  }
  pos = next_pos
  let (top_dict_index, next_pos) = match parse_cff1_index_at(data, pos) {
    Err(err) => return Err(err)
    Ok(value) => value
  }
  pos = next_pos
  let (string_index, next_pos) = match parse_cff1_index_at(data, pos) {
    Err(err) => return Err(err)
    Ok(value) => value
  }
  pos = next_pos
  let (global_subrs, next_pos) = match parse_cff1_index_at(data, pos) {
    Err(err) => return Err(err)
    Ok(value) => value
  }
  pos = next_pos
  if top_dict_index.count <= 0 {
    return Err(InvalidFormat)
  }
  let top_bytes = match top_dict_index.item_bytes(0) {
    Err(err) => return Err(err)
    Ok(value) => value
  }
  let top_dict = match parse_cff_dict(top_bytes[:]) {
    Err(err) => return Err(err)
    Ok(value) => value
  }
  let charstrings_offset = match top_dict.get_int(op_charstrings) {
    None => return Err(InvalidFormat)
    Some(value) => value
  }
  let charset_offset = match top_dict.get_int(op_charset) {
    None => 0
    Some(value) => value
  }
  let private_size_opt = top_dict.get_int(op_private, index=0)
  let private_offset_opt = top_dict.get_int(op_private, index=1)
  let (private_size, private_offset) =
    match (private_size_opt, private_offset_opt) {
      (None, None) => (0, 0)
      (Some(size), Some(offset)) => (size, offset)
      _ => return Err(InvalidFormat)
    }
  let charstrings = match parse_cff1_index(data, charstrings_offset) {
    Err(err) => return Err(err)
    Ok(value) => value
  }
  let charset_map =
    if charset_offset > 2 {
      match parse_cff1_charset(data, charset_offset, charstrings.count) {
        Err(err) => return Err(err)
        Ok(value) => Some(value)
      }
    } else {
      None
    }
  let (private_dict, local_subrs) =
    if private_size > 0 {
      if private_offset < 0 || private_offset + private_size > data.length() {
        return Err(UnexpectedEof)
      }
      let priv_bytes = data[private_offset:private_offset + private_size]
      let priv_dict = match parse_cff_dict(priv_bytes) {
        Err(err) => return Err(err)
        Ok(value) => value
      }
      let local_subrs = match priv_dict.get_int(op_subrs) {
        None => None
        Some(subrs_offset) => {
          let subrs_base = private_offset + subrs_offset
          match parse_cff1_index(data, subrs_base) {
            Err(err) => return Err(err)
            Ok(value) => Some(value)
          }
        }
      }
      (Some(priv_dict), local_subrs)
    } else {
      (None, None)
    }
  Ok(Cff1Table::{
    data: data.to_bytes(),
    name_index,
    top_dict,
    string_index,
    global_subrs,
    charstrings,
    private_dict,
    local_subrs,
    charstrings_offset,
    private_offset,
    private_size,
    charset_offset,
    charset_map,
  })
}

///|
pub fn Cff1Table::glyph_bounds(
  self : Cff1Table,
  glyph : Int,
) -> Result[CffBounds?, CffError] {
  if glyph < 0 || glyph >= self.charstrings.count {
    return Err(InvalidFormat)
  }
  let bytes = match self.charstrings.item_bytes(glyph) {
    Err(err) => return Err(err)
    Ok(value) => value
  }
  let seac = Cff1SeacContext::{
    charstrings: self.charstrings,
    charset_offset: self.charset_offset,
    charset_map: self.charset_map,
    glyph_count: self.charstrings.count,
  }
  cff1_charstring_bounds(bytes[:], Some(self.global_subrs), self.local_subrs, seac)
}

///|
pub fn Cff1Table::glyph_count(self : Cff1Table) -> Int {
  self.charstrings.count
}

///|
pub fn Cff1Table::glyph_name(
  self : Cff1Table,
  glyph : Int,
) -> Result[String?, CffError] {
  if glyph < 0 || glyph >= self.charstrings.count {
    return Ok(None)
  }
  let gid_to_sid = match build_gid_to_sid_map(
    self.charset_offset,
    self.charset_map,
    self.charstrings.count,
  ) {
    Err(err) => return Err(err)
    Ok(value) => value
  }
  let sid = gid_to_sid[glyph]
  if sid < 0 {
    return Ok(None)
  }
  match cff1_standard_string(sid) {
    Some(value) => Ok(Some(value))
    None => {
      let standard_len = cff1_standard_strings_len()
      let index = sid - standard_len
      if index < 0 || index >= self.string_index.count {
        return Ok(None)
      }
      let bytes = match self.string_index.item_bytes(index) {
        Err(err) => return Err(err)
        Ok(value) => value
      }
      Ok(Some(@utf8.decode_lossy(bytes[:])))
    }
  }
}

///|
pub fn Cff1Table::glyph_from_name(
  self : Cff1Table,
  name : String,
) -> Result[Int?, CffError] {
  if name == "" {
    return Ok(None)
  }
  let sid =
    match cff1_standard_sid_for(name) {
      Some(value) => Some(value)
      None => {
        let standard_len = cff1_standard_strings_len()
        let mut found : Int? = None
        for i in 0.. return Err(err)
            Ok(value) => value
          }
          if @utf8.decode_lossy(bytes[:]) == name {
            found = Some(i + standard_len)
            break
          }
        }
        found
      }
    }
  match sid {
    None => Ok(None)
    Some(value) =>
      Ok(cff1_sid_to_gid(
        self.charset_offset,
        self.charset_map,
        value,
        self.charstrings.count,
      ))
  }
}

///|
pub fn Cff1Table::seac_components(
  self : Cff1Table,
  glyph : Int,
) -> Result[(Int, Int)?, CffError] {
  if glyph < 0 || glyph >= self.charstrings.count {
    return Err(InvalidFormat)
  }
  let bytes = match self.charstrings.item_bytes(glyph) {
    Err(err) => return Err(err)
    Ok(value) => value
  }
  let seac = Cff1SeacContext::{
    charstrings: self.charstrings,
    charset_offset: self.charset_offset,
    charset_map: self.charset_map,
    glyph_count: self.charstrings.count,
  }
  cff1_charstring_seac_components(
    bytes[:],
    Some(self.global_subrs),
    self.local_subrs,
    seac,
  )
}

fn clone_entries(entries : Map[Int, Array[CffNumber]]) -> Map[Int, Array[CffNumber]] {
  let next : Map[Int, Array[CffNumber]] = {}
  entries.eachi((_, key, value) => {
    next[key] = value.copy()
  })
  next
}

fn replace_entry(
  entries : Map[Int, Array[CffNumber]],
  op : Int,
  values : Array[CffNumber],
) -> Unit {
  entries[op] = values
}

fn remove_entry(entries : Map[Int, Array[CffNumber]], op : Int) -> Unit {
  match entries.get(op) {
    None => ()
    Some(_) => entries.remove(op)
  }
}

fn build_charset_bytes(
  glyphs : Array[Int],
  gid_to_sid : Array[Int],
) -> Result[Bytes, CffError] {
  let bytes : Array[Byte] = []
  bytes.push(0x00)
  if glyphs.length() <= 1 {
    return Ok(Bytes::from_array(bytes))
  }
  for i in 1..= gid_to_sid.length() {
      return Err(InvalidFormat)
    }
    let sid = gid_to_sid[gid]
    if sid < 0 || sid > 0xFFFF {
      return Err(InvalidFormat)
    }
    bytes.push(((sid >> 8) & 0xff).to_byte())
    bytes.push((sid & 0xff).to_byte())
  }
  Ok(Bytes::from_array(bytes))
}

fn build_index_items(index : CffIndex) -> Result[Array[Bytes], CffError] {
  let items : Array[Bytes] = []
  for i in 0.. return Err(err)
      Ok(value) => value
    }
    items.push(bytes)
  }
  Ok(items)
}

fn build_private_dict(
  private_dict : CffDict,
  local_subrs : CffIndex?,
) -> Result[(Bytes, Bytes?), CffError] {
  let entries = clone_entries(private_dict.entries_map())
  remove_entry(entries, op_subrs)
  match local_subrs {
    None => {
      let private_bytes = match write_cff_dict(entries) {
        Err(err) => return Err(err)
        Ok(value) => value
      }
      Ok((private_bytes, None))
    }
    Some(subrs) => {
      let subr_items = match build_index_items(subrs) {
        Err(err) => return Err(err)
        Ok(value) => value
      }
      let subr_index = match write_cff_index(subr_items, 2) {
        Err(err) => return Err(err)
        Ok(value) => value
      }
      let mut dict_bytes = match write_cff_dict(entries) {
        Err(err) => return Err(err)
        Ok(value) => value
      }
      let mut done = false
      let mut iter = 0
      while !done && iter < 5 {
        replace_entry(entries, op_subrs, [Int(dict_bytes.length())])
        let next = match write_cff_dict(entries) {
          Err(err) => return Err(err)
          Ok(value) => value
        }
        if next.length() == dict_bytes.length() {
          dict_bytes = next
          done = true
        } else {
          dict_bytes = next
        }
        iter = iter + 1
      }
      if !done {
        return Err(InvalidFormat)
      }
      Ok((dict_bytes, Some(subr_index)))
    }
  }
}

fn build_top_dict(
  base : Map[Int, Array[CffNumber]],
  charset_offset : Int,
  charstrings_offset : Int,
  private_bytes : Bytes?,
  private_offset : Int?,
  encoding_value : Int?,
) -> Result[Bytes, CffError] {
  let entries = clone_entries(base)
  replace_entry(entries, op_charset, [Int(charset_offset)])
  replace_entry(entries, op_charstrings, [Int(charstrings_offset)])
  match (private_bytes, private_offset) {
    (Some(bytes), Some(offset)) =>
      replace_entry(
        entries,
        op_private,
        [Int(bytes.length()), Int(offset)],
      )
    _ => remove_entry(entries, op_private)
  }
  match encoding_value {
    None => remove_entry(entries, op_encoding)
    Some(value) => replace_entry(entries, op_encoding, [Int(value)])
  }
  if entries.get(op_ros) is Some(_) {
    return Err(InvalidFormat)
  }
  if entries.get(op_fd_array) is Some(_) || entries.get(op_fd_select) is Some(_) {
    return Err(InvalidFormat)
  }
  write_cff_dict(entries)
}

///|
pub fn Cff1Table::subset(
  self : Cff1Table,
  glyphs : Array[Int],
) -> Result[Bytes, CffError] {
  if glyphs.is_empty() {
    return Err(InvalidFormat)
  }
  if glyphs[0] != 0 {
    return Err(InvalidFormat)
  }
  let gid_to_sid = match build_gid_to_sid_map(
    self.charset_offset,
    self.charset_map,
    self.charstrings.count,
  ) {
    Err(err) => return Err(err)
    Ok(value) => value
  }
  let charset_bytes = match build_charset_bytes(glyphs, gid_to_sid) {
    Err(err) => return Err(err)
    Ok(value) => value
  }
  let char_items : Array[Bytes] = []
  for gid in glyphs {
    let bytes = match self.charstrings.item_bytes(gid) {
      Err(err) => return Err(err)
      Ok(value) => value
    }
    char_items.push(bytes)
  }
  let charstrings_index = match write_cff_index(char_items, 2) {
    Err(err) => return Err(err)
    Ok(value) => value
  }
  let name_items = match build_index_items(self.name_index) {
    Err(err) => return Err(err)
    Ok(value) => value
  }
  let name_index = match write_cff_index(name_items, 2) {
    Err(err) => return Err(err)
    Ok(value) => value
  }
  let string_items = match build_index_items(self.string_index) {
    Err(err) => return Err(err)
    Ok(value) => value
  }
  let string_index = match write_cff_index(string_items, 2) {
    Err(err) => return Err(err)
    Ok(value) => value
  }
  let global_items = match build_index_items(self.global_subrs) {
    Err(err) => return Err(err)
    Ok(value) => value
  }
  let global_index = match write_cff_index(global_items, 2) {
    Err(err) => return Err(err)
    Ok(value) => value
  }
  let (private_bytes, local_subrs_index) =
    match self.private_dict {
      None => (None, None)
      Some(private_dict) =>
        match build_private_dict(private_dict, self.local_subrs) {
          Err(err) => return Err(err)
          Ok((priv_bytes, subrs_index)) => (Some(priv_bytes), subrs_index)
        }
    }
  let header_size = if self.data.length() >= 3 { self.data[2].to_int() } else { 0 }
  if header_size < 4 || header_size > self.data.length() {
    return Err(InvalidFormat)
  }
  let header_bytes = self.data[0:header_size].to_bytes()
  let mut top_dict_bytes = match write_cff_dict(self.top_dict.entries_map()) {
    Err(err) => return Err(err)
    Ok(value) => value
  }
  let mut top_index = match write_cff_index([top_dict_bytes], 2) {
    Err(err) => return Err(err)
    Ok(value) => value
  }
  let encoding_value =
    match self.top_dict.get_int(op_encoding) {
      None => None
      Some(value) =>
        if value == 0 || value == 1 { Some(value) } else { Some(0) }
    }
  let mut done = false
  let mut iter = 0
  while !done && iter < 5 {
    let base_offset =
      header_bytes.length() +
      name_index.length() +
      top_index.length() +
      string_index.length() +
      global_index.length()
    let charset_offset = base_offset
    let charstrings_offset = charset_offset + charset_bytes.length()
    let mut private_offset : Int? = None
    if private_bytes is Some(_) {
      private_offset = Some(charstrings_offset + charstrings_index.length())
    }
    let updated_top = match build_top_dict(
      self.top_dict.entries_map(),
      charset_offset,
      charstrings_offset,
      private_bytes,
      private_offset,
      encoding_value,
    ) {
      Err(err) => return Err(err)
      Ok(value) => value
    }
    let updated_index = match write_cff_index([updated_top], 2) {
      Err(err) => return Err(err)
      Ok(value) => value
    }
    if updated_index.length() == top_index.length() {
      top_dict_bytes = updated_top
      top_index = updated_index
      done = true
    } else {
      top_dict_bytes = updated_top
      top_index = updated_index
    }
    iter = iter + 1
  }
  if !done {
    return Err(InvalidFormat)
  }
  let bytes : Array[Byte] = []
  for b in header_bytes {
    bytes.push(b)
  }
  for b in name_index {
    bytes.push(b)
  }
  for b in top_index {
    bytes.push(b)
  }
  for b in string_index {
    bytes.push(b)
  }
  for b in global_index {
    bytes.push(b)
  }
  for b in charset_bytes {
    bytes.push(b)
  }
  for b in charstrings_index {
    bytes.push(b)
  }
  match private_bytes {
    None => ()
    Some(private_blob) => {
      for b in private_blob {
        bytes.push(b)
      }
      match local_subrs_index {
        None => ()
        Some(subrs) =>
          for b in subrs {
            bytes.push(b)
          }
      }
    }
  }
  Ok(Bytes::from_array(bytes))
}

fn parse_cff1_index_at(
  data : BytesView,
  offset : Int,
) -> Result[(CffIndex, Int), CffError] {
  let index = match parse_cff1_index(data, offset) {
    Err(err) => return Err(err)
    Ok(value) => value
  }
  Ok((index, offset + index.byte_len(2)))
}