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

///|
pub suberror SfntError {
  UnexpectedEof
  InvalidFormat
  TableNotFound
} derive(Eq, Show, ToJson)

///|
/// Table directory entry.
pub struct TableRecord {
  tag : @common.Tag
  offset : Int
  length : Int
} derive(Eq, Show, ToJson)

///|
/// SFNT font directory.
pub struct Sfnt {
  blob : @blob.Blob
  records : Array[TableRecord]
} derive(Show, ToJson)

///|
fn read_u16(data : BytesView, offset : Int) -> Result[UInt, SfntError] {
  if offset + 2 > data.length() {
    return Err(UnexpectedEof)
  }
  let b0 = data[offset].to_uint()
  let b1 = data[offset + 1].to_uint()
  Ok((b0 << 8) | b1)
}

///|
fn read_u24(data : BytesView, offset : Int) -> Result[UInt, SfntError] {
  if offset + 3 > data.length() {
    return Err(UnexpectedEof)
  }
  let b0 = data[offset].to_uint()
  let b1 = data[offset + 1].to_uint()
  let b2 = data[offset + 2].to_uint()
  Ok((b0 << 16) | (b1 << 8) | b2)
}

///|
fn read_u32(data : BytesView, offset : Int) -> Result[UInt, SfntError] {
  if offset + 4 > data.length() {
    return Err(UnexpectedEof)
  }
  let b0 = data[offset].to_uint()
  let b1 = data[offset + 1].to_uint()
  let b2 = data[offset + 2].to_uint()
  let b3 = data[offset + 3].to_uint()
  Ok((b0 << 24) | (b1 << 16) | (b2 << 8) | b3)
}

///|
fn u16_to_int(value : UInt) -> Int {
  let v = value.reinterpret_as_int()
  if v < 0 {
    v + 0x10000
  } else {
    v
  }
}

///|
fn read_u16_int(data : BytesView, offset : Int) -> Result[Int, SfntError] {
  match read_u16(data, offset) {
    Err(err) => Err(err)
    Ok(value) => Ok(u16_to_int(value))
  }
}

///|
fn read_u32_int(data : BytesView, offset : Int) -> Result[Int, SfntError] {
  match read_u32(data, offset) {
    Err(err) => Err(err)
    Ok(value) => {
      let v = value.reinterpret_as_int()
      if v < 0 {
        Err(InvalidFormat)
      } else {
        Ok(v)
      }
    }
  }
}

///|
fn read_i16(data : BytesView, offset : Int) -> Result[Int, SfntError] {
  match read_u16_int(data, offset) {
    Err(err) => Err(err)
    Ok(value) => if value >= 0x8000 { Ok(value - 0x10000) } else { Ok(value) }
  }
}

///|
/// Parse a SFNT directory from a blob at a byte offset.
pub fn Sfnt::parse_at(
  blob : @blob.Blob,
  base : Int,
) -> Result[Sfnt, SfntError] {
  if base < 0 {
    return Err(InvalidFormat)
  }
  let data = blob.as_view()
  let scaler = read_u32(data, base)
  match scaler {
    Err(err) => Err(err)
    Ok(_) => {
      let tables = read_u16_int(data, base + 4)
      match tables {
        Err(err) => Err(err)
        Ok(num_tables) => {
          let count = num_tables
          let records : Array[TableRecord] = []
          let mut offset = base + 12
          for _ in 0.. return Err(err)
              (_, Err(err)) => return Err(err)
              (Ok(table_offset), Ok(length)) =>
                records.push(TableRecord::{
                  tag,
                  offset: base + table_offset,
                  length,
                })
            }
            offset = offset + 16
          }
          Ok(Sfnt::{ blob, records })
        }
      }
    }
  }
}

///|
/// Parse a SFNT directory from a blob.
pub fn Sfnt::parse(blob : @blob.Blob) -> Result[Sfnt, SfntError] {
  Sfnt::parse_at(blob, 0)
}

///|
/// List all table records.
pub fn Sfnt::tables(self : Sfnt) -> ArrayView[TableRecord] {
  self.records[:]
}

///|
/// Reference a table blob by tag.
pub fn Sfnt::table(
  self : Sfnt,
  tag : @common.Tag,
) -> Result[@blob.Blob, SfntError] {
  for record in self.records {
    if record.tag == tag {
      return match self.blob.sub(record.offset, record.length) {
        Ok(blob) => Ok(blob)
        Err(_) => Err(UnexpectedEof)
      }
    }
  }
  Err(TableNotFound)
}