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

///|
/// Shaper that partitions by font using trait based per-cluster selection.
///
/// Ported from `upstream swash/src/shape/partition.rs`.

///|
/// Font selected for a cluster.
///
/// Note: upstream uses a trait with an associated type returned by the selector.
/// MoonBit traits do not support associated types, so we use a concrete carrier
/// struct for the selected font.
pub struct SelectedFont {
  font : @swash.FontRef
  id_override : (UInt64, UInt64)?
  synthesis : @swash.Synthesis?
}

///|

///|
/// Creates a selected font wrapper.
pub fn SelectedFont::SelectedFont(font : @swash.FontRef) -> SelectedFont {
  { font, id_override: None, synthesis: None, }
}

///|
/// Returns a copy with an explicit cache id override.
pub fn SelectedFont::with_id_override(
  self : SelectedFont,
  id : (UInt64, UInt64),
) -> SelectedFont {
  { font: self.font, id_override: Some(id), synthesis: self.synthesis, }
}

///|
/// Returns a copy with a synthesis state attached.
pub fn SelectedFont::with_synthesis(
  self : SelectedFont,
  syn : @swash.Synthesis,
) -> SelectedFont {
  { font: self.font, id_override: self.id_override, synthesis: Some(syn), }
}

///|
/// Returns the underlying font.
pub fn SelectedFont::font(self : SelectedFont) -> @swash.FontRef {
  self.font
}

///|
/// Returns an optional override for cache identifier `(id0, id1)`.
pub fn SelectedFont::id_override(self : SelectedFont) -> (UInt64, UInt64)? {
  self.id_override
}

///|
/// Returns an optional synthesis state for the font.
pub fn SelectedFont::synthesis(self : SelectedFont) -> @swash.Synthesis? {
  self.synthesis
}

///|
/// Trait for types that can select appropriate fonts for character clusters.
pub(open) trait Selector {
  /// Selects a font for the specified character cluster.
  fn select_font(Self, cluster : CharCluster) -> SelectedFont?
}

///|
/// Trait for types that specify shaping options.
pub(open) trait ShapeOptions {
  fn script(Self) -> @swash.Script
  fn language(Self) -> @swash.Language?
  fn direction(Self) -> Direction
  fn size(Self) -> Double
  fn features(Self) -> Iter[@swash.FeatureSetting]
  fn variations(Self) -> Iter[@swash.VariationSetting]
  fn insert_dotted_circles(Self) -> Bool
}

///|
/// Simple implementation of the shape options trait.
pub struct SimpleShapeOptions {
  script : @swash.Script
  language : @swash.Language?
  direction : Direction
  size : Double
  features : Array[@swash.FeatureSetting]
  variations : Array[@swash.VariationSetting]
  insert_dotted_circles : Bool
}

///|
pub fn SimpleShapeOptions::default() -> SimpleShapeOptions {
  {
    script: Latin,
    language: None,
    direction: LeftToRight,
    size: 0.0,
    features: [],
    variations: [],
    insert_dotted_circles: false,
  }
}

///|
pub impl ShapeOptions for SimpleShapeOptions with fn script(self) {
  self.script
}

///|
pub impl ShapeOptions for SimpleShapeOptions with fn language(self) {
  self.language
}

///|
pub impl ShapeOptions for SimpleShapeOptions with fn direction(self) {
  self.direction
}

///|
pub impl ShapeOptions for SimpleShapeOptions with fn size(self) {
  self.size
}

///|
pub impl ShapeOptions for SimpleShapeOptions with fn features(self) {
  self.features.iter()
}

///|
pub impl ShapeOptions for SimpleShapeOptions with fn variations(self) {
  self.variations.iter()
}

///|
pub impl ShapeOptions for SimpleShapeOptions with fn insert_dotted_circles(self) {
  self.insert_dotted_circles
}

///|
fn selected_font_id(font : SelectedFont) -> (UInt64, UInt64) {
  match font.id_override() {
    Some(id) => id
    None => (font.font().key().value(), (-1).to_int64().reinterpret_as_uint64())
  }
}

///|
/// Shapes a run of text (provided as a `Token` iterator) using the specified
/// selector to assign per-cluster fonts.
pub fn[S : Selector, O : ShapeOptions] shape(
  context : ShapeContext,
  selector : S,
  options : O,
  tokens : Iter[@swash.Token],
  f : (SelectedFont, Shaper) -> Unit,
) -> Unit {
  let mut cluster = @swash.CharCluster::CharCluster()
  let parser0 = if options.direction() == RightToLeft {
    // Mirror characters for RTL text.
    @swash.Parser::Parser(
      options.script(),
      tokens.map((t : @swash.Token) => {
        let ch0 = t.ch()
        let ch = match @swash.Codepoint::mirror(ch0) {
          None => ch0
          Some(m) => m
        }
        Token(ch, t.offset(), t.len(), t.info(), t.data())
      }),
    )
  } else {
    Parser(options.script(), tokens)
  }
  if !parser0.next(cluster) {
    return
  }
  let mut current_font = selector.select_font(cluster)
  let mut parser1 = parser0
  while true {
    let (cont, parser2, cluster2, next_font) = shape_clusters(
      context, selector, options, parser1, cluster, current_font, f,
    )
    parser1 = parser2
    cluster = cluster2
    current_font = next_font
    if !cont {
      break
    }
  }
}

///|
fn[S : Selector, O : ShapeOptions] shape_clusters(
  context : ShapeContext,
  selector : S,
  options : O,
  parser : @swash.Parser,
  cluster : CharCluster,
  current_font : SelectedFont?,
  f : (SelectedFont, Shaper) -> Unit,
) -> (Bool, @swash.Parser, CharCluster, SelectedFont?) {
  match current_font {
    None => (false, parser, cluster, None)
    Some(font) => {
      let (id0, id1) = selected_font_id(font)
      let mut builder = context
        .builder_with_id(font.font(), (id0, id1))
        .script(options.script())
        .language(options.language())
        .direction(options.direction())
        .size(options.size())
        .features(options.features())
      match font.synthesis() {
        None => ()
        Some(syn) => builder = builder.variations(syn.variations().iter())
      }
      builder = builder
        .variations(options.variations())
        .insert_dotted_circles(options.insert_dotted_circles())
      let shaper = builder.build()
      while true {
        shaper.add_cluster(cluster)
        if !parser.next(cluster) {
          f(font, shaper)
          return (false, parser, cluster, None)
        }
        match selector.select_font(cluster) {
          None => return (false, parser, cluster, None)
          Some(next_font) => {
            let (nid0, nid1) = selected_font_id(next_font)
            if nid0 != id0 || nid1 != id1 {
              f(font, shaper)
              return (true, parser, cluster, Some(next_font))
            }
          }
        }
      } nobreak {
        (
          // Unreachable, but required because `while` is an expression.
          false,
          parser,
          cluster,
          None,
        )
      }
    }
  }
}

///|
pub extend SimpleShapeOptions with ShapeOptions::{
  insert_dotted_circles,
  features,
  direction,
  size,
  variations,
  script,
  language,
}