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

///|
/// Localized strings (OpenType `name` table).
///
/// Ported from `fontations/skrifa/src/string.rs` (Apache-2.0 OR MIT).
const TAG_NAME : UInt = 0x6E616D65 // "name"

///|
/// OpenType name identifier (NameID).
///
/// This is a `UInt16` in the OpenType spec.
pub type StringId = UInt16

///|
/// Copyright notice.
pub const STRING_ID_COPYRIGHT_NOTICE : StringId = 0

///|
pub const STRING_ID_FAMILY_NAME : StringId = 1

///|
pub const STRING_ID_SUBFAMILY_NAME : StringId = 2

///|
/// Unique font identifier.
pub const STRING_ID_UNIQUE_ID : StringId = 3

///|
/// Full font name.
pub const STRING_ID_FULL_NAME : StringId = 4

///|
/// Version string.
pub const STRING_ID_VERSION_STRING : StringId = 5

///|
/// PostScript name.
pub const STRING_ID_POSTSCRIPT_NAME : StringId = 6

///|
/// Trademark.
pub const STRING_ID_TRADEMARK : StringId = 7

///|
/// Manufacturer name.
pub const STRING_ID_MANUFACTURER : StringId = 8

///|
/// Designer name.
pub const STRING_ID_DESIGNER : StringId = 9

///|
/// Description.
pub const STRING_ID_DESCRIPTION : StringId = 10

///|
/// Vendor URL.
pub const STRING_ID_VENDOR_URL : StringId = 11

///|
/// Designer URL.
pub const STRING_ID_DESIGNER_URL : StringId = 12

///|
/// License description.
pub const STRING_ID_LICENSE_DESCRIPTION : StringId = 13

///|
/// License URL.
pub const STRING_ID_LICENSE_URL : StringId = 14

///|
/// Typographic family name.
pub const STRING_ID_TYPOGRAPHIC_FAMILY_NAME : StringId = 16

///|
/// Typographic subfamily name.
pub const STRING_ID_TYPOGRAPHIC_SUBFAMILY_NAME : StringId = 17

///|
/// Compatible full name (Macintosh only).
pub const STRING_ID_COMPATIBLE_FULL_NAME : StringId = 18

///|
/// Sample text.
pub const STRING_ID_SAMPLE_TEXT : StringId = 19

///|
/// PostScript CID findfont name.
pub const STRING_ID_POSTSCRIPT_CID_NAME : StringId = 20

///|
/// WWS family name.
pub const STRING_ID_WWS_FAMILY_NAME : StringId = 21

///|
/// WWS subfamily name.
pub const STRING_ID_WWS_SUBFAMILY_NAME : StringId = 22

///|
/// Light background palette label.
pub const STRING_ID_LIGHT_BACKGROUND_PALETTE : StringId = 23

///|
/// Dark background palette label.
pub const STRING_ID_DARK_BACKGROUND_PALETTE : StringId = 24

///|
/// Variations PostScript name prefix.
pub const STRING_ID_VARIATIONS_POSTSCRIPT_NAME_PREFIX : StringId = 25

///|
/// The last value that is explicitly reserved for standard names.
pub const STRING_ID_LAST_RESERVED_NAME_ID : StringId = 255

///|
/// The last value that is available for font-specific names.
pub const STRING_ID_LAST_ALLOWED_NAME_ID : StringId = 32767

///|
pub fn string_id_is_reserved(id : StringId) -> Bool {
  id.to_uint64() <= STRING_ID_LAST_RESERVED_NAME_ID.to_uint64()
}

///|
pub fn string_id_checked_add(id : StringId, rhs : UInt16) -> StringId? {
  let sum = id.to_uint64().to_int() + rhs.to_uint64().to_int()
  if sum < 0 || sum > STRING_ID_LAST_ALLOWED_NAME_ID.to_int() {
    None
  } else {
    Some(sum.to_uint16())
  }
}

///|
/// Returns the set of predefined name identifiers according to the OpenType spec.
///
/// This matches `fontations/font-types::NameId::predefined()`: IDs `0..15` and
/// `16..=25` (note: ID 15 is not assigned).
pub fn string_id_predefined() -> Array[StringId] {
  let out : Array[StringId] = Array::new()
  for i in 0..<15 {
    out.push(i.to_uint16())
  }
  for i in 16..<26 {
    out.push(i.to_uint16())
  }
  out
}

///|
fn string_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 string_decode_utf16be(view : BytesView) -> String? {
  let len = view.length()
  if len < 0 || (len & 1) != 0 {
    return None
  }
  let chars : Array[Char] = Array::new()
  let mut i = 0
  while i + 1 < len {
    let u = string_read_u16_be(view, i).unwrap_or(-1)
    if u < 0 {
      return None
    }
    if u >= 0xD800 && u <= 0xDBFF {
      if i + 3 >= len {
        return None
      }
      let u2 = string_read_u16_be(view, i + 2).unwrap_or(-1)
      if u2 < 0xDC00 || u2 > 0xDFFF {
        return None
      }
      let hi = u - 0xD800
      let lo = u2 - 0xDC00
      let code_point = 0x10000 + (hi << 10) + lo
      if code_point > 0x10FFFF {
        return None
      }
      chars.push(code_point.unsafe_to_char())
      i = i + 4
    } else if u >= 0xDC00 && u <= 0xDFFF {
      return None
    } else {
      chars.push(u.unsafe_to_char())
      i = i + 2
    }
  }
  Some(String::from_array(chars.op_as_view()))
}

///|
fn string_mac_roman_code_point(b : Int) -> Int {
  if b >= 0 && b <= 0x7F {
    b
  } else {
    match b {
      0x80 => 0x00C4
      0x81 => 0x00C5
      0x82 => 0x00C7
      0x83 => 0x00C9
      0x84 => 0x00D1
      0x85 => 0x00D6
      0x86 => 0x00DC
      0x87 => 0x00E1
      0x88 => 0x00E0
      0x89 => 0x00E2
      0x8A => 0x00E4
      0x8B => 0x00E3
      0x8C => 0x00E5
      0x8D => 0x00E7
      0x8E => 0x00E9
      0x8F => 0x00E8
      0x90 => 0x00EA
      0x91 => 0x00EB
      0x92 => 0x00ED
      0x93 => 0x00EC
      0x94 => 0x00EE
      0x95 => 0x00EF
      0x96 => 0x00F1
      0x97 => 0x00F3
      0x98 => 0x00F2
      0x99 => 0x00F4
      0x9A => 0x00F6
      0x9B => 0x00F5
      0x9C => 0x00FA
      0x9D => 0x00F9
      0x9E => 0x00FB
      0x9F => 0x00FC
      0xA0 => 0x2020
      0xA1 => 0x00B0
      0xA2 => 0x00A2
      0xA3 => 0x00A3
      0xA4 => 0x00A7
      0xA5 => 0x2022
      0xA6 => 0x00B6
      0xA7 => 0x00DF
      0xA8 => 0x00AE
      0xA9 => 0x00A9
      0xAA => 0x2122
      0xAB => 0x00B4
      0xAC => 0x00A8
      0xAD => 0x2260
      0xAE => 0x00C6
      0xAF => 0x00D8
      0xB0 => 0x221E
      0xB1 => 0x00B1
      0xB2 => 0x2264
      0xB3 => 0x2265
      0xB4 => 0x00A5
      0xB5 => 0x00B5
      0xB6 => 0x2202
      0xB7 => 0x2211
      0xB8 => 0x220F
      0xB9 => 0x03C0
      0xBA => 0x222B
      0xBB => 0x00AA
      0xBC => 0x00BA
      0xBD => 0x03A9
      0xBE => 0x00E6
      0xBF => 0x00F8
      0xC0 => 0x00BF
      0xC1 => 0x00A1
      0xC2 => 0x00AC
      0xC3 => 0x221A
      0xC4 => 0x0192
      0xC5 => 0x2248
      0xC6 => 0x2206
      0xC7 => 0x00AB
      0xC8 => 0x00BB
      0xC9 => 0x2026
      0xCA => 0x00A0
      0xCB => 0x00C0
      0xCC => 0x00C3
      0xCD => 0x00D5
      0xCE => 0x0152
      0xCF => 0x0153
      0xD0 => 0x2013
      0xD1 => 0x2014
      0xD2 => 0x201C
      0xD3 => 0x201D
      0xD4 => 0x2018
      0xD5 => 0x2019
      0xD6 => 0x00F7
      0xD7 => 0x25CA
      0xD8 => 0x00FF
      0xD9 => 0x0178
      0xDA => 0x2044
      0xDB => 0x20AC
      0xDC => 0x2039
      0xDD => 0x203A
      0xDE => 0xFB01
      0xDF => 0xFB02
      0xE0 => 0x2021
      0xE1 => 0x00B7
      0xE2 => 0x201A
      0xE3 => 0x201E
      0xE4 => 0x2030
      0xE5 => 0x00C2
      0xE6 => 0x00CA
      0xE7 => 0x00C1
      0xE8 => 0x00CB
      0xE9 => 0x00C8
      0xEA => 0x00CD
      0xEB => 0x00CE
      0xEC => 0x00CF
      0xED => 0x00CC
      0xEE => 0x00D3
      0xEF => 0x00D4
      0xF0 => 0xF8FF
      0xF1 => 0x00D2
      0xF2 => 0x00DA
      0xF3 => 0x00DB
      0xF4 => 0x00D9
      0xF5 => 0x0131
      0xF6 => 0x02C6
      0xF7 => 0x02DC
      0xF8 => 0x00AF
      0xF9 => 0x02D8
      0xFA => 0x02D9
      0xFB => 0x02DA
      0xFC => 0x00B8
      0xFD => 0x02DD
      0xFE => 0x02DB
      0xFF => 0x02C7
      _ => 0xFFFD
    }
  }
}

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

///|
fn string_decode_name_string(
  platform_id : Int,
  encoding_id : Int,
  view : BytesView,
) -> String? {
  match platform_id {
    // Unicode
    0 => string_decode_utf16be(view)
    // Macintosh (MacRoman is the most common encoding).
    1 =>
      if encoding_id == 0 {
        Some(string_decode_bytes_mac_roman(view))
      } else {
        None
      }
    // Windows
    3 => string_decode_utf16be(view)
    _ => None
  }
}

///|
fn string_is_ascii(s : String) -> Bool {
  for ch in s {
    if !ch.is_ascii() {
      return false
    }
  }
  true
}

///|
fn string_language_id_to_bcp47(language_id : Int) -> String? {
  // Borrowed from Skia (`SkOTTable_name.cpp`) via skrifa-reference.
  match language_id {
    0x0000 => Some("en")
    0x0001 => Some("fr")
    0x0002 => Some("de")
    0x0003 => Some("it")
    0x0004 => Some("nl")
    0x0005 => Some("sv")
    0x0006 => Some("es")
    0x0007 => Some("da")
    0x0008 => Some("pt")
    0x0009 => Some("nb")
    0x000A => Some("he")
    0x000B => Some("ja")
    0x000C => Some("ar")
    0x000D => Some("fi")
    0x000E => Some("el")
    0x000F => Some("is")
    0x0010 => Some("mt")
    0x0011 => Some("tr")
    0x0012 => Some("hr")
    0x0013 => Some("zh-Hant")
    0x0014 => Some("ur")
    0x0015 => Some("hi")
    0x0016 => Some("th")
    0x0017 => Some("ko")
    0x0018 => Some("lt")
    0x0019 => Some("pl")
    0x001A => Some("hu")
    0x001B => Some("et")
    0x001C => Some("lv")
    0x001D => Some("se")
    0x001E => Some("fo")
    0x001F => Some("fa")
    0x0020 => Some("ru")
    0x0021 => Some("zh-Hans")
    0x0022 => Some("nl")
    0x0023 => Some("ga")
    0x0024 => Some("sq")
    0x0025 => Some("ro")
    0x0026 => Some("cs")
    0x0027 => Some("sk")
    0x0028 => Some("sl")
    0x0029 => Some("yi")
    0x002A => Some("sr")
    0x002B => Some("mk")
    0x002C => Some("bg")
    0x002D => Some("uk")
    0x002E => Some("be")
    0x002F => Some("uz")
    0x0030 => Some("kk")
    0x0031 => Some("az-Cyrl")
    0x0032 => Some("az-Arab")
    0x0033 => Some("hy")
    0x0034 => Some("ka")
    0x0035 => Some("mo")
    0x0036 => Some("ky")
    0x0037 => Some("tg")
    0x0038 => Some("tk")
    0x0039 => Some("mn-Mong")
    0x003A => Some("mn-Cyrl")
    0x003B => Some("ps")
    0x003C => Some("ku")
    0x003D => Some("ks")
    0x003E => Some("sd")
    0x003F => Some("bo")
    0x0040 => Some("ne")
    0x0041 => Some("sa")
    0x0042 => Some("mr")
    0x0043 => Some("bn")
    0x0044 => Some("as")
    0x0045 => Some("gu")
    0x0046 => Some("pa")
    0x0047 => Some("or")
    0x0048 => Some("ml")
    0x0049 => Some("kn")
    0x004A => Some("ta")
    0x004B => Some("te")
    0x004C => Some("si")
    0x004D => Some("my")
    0x004E => Some("km")
    0x004F => Some("lo")
    0x0050 => Some("vi")
    0x0051 => Some("id")
    0x0052 => Some("tl")
    0x0053 => Some("ms-Latn")
    0x0054 => Some("ms-Arab")
    0x0055 => Some("am")
    0x0056 => Some("ti")
    0x0057 => Some("om")
    0x0058 => Some("so")
    0x0059 => Some("sw")
    0x005A => Some("rw")
    0x005B => Some("rn")
    0x005C => Some("ny")
    0x005D => Some("mg")
    0x005E => Some("eo")
    0x0080 => Some("cy")
    0x0081 => Some("eu")
    0x0082 => Some("ca")
    0x0083 => Some("la")
    0x0084 => Some("qu")
    0x0085 => Some("gn")
    0x0086 => Some("ay")
    0x0087 => Some("tt")
    0x0088 => Some("ug")
    0x0089 => Some("dz")
    0x008A => Some("jv-Latn")
    0x008B => Some("su-Latn")
    0x008C => Some("gl")
    0x008D => Some("af")
    0x008E => Some("br")
    0x008F => Some("iu")
    0x0090 => Some("gd")
    0x0091 => Some("gv")
    0x0092 => Some("ga")
    0x0093 => Some("to")
    0x0094 => Some("el")
    0x0095 => Some("kl")
    0x0096 => Some("az-Latn")
    0x0097 => Some("nn")
    0x0401 => Some("ar-SA")
    0x0402 => Some("bg-BG")
    0x0403 => Some("ca-ES")
    0x0404 => Some("zh-TW")
    0x0405 => Some("cs-CZ")
    0x0406 => Some("da-DK")
    0x0407 => Some("de-DE")
    0x0408 => Some("el-GR")
    0x0409 => Some("en-US")
    0x040A => Some("es-ES_tradnl")
    0x040B => Some("fi-FI")
    0x040C => Some("fr-FR")
    0x040D => Some("he-IL")
    0x040E => Some("hu-HU")
    0x040F => Some("is-IS")
    0x0410 => Some("it-IT")
    0x0411 => Some("ja-JP")
    0x0412 => Some("ko-KR")
    0x0413 => Some("nl-NL")
    0x0414 => Some("nb-NO")
    0x0415 => Some("pl-PL")
    0x0416 => Some("pt-BR")
    0x0417 => Some("rm-CH")
    0x0418 => Some("ro-RO")
    0x0419 => Some("ru-RU")
    0x041A => Some("hr-HR")
    0x041B => Some("sk-SK")
    0x041C => Some("sq-AL")
    0x041D => Some("sv-SE")
    0x041E => Some("th-TH")
    0x041F => Some("tr-TR")
    0x0420 => Some("ur-PK")
    0x0421 => Some("id-ID")
    0x0422 => Some("uk-UA")
    0x0423 => Some("be-BY")
    0x0424 => Some("sl-SI")
    0x0425 => Some("et-EE")
    0x0426 => Some("lv-LV")
    0x0427 => Some("lt-LT")
    0x0428 => Some("tg-Cyrl-TJ")
    0x0429 => Some("fa-IR")
    0x042A => Some("vi-VN")
    0x042B => Some("hy-AM")
    0x042C => Some("az-Latn-AZ")
    0x042D => Some("eu-ES")
    0x042E => Some("hsb-DE")
    0x042F => Some("mk-MK")
    0x0432 => Some("tn-ZA")
    0x0434 => Some("xh-ZA")
    0x0435 => Some("zu-ZA")
    0x0436 => Some("af-ZA")
    0x0437 => Some("ka-GE")
    0x0438 => Some("fo-FO")
    0x0439 => Some("hi-IN")
    0x043A => Some("mt-MT")
    0x043B => Some("se-NO")
    0x043E => Some("ms-MY")
    0x043F => Some("kk-KZ")
    0x0440 => Some("ky-KG")
    0x0441 => Some("sw-KE")
    0x0442 => Some("tk-TM")
    0x0443 => Some("uz-Latn-UZ")
    0x0444 => Some("tt-RU")
    0x0445 => Some("bn-IN")
    0x0446 => Some("pa-IN")
    0x0447 => Some("gu-IN")
    0x0448 => Some("or-IN")
    0x0449 => Some("ta-IN")
    0x044A => Some("te-IN")
    0x044B => Some("kn-IN")
    0x044C => Some("ml-IN")
    0x044D => Some("as-IN")
    0x044E => Some("mr-IN")
    0x044F => Some("sa-IN")
    0x0450 => Some("mn-Cyrl")
    0x0451 => Some("bo-CN")
    0x0452 => Some("cy-GB")
    0x0453 => Some("km-KH")
    0x0454 => Some("lo-LA")
    0x0456 => Some("gl-ES")
    0x0457 => Some("kok-IN")
    0x045A => Some("syr-SY")
    0x045B => Some("si-LK")
    0x045D => Some("iu-Cans-CA")
    0x045E => Some("am-ET")
    0x0461 => Some("ne-NP")
    0x0462 => Some("fy-NL")
    0x0463 => Some("ps-AF")
    0x0464 => Some("fil-PH")
    0x0465 => Some("dv-MV")
    0x0468 => Some("ha-Latn-NG")
    0x046A => Some("yo-NG")
    0x046B => Some("quz-BO")
    0x046C => Some("nso-ZA")
    0x046D => Some("ba-RU")
    0x046E => Some("lb-LU")
    0x046F => Some("kl-GL")
    0x0470 => Some("ig-NG")
    0x0478 => Some("ii-CN")
    0x047A => Some("arn-CL")
    0x047C => Some("moh-CA")
    0x047E => Some("br-FR")
    0x0480 => Some("ug-CN")
    0x0481 => Some("mi-NZ")
    0x0482 => Some("oc-FR")
    0x0483 => Some("co-FR")
    0x0484 => Some("gsw-FR")
    0x0485 => Some("sah-RU")
    0x0486 => Some("qut-GT")
    0x0487 => Some("rw-RW")
    0x0488 => Some("wo-SN")
    0x048C => Some("prs-AF")
    0x0491 => Some("gd-GB")
    0x0801 => Some("ar-IQ")
    0x0804 => Some("zh-Hans")
    0x0807 => Some("de-CH")
    0x0809 => Some("en-GB")
    0x080A => Some("es-MX")
    0x080C => Some("fr-BE")
    0x0810 => Some("it-CH")
    0x0813 => Some("nl-BE")
    0x0814 => Some("nn-NO")
    0x0816 => Some("pt-PT")
    0x081A => Some("sr-Latn-CS")
    0x081D => Some("sv-FI")
    0x082C => Some("az-Cyrl-AZ")
    0x082E => Some("dsb-DE")
    0x083B => Some("se-SE")
    0x083C => Some("ga-IE")
    0x083E => Some("ms-BN")
    0x0843 => Some("uz-Cyrl-UZ")
    0x0845 => Some("bn-BD")
    0x0850 => Some("mn-Mong-CN")
    0x085D => Some("iu-Latn-CA")
    0x085F => Some("tzm-Latn-DZ")
    0x086B => Some("quz-EC")
    0x0C01 => Some("ar-EG")
    0x0C04 => Some("zh-Hant")
    0x0C07 => Some("de-AT")
    0x0C09 => Some("en-AU")
    0x0C0A => Some("es-ES")
    0x0C0C => Some("fr-CA")
    0x0C1A => Some("sr-Cyrl-CS")
    0x0C3B => Some("se-FI")
    0x0C6B => Some("quz-PE")
    0x1001 => Some("ar-LY")
    0x1004 => Some("zh-SG")
    0x1007 => Some("de-LU")
    0x1009 => Some("en-CA")
    0x100A => Some("es-GT")
    0x100C => Some("fr-CH")
    0x101A => Some("hr-BA")
    0x103B => Some("smj-NO")
    0x1401 => Some("ar-DZ")
    0x1404 => Some("zh-MO")
    0x1407 => Some("de-LI")
    0x1409 => Some("en-NZ")
    0x140A => Some("es-CR")
    0x140C => Some("fr-LU")
    0x141A => Some("bs-Latn-BA")
    0x143B => Some("smj-SE")
    0x1801 => Some("ar-MA")
    0x1809 => Some("en-IE")
    0x180A => Some("es-PA")
    0x180C => Some("fr-MC")
    0x181A => Some("sr-Latn-BA")
    0x183B => Some("sma-NO")
    0x1C01 => Some("ar-TN")
    0x1C09 => Some("en-ZA")
    0x1C0A => Some("es-DO")
    0x1C1A => Some("sr-Cyrl-BA")
    0x1C3B => Some("sma-SE")
    0x2001 => Some("ar-OM")
    0x2009 => Some("en-JM")
    0x200A => Some("es-VE")
    0x201A => Some("bs-Cyrl-BA")
    0x203B => Some("sms-FI")
    0x2401 => Some("ar-YE")
    0x2409 => Some("en-029")
    0x240A => Some("es-CO")
    0x241A => Some("sr-Latn-RS")
    0x243B => Some("smn-FI")
    0x2801 => Some("ar-SY")
    0x2809 => Some("en-BZ")
    0x280A => Some("es-PE")
    0x281A => Some("sr-Cyrl-RS")
    0x2C01 => Some("ar-JO")
    0x2C09 => Some("en-TT")
    0x2C0A => Some("es-AR")
    0x2C1A => Some("sr-Latn-ME")
    0x3001 => Some("ar-LB")
    0x3009 => Some("en-ZW")
    0x300A => Some("es-EC")
    0x301A => Some("sr-Cyrl-ME")
    0x3401 => Some("ar-KW")
    0x3409 => Some("en-PH")
    0x340A => Some("es-CL")
    0x3801 => Some("ar-AE")
    0x380A => Some("es-UY")
    0x3C01 => Some("ar-BH")
    0x3C0A => Some("es-PY")
    0x4001 => Some("ar-QA")
    0x4009 => Some("en-IN")
    0x400A => Some("es-BO")
    0x4409 => Some("en-MY")
    0x440A => Some("es-SV")
    0x4809 => Some("en-SG")
    0x480A => Some("es-HN")
    0x4C0A => Some("es-NI")
    0x500A => Some("es-PR")
    0x540A => Some("es-US")
    _ => None
  }
}

///|

///|
pub struct LocalizedString {
  priv language : String?
  priv value : String
}

///|
pub fn LocalizedString::language(self : LocalizedString) -> String? {
  self.language
}

///|
pub fn LocalizedString::chars(self : LocalizedString) -> Array[Char] {
  let out : Array[Char] = Array::new()
  for ch in self.value {
    out.push(ch)
  }
  out
}

///|
pub fn LocalizedString::to_string(self : LocalizedString) -> String {
  self.value
}

///|
pub struct LocalizedStrings {
  priv id : StringId
  priv strings : Array[LocalizedString]
}

///|
pub fn LocalizedStrings::id(self : LocalizedStrings) -> StringId {
  self.id
}

///|
pub fn LocalizedStrings::strings(
  self : LocalizedStrings,
) -> ArrayView[LocalizedString] {
  self.strings.op_as_view()
}

///|
pub fn LocalizedStrings::english_or_first(
  self : LocalizedStrings,
) -> LocalizedString? {
  let mut best_rank = -1
  let mut best : LocalizedString? = None
  let n = self.strings.length()
  for i in 0..
        if lang == "en-US" {
          return Some(s)
        } else if lang == "en" {
          2
        } else if i == 0 {
          0
        } else {
          continue
        }
      None => 1
    }
    if rank > best_rank {
      best_rank = rank
      best = Some(s)
    }
  }
  best
}

///|
pub fn LocalizedStrings::LocalizedStrings(
  font : FontRef,
  id : StringId,
) -> LocalizedStrings {
  FontRef::localized_strings(font, id)
}

///|
pub fn FontRef::localized_strings(
  self : FontRef,
  id : StringId,
) -> LocalizedStrings {
  let name = match self.table(TAG_NAME) {
    None => return { id, strings: Array::new() }
    Some(v) => v
  }
  let format = string_read_u16_be(name, 0).unwrap_or(-1)
  let count = string_read_u16_be(name, 2).unwrap_or(-1)
  let string_offset = string_read_u16_be(name, 4).unwrap_or(-1)
  if format < 0 || count < 0 || string_offset < 0 {
    return { id, strings: Array::new() }
  }
  let records_base = 6
  let records_end = records_base + count * 12
  if records_end < records_base || records_end > name.length() {
    return { id, strings: Array::new() }
  }
  let lang_tags : Array[(Int, Int)] = Array::new()
  if format == 1 {
    if records_end + 2 > name.length() {
      return { id, strings: Array::new() }
    }
    let tag_count = string_read_u16_be(name, records_end).unwrap_or(-1)
    if tag_count < 0 {
      return { id, strings: Array::new() }
    }
    let tags_base = records_end + 2
    let tags_end = tags_base + tag_count * 4
    if tags_end < tags_base || tags_end > name.length() {
      return { id, strings: Array::new() }
    }
    for i in 0.. name.length() {
    return { id, strings: Array::new() }
  }
  let strings : Array[LocalizedString] = Array::new()
  for i in 0.. name.length() {
      continue
    }
    let view = name[start:end]
    let value = match
      string_decode_name_string(platform_id, encoding_id, view) {
      None => continue
      Some(s) => s
    }
    let language = if format == 1 && language_id >= 0x8000 {
      let index = language_id - 0x8000
      if index < 0 || index >= lang_tags.length() {
        None
      } else {
        let (tlen, toff) = lang_tags.at(index)
        let start = string_offset + toff
        let end = start + tlen
        if start < 0 || end < start || end > name.length() {
          None
        } else {
          match string_decode_utf16be(name[start:end]) {
            None => None
            Some(tag) => if string_is_ascii(tag) { Some(tag) } else { None }
          }
        }
      }
    } else {
      match platform_id {
        1 | 3 => string_language_id_to_bcp47(language_id)
        _ => None
      }
    }
    strings.push({ language, value })
  }
  { id, strings }
}