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

///|
/// Bitmap strikes (sbix/CBDT/EBDT).
///
/// This is an incremental port of `fontations/skrifa/src/bitmap.rs`.
/// For now it supports format detection and strike enumeration (ppem only).
const TAG_SBIX : UInt = 0x73626978 // "sbix"

///|
const TAG_CBLC : UInt = 0x43424C43 // "CBLC"

///|
const TAG_CBDT : UInt = 0x43424454 // "CBDT"

///|
const TAG_EBLC : UInt = 0x45424C43 // "EBLC"

///|
const TAG_EBDT : UInt = 0x45424454 // "EBDT"

///|
const BITMAP_TAG_MAXP : UInt = 0x6D617870 // "maxp"

///|
fn bitmap_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 bitmap_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 bitmap_read_u32_be_int(view : BytesView, offset : Int) -> Int? {
  match bitmap_read_u32_be(view, offset) {
    None => None
    Some(u) => Some(u.to_uint64().to_int())
  }
}

///|
fn bitmap_read_i16_be(view : BytesView, offset : Int) -> Int? {
  match bitmap_read_u16_be(view, offset) {
    None => None
    Some(u) => if u >= 0x8000 { Some(u - 0x10000) } else { Some(u) }
  }
}

///|
pub(all) enum BitmapFormat {
  Sbix
  Cbdt
  Ebdt
}

///|
pub(all) enum BitmapImageFormat {
  Png
  Jpeg
  Unknown(UInt)
}

///|
pub struct BitmapGlyph {
  priv format : BitmapImageFormat
  priv data : BytesView
  priv ppem_x : Int
  priv ppem_y : Int
  priv origin_x : Int
  priv origin_y : Int
  priv width : Int?
  priv height : Int?
}

///|
pub fn BitmapGlyph::format(self : BitmapGlyph) -> BitmapImageFormat {
  self.format
}

///|
pub fn BitmapGlyph::data(self : BitmapGlyph) -> BytesView {
  self.data
}

///|
pub fn BitmapGlyph::ppem_x(self : BitmapGlyph) -> Int {
  self.ppem_x
}

///|
pub fn BitmapGlyph::ppem_y(self : BitmapGlyph) -> Int {
  self.ppem_y
}

///|
pub fn BitmapGlyph::origin_x(self : BitmapGlyph) -> Int {
  self.origin_x
}

///|
pub fn BitmapGlyph::origin_y(self : BitmapGlyph) -> Int {
  self.origin_y
}

///|
pub fn BitmapGlyph::width(self : BitmapGlyph) -> Int? {
  self.width
}

///|
pub fn BitmapGlyph::height(self : BitmapGlyph) -> Int? {
  self.height
}

///|
pub struct BitmapStrike {
  priv format : BitmapFormat
  priv ppem_x : Int
  priv ppem_y : Int
  priv sbix : BytesView?
  priv sbix_strike_off : Int
  priv num_glyphs : Int
  priv bdt_loc : BytesView?
  priv bdt_data : BytesView?
  priv bdt_is_color : Bool
  priv bdt_index_subtable_array_off : Int
  priv bdt_number_of_index_subtables : Int
  priv bdt_start_gid : Int
  priv bdt_end_gid : Int
  priv bdt_bit_depth : Int
}

///|
pub fn BitmapStrike::format(self : BitmapStrike) -> BitmapFormat {
  self.format
}

///|
pub fn BitmapStrike::ppem_x(self : BitmapStrike) -> Int {
  self.ppem_x
}

///|
pub fn BitmapStrike::ppem_y(self : BitmapStrike) -> Int {
  self.ppem_y
}

///|
/// Returns the bitmap glyph for `gid` if present.
///
/// Currently supports `sbix` and a minimal subset of `CBDT/CBLC` and `EBDT/EBLC`.
pub fn BitmapStrike::get(self : BitmapStrike, gid : GlyphId) -> BitmapGlyph? {
  match self.format {
    Sbix =>
      match self.sbix {
        None => None
        Some(sbix) =>
          bitmap_sbix_strike_glyph(
            sbix,
            self.sbix_strike_off,
            self.num_glyphs,
            self.ppem_x,
            gid,
          )
      }
    _ => bitmap_bdt_glyph(self, gid)
  }
}

///|
priv enum BitmapStrikesKind {
  NoStrikes
  Sbix(BytesView)
  Cbdt(BytesView, BytesView)
  Ebdt(BytesView, BytesView)
}

///|
pub struct BitmapStrikes {
  priv kind : BitmapStrikesKind
  priv num_glyphs : Int
}

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

///|
pub fn BitmapStrikes::BitmapStrikes(font : FontRef) -> BitmapStrikes {
  let num_glyphs = bitmap_num_glyphs(font)
  match BitmapStrikes::with_format(font, Sbix) {
    None =>
      match BitmapStrikes::with_format(font, Cbdt) {
        None =>
          match BitmapStrikes::with_format(font, Ebdt) {
            None => { kind: NoStrikes, num_glyphs }
            Some(v) => v
          }
        Some(v) => v
      }
    Some(v) => v
  }
}

///|
pub fn BitmapStrikes::with_format(
  font : FontRef,
  format : BitmapFormat,
) -> BitmapStrikes? {
  let num_glyphs = bitmap_num_glyphs(font)
  match format {
    Sbix =>
      match font.table(TAG_SBIX) {
        None => None
        Some(sbix) => Some({ kind: Sbix(sbix), num_glyphs })
      }
    Cbdt =>
      match (font.table(TAG_CBLC), font.table(TAG_CBDT)) {
        (Some(cblc), Some(cbdt)) => Some({ kind: Cbdt(cblc, cbdt), num_glyphs })
        _ => None
      }
    Ebdt =>
      match (font.table(TAG_EBLC), font.table(TAG_EBDT)) {
        (Some(eblc), Some(ebdt)) => Some({ kind: Ebdt(eblc, ebdt), num_glyphs })
        _ => None
      }
  }
}

///|
pub fn BitmapStrikes::format(self : BitmapStrikes) -> BitmapFormat? {
  match self.kind {
    NoStrikes => None
    Sbix(_) => Some(Sbix)
    Cbdt(_, _) => Some(Cbdt)
    Ebdt(_, _) => Some(Ebdt)
  }
}

///|
pub fn BitmapStrikes::len(self : BitmapStrikes) -> Int {
  match self.kind {
    NoStrikes => 0
    Sbix(sbix) => bitmap_read_u32_be_int(sbix, 4).unwrap_or(0)
    Cbdt(loc, _) | Ebdt(loc, _) => bitmap_read_u32_be_int(loc, 4).unwrap_or(0)
  }
}

///|
pub fn BitmapStrikes::is_empty(self : BitmapStrikes) -> Bool {
  self.len() == 0
}

///|
pub fn BitmapStrikes::iter(self : BitmapStrikes) -> Array[BitmapStrike] {
  let out : Array[BitmapStrike] = Array::new(capacity=self.len())
  for i in 0.. BitmapGlyph? {
  let target_ppem = size.ppem().unwrap_or(3.402823e38)
  let mut best : BitmapGlyph? = None
  let mut best_size = 0.0
  for strike in self.iter() {
    let strike_size = strike.ppem_y().to_double()
    if best is Some(_) {
      if (strike_size >= target_ppem && strike_size < best_size) ||
        (best_size < target_ppem && strike_size > best_size) {
        if strike.get(glyph_id) is Some(glyph) {
          best = Some(glyph)
          best_size = strike_size
        }
      }
    } else if strike.get(glyph_id) is Some(glyph) {
      best = Some(glyph)
      best_size = strike_size
    }
  }
  best
}

///|
pub fn BitmapStrikes::get(self : BitmapStrikes, index : Int) -> BitmapStrike? {
  if index < 0 || index >= self.len() {
    return None
  }
  match self.kind {
    NoStrikes => None
    Sbix(sbix) => {
      // sbix header: version u16, flags u16, numStrikes u32, strikeOffsets[numStrikes] u32
      let strike_off = bitmap_read_u32_be_int(sbix, 8 + index * 4).unwrap_or(-1)
      if strike_off < 0 || strike_off + 4 > sbix.length() {
        return None
      }
      let ppem = bitmap_read_u16_be(sbix, strike_off).unwrap_or(-1)
      if ppem < 0 {
        None
      } else {
        Some({
          format: Sbix,
          ppem_x: ppem,
          ppem_y: ppem,
          sbix: Some(sbix),
          sbix_strike_off: strike_off,
          num_glyphs: self.num_glyphs,
          bdt_loc: None,
          bdt_data: None,
          bdt_is_color: false,
          bdt_index_subtable_array_off: -1,
          bdt_number_of_index_subtables: -1,
          bdt_start_gid: -1,
          bdt_end_gid: -1,
          bdt_bit_depth: -1,
        })
      }
    }
    Cbdt(loc, data) => bitmap_loc_get_size(loc, data, true, index, Cbdt)
    Ebdt(loc, data) => bitmap_loc_get_size(loc, data, false, index, Ebdt)
  }
}

///|
fn bitmap_loc_get_size(
  loc : BytesView,
  data : BytesView,
  is_color : Bool,
  index : Int,
  format : BitmapFormat,
) -> BitmapStrike? {
  // EBLC/CBLC: major u16, minor u16, numSizes u32, BitmapSizeTable[numSizes]
  // BitmapSizeTable has ppemX/ppemY at offsets 44/45.
  let base = 8 + index * 48
  if base < 0 || base + 48 > loc.length() {
    return None
  }
  let start_gid = bitmap_read_u16_be(loc, base + 40).unwrap_or(-1)
  let end_gid = bitmap_read_u16_be(loc, base + 42).unwrap_or(-1)
  let index_subtable_array_off = bitmap_read_u32_be_int(loc, base).unwrap_or(-1)
  let num_subtables = bitmap_read_u32_be_int(loc, base + 8).unwrap_or(-1)
  let bit_depth = loc.at(base + 46).to_int()
  let ppem_x = loc.at(base + 44).to_int()
  let ppem_y = loc.at(base + 45).to_int()
  Some({
    format,
    ppem_x,
    ppem_y,
    sbix: None,
    sbix_strike_off: -1,
    num_glyphs: 0,
    bdt_loc: Some(loc),
    bdt_data: Some(data),
    bdt_is_color: is_color,
    bdt_index_subtable_array_off: index_subtable_array_off,
    bdt_number_of_index_subtables: num_subtables,
    bdt_start_gid: start_gid,
    bdt_end_gid: end_gid,
    bdt_bit_depth: bit_depth,
  })
}

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

///|
fn bitmap_read_i8(view : BytesView, offset : Int) -> Int? {
  match bitmap_read_u8(view, offset) {
    None => None
    Some(u) => if u >= 0x80 { Some(u - 0x100) } else { Some(u) }
  }
}

///|
fn bitmap_small_metrics(
  view : BytesView,
  offset : Int,
) -> (Int, Int, Int, Int, Int)? {
  let h = bitmap_read_u8(view, offset).unwrap_or(-1)
  let w = bitmap_read_u8(view, offset + 1).unwrap_or(-1)
  let bx = bitmap_read_i8(view, offset + 2).unwrap_or(0)
  let by = bitmap_read_i8(view, offset + 3).unwrap_or(0)
  let adv = bitmap_read_u8(view, offset + 4).unwrap_or(0)
  if h < 0 || w < 0 {
    None
  } else {
    Some((w, h, bx, by, adv))
  }
}

///|
fn bitmap_div_ceil(n : Int, d : Int) -> Int {
  if d == 0 {
    0
  } else {
    (n + d - 1) / d
  }
}

///|
fn bitmap_bdt_location(
  loc : BytesView,
  strike : BitmapStrike,
  gid : GlyphId,
) -> (Int, Int, Int)? {
  let gid_i = gid.to_uint64().to_int()
  if gid_i < strike.bdt_start_gid || gid_i > strike.bdt_end_gid {
    return None
  }
  let array_off = strike.bdt_index_subtable_array_off
  let num = strike.bdt_number_of_index_subtables
  if array_off < 0 || num <= 0 {
    return None
  }
  let array_end = array_off + num * 8
  if array_end < array_off || array_end > loc.length() {
    return None
  }
  for i in 0.. last {
      continue
    }
    let sub_off = bitmap_read_u32_be_int(loc, rec + 4).unwrap_or(-1)
    if sub_off < 0 || sub_off >= loc.length() {
      return None
    }
    let index_format = bitmap_read_u16_be(loc, sub_off).unwrap_or(-1)
    let image_format = bitmap_read_u16_be(loc, sub_off + 2).unwrap_or(-1)
    let image_data_off = bitmap_read_u32_be_int(loc, sub_off + 4).unwrap_or(-1)
    if index_format != 1 || image_format < 0 || image_data_off < 0 {
      return None
    }
    let count = last - first + 2
    let offsets_off = sub_off + 8
    let offsets_end = offsets_off + count * 4
    if offsets_end < offsets_off || offsets_end > loc.length() {
      return None
    }
    let rel_ix = gid_i - first
    let o0 = bitmap_read_u32_be_int(loc, offsets_off + rel_ix * 4).unwrap_or(-1)
    let o1 = bitmap_read_u32_be_int(loc, offsets_off + (rel_ix + 1) * 4).unwrap_or(
      -1,
    )
    if o0 < 0 || o1 < o0 {
      return None
    }
    let start = image_data_off + o0
    let end = image_data_off + o1
    if start < 0 || end < start {
      return None
    }
    return Some((image_format, start, end - start))
  }
  None
}

///|
fn bitmap_bdt_glyph(strike : BitmapStrike, gid : GlyphId) -> BitmapGlyph? {
  match (strike.bdt_loc, strike.bdt_data) {
    (Some(loc), Some(data)) =>
      match bitmap_bdt_location(loc, strike, gid) {
        None => None
        Some((image_format, start, size)) => {
          let end = start + size
          if start < 0 || end < start || end > data.length() {
            return None
          }
          let record = data[start:end]
          // CBDT: small metrics + PNG data
          if strike.bdt_is_color && image_format == 17 {
            if record.length() < 9 {
              return None
            }
            let (w, h, bx, by, _) = match bitmap_small_metrics(record, 0) {
              None => return None
              Some(v) => v
            }
            let png_len = bitmap_read_u32_be_int(record, 5).unwrap_or(-1)
            if png_len < 0 || 9 + png_len > record.length() {
              return None
            }
            let start = 9
            let end = start + png_len
            let png = record[start:end]
            Some({
              format: Png,
              data: png,
              ppem_x: strike.ppem_x,
              ppem_y: strike.ppem_y,
              origin_x: bx,
              origin_y: by,
              width: Some(w),
              height: Some(h),
            })
          } else if !strike.bdt_is_color &&
            (image_format == 1 || image_format == 2) {
            // EBDT: small metrics + bitmap data (byte/bit aligned)
            if record.length() < 5 {
              return None
            }
            let (w, h, bx, by, _) = match bitmap_small_metrics(record, 0) {
              None => return None
              Some(v) => v
            }
            let bit_depth = strike.bdt_bit_depth
            let data_len = if image_format == 1 {
              let pitch = bitmap_div_ceil(w * bit_depth, 8)
              pitch * h
            } else {
              bitmap_div_ceil(w * h * bit_depth, 8)
            }
            if data_len < 0 || 5 + data_len > record.length() {
              return None
            }
            let start = 5
            let end = start + data_len
            let bits = record[start:end]
            Some({
              format: Unknown(image_format.reinterpret_as_uint()),
              data: bits,
              ppem_x: strike.ppem_x,
              ppem_y: strike.ppem_y,
              origin_x: bx,
              origin_y: by,
              width: Some(w),
              height: Some(h),
            })
          } else {
            None
          }
        }
      }
    _ => None
  }
}

///|
fn bitmap_sbix_strike_glyph(
  sbix : BytesView,
  strike_off : Int,
  num_glyphs : Int,
  ppem : Int,
  gid : GlyphId,
) -> BitmapGlyph? {
  let idx = gid.to_uint64().to_int()
  if num_glyphs <= 0 || idx < 0 || idx >= num_glyphs {
    return None
  }
  // Strike header: ppem u16, resolution u16, glyphDataOffsets[numGlyphs+1] u32
  let offsets_off = strike_off + 4
  let offsets_len = (num_glyphs + 1) * 4
  let offsets_end = offsets_off + offsets_len
  if offsets_off < 0 || offsets_end < offsets_off || offsets_end > sbix.length() {
    return None
  }
  let off0 = bitmap_read_u32_be_int(sbix, offsets_off + idx * 4).unwrap_or(-1)
  let off1 = bitmap_read_u32_be_int(sbix, offsets_off + (idx + 1) * 4).unwrap_or(
    -1,
  )
  if off0 < 0 || off1 < off0 {
    return None
  }
  if off0 == off1 {
    return None
  }
  let glyph_off = strike_off + off0
  let glyph_end = strike_off + off1
  if glyph_off < 0 || glyph_end < glyph_off || glyph_end > sbix.length() {
    return None
  }
  let origin_x = bitmap_read_i16_be(sbix, glyph_off).unwrap_or(0)
  let origin_y = bitmap_read_i16_be(sbix, glyph_off + 2).unwrap_or(0)
  let graphic_type = bitmap_read_u32_be(sbix, glyph_off + 4).unwrap_or(0)
  let data_off = glyph_off + 8
  if data_off > glyph_end {
    return None
  }
  let start = data_off
  let end = glyph_end
  let data = sbix[start:end]
  let format = if graphic_type == 0x706E6720 { // "png "
    BitmapImageFormat::Png
  } else if graphic_type == 0x6A706720 { // "jpg "
    Jpeg
  } else {
    Unknown(graphic_type)
  }
  let mut width : Int? = None
  let mut height : Int? = None
  if format is Png {
    if data.length() >= 24 {
      let w = bitmap_read_u32_be_int(data, 16).unwrap_or(-1)
      let h = bitmap_read_u32_be_int(data, 20).unwrap_or(-1)
      if w >= 0 && h >= 0 {
        width = Some(w)
        height = Some(h)
      }
    }
  }
  Some({
    format,
    data,
    ppem_x: ppem,
    ppem_y: ppem,
    origin_x,
    origin_y,
    width,
    height,
  })
}

///|
pub fn FontRef::bitmap_strikes(self : FontRef) -> BitmapStrikes {
  BitmapStrikes(self)
}