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

///|
pub struct GlyphCacheKey {
  glyph_id : Int
  subpixel_x : Int
  subpixel_y : Int
  font_weight : Int
  flags : UInt
}

///|
pub fn GlyphCacheKey::new(
  glyph_id : Int,
  subpixel_x : Int,
  subpixel_y : Int,
  font_weight : Int,
  flags : UInt,
) -> GlyphCacheKey {
  GlyphCacheKey::{ glyph_id, subpixel_x, subpixel_y, font_weight, flags }
}

///|
fn glyph_cache_key_equals(a : GlyphCacheKey, b : GlyphCacheKey) -> Bool {
  a.glyph_id == b.glyph_id &&
  a.subpixel_x == b.subpixel_x &&
  a.subpixel_y == b.subpixel_y &&
  a.font_weight == b.font_weight &&
  a.flags == b.flags
}

///|
pub struct GlyphRaster {
  glyph_id : Int
  size : @math.UVec2
  offset : @math.IVec2
  pixels_rgba8 : Bytes
}

///|
priv struct SwashFontCacheEntry {
  font_id : Int
  font : @swash.FontRef
  charmap : @swash.Charmap
}

///|
priv struct SwashFontCache {
  entries : Array[SwashFontCacheEntry]
}

///|
let swash_font_cache : Ref[SwashFontCache] = Ref::new(SwashFontCache::{
  entries: [],
})

///|
let swash_scale_context : Ref[@swash_scale.ScaleContext] = Ref::new(
  @swash_scale.ScaleContext::new(),
)

///|
let cosmic_swash_cache : Ref[@cosmic.SwashCache] = Ref::new(
  @cosmic.SwashCache::new(),
)

///|
fn swash_get_font(
  font_handle : @asset.Handle[@asset.Font],
) -> SwashFontCacheEntry? {
  let entries = swash_font_cache.val.entries
  let n = entries.length()
  for i in 0.. Float {
  let entry_opt = swash_get_font(font)
  if entry_opt is None {
    return 0.0F
  }
  let entry = if entry_opt is Some(v) { v } else { return 0.0F }
  let gid = entry.charmap.map(codepoint.reinterpret_as_uint()).to_int()
  let glyph_metrics = entry.font
    .glyph_metrics([].op_as_view())
    .scale(font_size.to_double())
  Float::from_double(glyph_metrics.advance_width(gid.to_uint16()))
}

///|
pub fn font_rasterize_glyph(
  font : @asset.Handle[@asset.Font],
  font_size : Float,
  codepoint : Int,
  smoothing : FontSmoothing,
) -> GlyphRaster? {
  let entry_opt = swash_get_font(font)
  if entry_opt is None {
    return None
  }
  let entry = if entry_opt is Some(v) { v } else { return None }
  let gid = entry.charmap.map(codepoint.reinterpret_as_uint())
  if gid.to_int() == 0 {
    return None
  }
  font_rasterize_glyph_id(font, font_size, gid.to_int(), smoothing)
}

///|
/// Rasterize a glyph by its glyph ID (already shaped).
///
/// This is required for complex shaping (ligatures, marks) where mapping from
/// codepoint -> glyph_id is not sufficient.
pub fn font_rasterize_glyph_id(
  font : @asset.Handle[@asset.Font],
  font_size : Float,
  glyph_id : Int,
  smoothing : FontSmoothing,
) -> GlyphRaster? {
  // Default to the "zero bin" for subpixel placement.
  font_rasterize_glyph_id_subpixel(font, font_size, glyph_id, smoothing, 0, 0)
}

///|
/// Rasterize a glyph with a subpixel cache key (cosmic-text compatible).
///
/// `subpixel_x/subpixel_y` are bin indices in [0..3], matching
/// `cosmic-text`/`moon_cosmic` (`Zero/One/Two/Three` → 0/1/2/3).
pub fn font_rasterize_glyph_id_subpixel(
  font : @asset.Handle[@asset.Font],
  font_size : Float,
  glyph_id : Int,
  smoothing : FontSmoothing,
  subpixel_x : Int,
  subpixel_y : Int,
) -> GlyphRaster? {
  fn subpixel_bin_as_double(bin : Int) -> Double {
    match bin {
      1 => 0.25
      2 => 0.5
      3 => 0.75
      _ => 0.0
    }
  }

  if glyph_id <= 0 || glyph_id > 0xFFFF {
    return None
  }
  let entry_opt = swash_get_font(font)
  if entry_opt is None {
    return None
  }
  let entry = if entry_opt is Some(v) { v } else { return None }
  let gid = glyph_id.to_uint16()
  let hint = match smoothing {
    FontSmoothing::None => false
    FontSmoothing::AntiAliased => true
  }
  let scaler = swash_scale_context.val
    .builder(entry.font)
    .size(font_size.to_double())
    .hint(hint)
    .build()
  let render = @swash_scale.Render::new([@swash_scale.Source::Outline]).offset(
    @moon_zeno.Vector::new(
      subpixel_bin_as_double(subpixel_x),
      subpixel_bin_as_double(subpixel_y),
    ),
  )
  let img_opt = render.render(scaler, gid)
  if img_opt is None {
    return None
  }
  let img = if img_opt is Some(v) { v } else { return None }
  // `placement()` comes from moon_zeno; we keep the explicit type so the
  // dependency is tracked and fields are accessible.
  let placement : @moon_zeno.Placement = img.placement()
  let width = placement.width.reinterpret_as_int()
  let height = placement.height.reinterpret_as_int()
  if width <= 0 || height <= 0 {
    return None
  }
  let pixel_count = width * height
  fn byte_at(bytes : Bytes, index : Int) -> Int {
    match bytes.get(index) {
      Some(v) => v.to_int() & 0xFF
      None => 0
    }
  }

  fn alpha_for_smoothing(alpha : Int, smoothing : FontSmoothing) -> Int {
    match smoothing {
      FontSmoothing::None => if alpha > 127 { 255 } else { 0 }
      FontSmoothing::AntiAliased => alpha
    }
  }

  // Bevy parity:
  // - Mask: output straight-alpha white glyphs ([255,255,255,a]).
  // - SubpixelMask: be conservative, collapse to grayscale alpha via max(R,G,B).
  // - Color: pass through RGBA.
  let src = img.data()
  let pixels = match img.content() {
    @swash_scale.Content::Mask => {
      if src.length() < pixel_count {
        return None
      }
      Bytes::makei(pixel_count * 4, i => {
        let pixel_index = i / 4
        let channel = i % 4
        let mask_alpha = alpha_for_smoothing(
          byte_at(src, pixel_index),
          smoothing,
        )
        if channel == 3 {
          mask_alpha.to_byte()
        } else {
          (255).to_byte()
        }
      })
    }
    @swash_scale.Content::SubpixelMask => {
      if src.length() < pixel_count * 4 {
        return None
      }
      Bytes::makei(pixel_count * 4, i => {
        let pixel_index = i / 4
        let channel = i % 4
        let base = pixel_index * 4
        let r = byte_at(src, base)
        let g = byte_at(src, base + 1)
        let b = byte_at(src, base + 2)
        let max_rg = if r > g { r } else { g }
        let coverage = if max_rg > b { max_rg } else { b }
        let alpha = alpha_for_smoothing(coverage, smoothing)
        if channel == 3 {
          alpha.to_byte()
        } else {
          (255).to_byte()
        }
      })
    }
    @swash_scale.Content::Color => {
      if src.length() < pixel_count * 4 {
        return None
      }
      src
    }
  }
  Some(GlyphRaster::{
    glyph_id: gid.to_int(),
    size: @math.UVec2::new(width, height),
    offset: @math.IVec2::new(placement.left, placement.top),
    pixels_rgba8: pixels,
  })
}

///|
/// Rasterize a glyph directly from `cosmic` physical cache key (Bevy parity).
///
/// This follows Bevy's `physical_glyph.cache_key -> swash_cache.get_image_uncached`
/// flow and avoids rebuilding a partial key from `layout_glyph`.
pub fn font_rasterize_cache_key(
  font_system : @cosmic.FontSystem,
  cache_key : @cosmic.CacheKey,
  smoothing : FontSmoothing,
) -> GlyphRaster? {
  let image_opt = cosmic_swash_cache.val.get_image_uncached(
    font_system, cache_key,
  )
  if image_opt is None {
    return None
  }
  let image = if image_opt is Some(v) { v } else { return None }
  let placement = image.placement()
  let width = placement.width.reinterpret_as_int()
  let height = placement.height.reinterpret_as_int()
  if width <= 0 || height <= 0 {
    return None
  }
  let pixel_count = width * height
  let src = image.data()
  fn byte_at(bytes : Bytes, index : Int) -> Int {
    match bytes.get(index) {
      Some(v) => v.to_int() & 0xFF
      None => 0
    }
  }

  fn alpha_for_smoothing(alpha : Int, smoothing : FontSmoothing) -> Int {
    match smoothing {
      FontSmoothing::None => if alpha > 127 { 255 } else { 0 }
      FontSmoothing::AntiAliased => alpha
    }
  }

  let pixels = match image.content() {
    @swash_scale.Content::Mask => {
      if src.length() < pixel_count {
        return None
      }
      Bytes::makei(pixel_count * 4, i => {
        let pixel_index = i / 4
        let channel = i % 4
        let alpha = alpha_for_smoothing(byte_at(src, pixel_index), smoothing)
        if channel == 3 {
          alpha.to_byte()
        } else {
          (255).to_byte()
        }
      })
    }
    @swash_scale.Content::SubpixelMask => {
      if src.length() < pixel_count * 4 {
        return None
      }
      Bytes::makei(pixel_count * 4, i => {
        let pixel_index = i / 4
        let channel = i % 4
        let base = pixel_index * 4
        let r = byte_at(src, base)
        let g = byte_at(src, base + 1)
        let b = byte_at(src, base + 2)
        let max_rg = if r > g { r } else { g }
        let coverage = if max_rg > b { max_rg } else { b }
        let alpha = alpha_for_smoothing(coverage, smoothing)
        if channel == 3 {
          alpha.to_byte()
        } else {
          (255).to_byte()
        }
      })
    }
    @swash_scale.Content::Color => {
      if src.length() < pixel_count * 4 {
        return None
      }
      src
    }
  }
  Some(GlyphRaster::{
    glyph_id: cache_key.glyph_id,
    size: @math.UVec2::new(width, height),
    offset: @math.IVec2::new(placement.left, placement.top),
    pixels_rgba8: pixels,
  })
}

///|
pub struct FontAtlas {
  dynamic_texture_atlas_builder : @asset.DynamicTextureAtlasBuilder
  glyph_locations : Array[(GlyphCacheKey, GlyphAtlasLocation)]
  texture_atlas : @asset.Handle[@asset.TextureAtlasLayout]
  texture : @asset.Handle[@asset.Image]
}

///|
pub fn FontAtlas::new(
  size : @math.UVec2,
  font_smoothing : FontSmoothing,
) -> FontAtlas {
  let nearest = match font_smoothing {
    FontSmoothing::None => true
    FontSmoothing::AntiAliased => false
  }
  let texture = @asset.asset_create_dynamic_texture(size, nearest)
  let texture_atlas_layout = @asset.TextureAtlasLayout::new_empty(size)
  let builder = @asset.DynamicTextureAtlasBuilder::new(size, 1)
  let texture_atlas = @asset.asset_add_texture_atlas_layout(
    texture_atlas_layout,
  )
  FontAtlas::{
    dynamic_texture_atlas_builder: builder,
    glyph_locations: [],
    texture_atlas,
    texture,
  }
}

///|
pub fn FontAtlas::get_glyph_index(
  self : FontAtlas,
  cache_key : GlyphCacheKey,
) -> GlyphAtlasLocation? {
  let count = self.glyph_locations.length()
  for i in 0.. Bool {
  self.get_glyph_index(cache_key) is Some(_)
}

///|
pub fn FontAtlas::add_glyph(
  self : FontAtlas,
  cache_key : GlyphCacheKey,
  raster : GlyphRaster,
) -> Bool {
  let layout = if @asset.asset_get_texture_atlas_layout(self.texture_atlas)
    is Some(value) {
    value
  } else {
    return false
  }
  let allocation = self.dynamic_texture_atlas_builder.add_texture(
    layout,
    raster.size,
  )
  if allocation is None {
    return false
  }
  let index = if allocation is Some(value) { value.0 } else { return false }
  let rect = if allocation is Some(value) { value.1 } else { return false }
  @asset.asset_set_texture_atlas_layout(self.texture_atlas, layout) |> ignore
  @asset.asset_update_texture_region_rgba8(
    self.texture,
    rect,
    raster.pixels_rgba8,
  )
  self.glyph_locations.push(
    (
      cache_key,
      GlyphAtlasLocation::{ glyph_index: index, offset: raster.offset },
    ),
  )
  true
}

///|
pub fn get_glyph_atlas_info(
  atlases : Array[FontAtlas],
  cache_key : GlyphCacheKey,
) -> GlyphAtlasInfo? {
  let count = atlases.length()
  for i in 0.. GlyphAtlasInfo? {
  let count = font_atlases.length()
  for i in 0.. raster.size.y {
    raster.size.x
  } else {
    raster.size.y
  }
  let mut containing = 1
  while containing < max_size {
    containing = containing * 2
  }
  if containing < 512 {
    containing = 512
  }
  let new_atlas = FontAtlas::new(@math.UVec2::splat(containing), font_smoothing)
  if new_atlas.add_glyph(cache_key, raster) {
    font_atlases.push(new_atlas)
    return get_glyph_atlas_info(font_atlases, cache_key)
  }
  None
}