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

///|
/// Basic representation of an in-memory font resource.
///
/// Ported from `fontations/skrifa/src/font.rs` (Apache-2.0 OR MIT).
pub struct GlyphId {
  priv value : UInt16
}

///|
pub fn GlyphId::GlyphId(value : UInt16) -> GlyphId {
  { value, }
}

///|
pub fn GlyphId::default() -> GlyphId {
  GlyphId(0)
}

///|
pub fn GlyphId::notdef() -> GlyphId {
  GlyphId::default()
}

///|
pub fn GlyphId::to_u16(self : GlyphId) -> UInt16 {
  self.value
}

///|
pub fn GlyphId::to_int(self : GlyphId) -> Int {
  self.value.to_int()
}

///|
pub fn GlyphId::to_uint64(self : GlyphId) -> UInt64 {
  self.value.to_uint64()
}

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

///|
pub impl Show for GlyphId with fn output(self, logger) {
  logger.write_string(self.value.to_string())
}

///|
pub impl Debug for GlyphId with fn to_repr(self) {
  Repr::integer(self.value.to_string())
}

///|
pub impl Eq for GlyphId with fn equal(self, other) {
  self.value == other.value
}

///|
pub impl Compare for GlyphId with fn compare(self, other) {
  self.value.to_int() - other.value.to_int()
}

///|
const TAG_TTCF : UInt = 0x74746366

///|
const SFNT_1_0 : UInt = 0x00010000

///|
const SFNT_OTTO : UInt = 0x4F54544F

///|
const SFNT_TRUE : UInt = 0x74727565

///|
const SFNT_TYP1 : UInt = 0x74797031

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

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

///|
fn read_u32_be_int(data : Bytes, offset : Int) -> Int? {
  match read_u32_be(data, offset) {
    None => None
    Some(u) => Some(u.to_uint64().to_int())
  }
}

///|
fn is_supported_sfnt_version(sfnt_version : UInt) -> Bool {
  sfnt_version == SFNT_1_0 ||
  sfnt_version == SFNT_OTTO ||
  sfnt_version == SFNT_TRUE ||
  sfnt_version == SFNT_TYP1
}

///|
fn validate_sfnt(data : Bytes, base : Int) -> Bool {
  match read_u32_be(data, base) {
    None => false
    Some(sfnt_version) => {
      if !is_supported_sfnt_version(sfnt_version) {
        return false
      }
      match read_u16_be(data, base + 4) {
        None => false
        Some(num_tables) =>
          // offset table (12 bytes) + table records (16 bytes each)
          if num_tables < 0 {
            false
          } else {
            let dir_end = base + 12 + num_tables * 16
            dir_end <= data.length()
          }
      }
    }
  }
}

///|
pub struct FontRef {
  data : Bytes
  index : UInt
}

///|
pub fn FontRef::from_bytes(data : Bytes) -> FontRef? {
  match read_u32_be(data, 0) {
    None => None
    Some(TAG_TTCF) => FontRef::from_index(data, 0)
    Some(_) => FontRef::from_index(data, 0)
  }
}

///|
pub fn FontRef::from_index(data : Bytes, index : UInt) -> FontRef? {
  match read_u32_be(data, 0) {
    None => None
    Some(TAG_TTCF) => {
      let idx = index.to_uint64().to_int()
      match read_u32_be_int(data, 8) {
        None => None
        Some(num_fonts) => {
          if idx < 0 || idx >= num_fonts {
            return None
          }
          let offsets_base = 12
          let off_pos = offsets_base + idx * 4
          match read_u32_be_int(data, off_pos) {
            None => None
            Some(sfnt_off) =>
              if sfnt_off < 0 || sfnt_off >= data.length() {
                None
              } else if validate_sfnt(data, sfnt_off) {
                Some({ data, index })
              } else {
                None
              }
          }
        }
      }
    }
    Some(_) =>
      if index != 0 {
        None
      } else if validate_sfnt(data, 0) {
        Some({ data, index: 0 })
      } else {
        None
      }
  }
}

///|
pub fn FontRef::data(self : FontRef) -> Bytes {
  self.data
}

///|
pub fn FontRef::index(self : FontRef) -> UInt {
  self.index
}

///|
fn FontRef::sfnt_offset(self : FontRef) -> Int? {
  match read_u32_be(self.data, 0) {
    None => None
    Some(TAG_TTCF) => {
      let idx = self.index.to_uint64().to_int()
      match read_u32_be_int(self.data, 8) {
        None => None
        Some(num_fonts) =>
          if idx < 0 || idx >= num_fonts {
            None
          } else {
            read_u32_be_int(self.data, 12 + idx * 4)
          }
      }
    }
    Some(_) => Some(0)
  }
}

///|
/// Returns the table data as a `BytesView` (slice into the original font bytes).
///
/// `tag` is a big-endian 4-byte OpenType table tag (e.g. `0x636D6170` for "cmap").
pub fn FontRef::table(self : FontRef, tag : UInt) -> BytesView? {
  match self.sfnt_offset() {
    None => None
    Some(base) => {
      if !validate_sfnt(self.data, base) {
        return None
      }
      match read_u16_be(self.data, base + 4) {
        None => None
        Some(num_tables) => {
          let dir_base = base + 12
          for i in 0.. return None
              Some(rec_tag) =>
                if rec_tag == tag {
                  let off = match read_u32_be_int(self.data, rec + 8) {
                    None => return None
                    Some(v) => v
                  }
                  let len = match read_u32_be_int(self.data, rec + 12) {
                    None => return None
                    Some(v) => v
                  }
                  // In TTC, table record offsets are absolute from the start of
                  // the collection file (not relative to the per-font base).
                  let start = off
                  let end = start + len
                  if off < 0 || len < 0 || start < 0 || end > self.data.length() {
                    return None
                  }
                  return Some(self.data[start:end])
                }
            }
          }
          None
        }
      }
    }
  }
}

///|
/// Tag-based table lookup adapter for parity with upstream `TableProvider`.
pub fn FontRef::table_tag(self : FontRef, tag : Tag) -> BytesView? {
  self.table(tag.to_uint())
}