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

///|
/// Shaping engine that handles the various methods available in an OpenType font.
///
/// Ported from `upstream swash/src/shape/engine.rs`.

///|
pub(all) enum EngineMode {
  Simple
  Myanmar
  Complex
}

///|
pub impl Eq for EngineMode with fn equal(self, other) {
  match self {
    Simple => other is Simple
    Myanmar => other is Myanmar
    Complex => other is Complex
  }
}

///|
pub(all) enum SubMode {
  None
  Gsub
  Morx
}

///|
pub impl Eq for SubMode with fn equal(self, other) {
  match self {
    None => other is None
    Gsub => other is Gsub
    Morx => other is Morx
  }
}

///|
pub(all) enum PosMode {
  None
  Gpos
  Kerx
  Kern
}

///|
pub impl Eq for PosMode with fn equal(self, other) {
  match self {
    None => other is None
    Gpos => other is Gpos
    Kerx => other is Kerx
    Kern => other is Kern
  }
}

///|
/// Metadata for creating a shaping engine.
pub struct EngineMetadata {
  gdef : UInt32
  gsub : UInt32
  gpos : UInt32
  morx : UInt32
  kerx : UInt32
  ankr : UInt32
  kern : UInt32
  sub_mode : SubMode
  pos_mode : PosMode
}

///|
pub fn EngineMetadata::from_font(font : @swash.FontRef) -> EngineMetadata {
  let gdef = font.table_offset(0x47444546) // "GDEF"
  let gsub = font.table_offset(0x47535542) // "GSUB"
  let gpos = font.table_offset(0x47504F53) // "GPOS"
  let morx = font.table_offset(0x6D6F7278) // "morx"
  let kerx = font.table_offset(0x6B657278) // "kerx"
  let ankr = font.table_offset(0x616E6B72) // "ankr"
  let kern = font.table_offset(0x6B65726E) // "kern"
  let mut sub_mode = SubMode::None
  if gsub != 0U {
    sub_mode = Gsub
  } else if morx != 0U {
    sub_mode = Morx
  }
  let mut pos_mode = PosMode::None
  if gpos != 0U {
    pos_mode = Gpos
  } else if kerx != 0U {
    pos_mode = Kerx
  } else if kern != 0U {
    pos_mode = Kern
  }
  { gdef, gsub, gpos, morx, kerx, ankr, kern, sub_mode, pos_mode, }
}

///|
pub struct Engine {
  data : Bytes
  gdef : @internal.Gdef
  gsub : StageOffsets
  gpos : StageOffsets
  morx : UInt32
  kerx : UInt32
  ankr : UInt32
  kern : UInt32
  storage : Storage
  coords : ArrayView[Int32]
  script : @swash.Script
  tags : (UInt32, UInt32, UInt32, UInt32)
  sub_mode : SubMode
  pos_mode : PosMode
  use_ot : Bool
  mode : EngineMode
}

///|

///|
pub fn Engine::Engine(
  metadata : EngineMetadata,
  font_data : Bytes,
  coords : ArrayView[Int32],
  script : @swash.Script,
  language : @swash.Language?,
) -> Engine {
  let gdef = match @internal.Gdef::from_offset(font_data, metadata.gdef) {
    None => @internal.Gdef::empty()
    Some(g) => g
  }
  let script_tag = script.to_opentype()
  let lang_tag : UInt32? = match language {
    None => None
    Some(l) => l.to_opentype()
  }
  let (gsub, stags) = if metadata.sub_mode == Gsub {
    match
      StageOffsets::from_layout(font_data, metadata.gsub, script_tag, lang_tag) {
      None => (StageOffsets::default(), (0U, 0U))
      Some((off, tags)) => (off, tags)
    }
  } else {
    (StageOffsets::default(), (0U, 0U))
  }
  let (gpos, ptags) = if metadata.pos_mode == Gpos {
    match
      StageOffsets::from_layout(font_data, metadata.gpos, script_tag, lang_tag) {
      None => (StageOffsets::default(), (0U, 0U))
      Some((off, tags)) => (off, tags)
    }
  } else {
    (StageOffsets::default(), (0U, 0U))
  }
  let tags : (UInt32, UInt32, UInt32, UInt32) = (
    stags.0,
    stags.1,
    ptags.0,
    ptags.1,
  )
  let use_ot = gsub.lang != 0U || gpos.lang != 0U
  let mode = if gsub.lang != 0U && script.is_complex() {
    if script is Myanmar {
      EngineMode::Myanmar
    } else {
      Complex
    }
  } else {
    Simple
  }
  let sub_mode = if metadata.sub_mode == Gsub && gsub.lang == 0U {
    SubMode::None
  } else {
    metadata.sub_mode
  }
  let pos_mode = if metadata.pos_mode == Gpos && gpos.lang == 0U {
    PosMode::None
  } else {
    metadata.pos_mode
  }
  {
    data: font_data,
    gdef,
    gsub,
    gpos,
    morx: metadata.morx,
    kerx: metadata.kerx,
    ankr: metadata.ankr,
    kern: metadata.kern,
    storage: Storage::default(),
    coords,
    script,
    tags,
    sub_mode,
    pos_mode,
    use_ot,
    mode,
  }
}

///|
/// OpenType shaping.
pub fn Engine::tags(self : Engine) -> (UInt32, UInt32, UInt32, UInt32) {
  self.tags
}

///|
/// Builds a feature store for the current engine configuration.
pub fn Engine::collect_features(
  self : Engine,
  builder : FeatureStoreBuilder,
  store : Ref[FeatureStore],
) -> Unit {
  builder.build(store, self.data, self.coords, self.gdef, self.gsub, self.gpos)
  store.val.groups = store.val.groups(self.script)
}

///|
/// Returns true if feature variations are supported.
pub fn Engine::has_feature_vars(self : Engine) -> Bool {
  self.gsub.vars != 0U || self.gpos.vars != 0U
}

///|
/// Sets glyph and mark classes for the specified range of the buffer.
pub fn Engine::set_classes(
  self : Engine,
  buffer : Buffer,
  range : IndexRange?,
) -> Unit {
  if !self.gdef.ok() {
    return
  }
  let (start, end) = match range {
    None => (0, buffer.glyphs.length())
    Some(r) => (r.start, r.end)
  }
  if self.gdef.has_mark_classes() {
    for i in start.. Bool {
  apply(
    (0).to_byte(),
    self.data,
    self.gsub.base,
    self.coords,
    self.gdef,
    self.storage,
    store,
    feature_mask,
    buffer,
    buffer_range,
  ) ==
  Some(true)
}

///|
/// Applies the GPOS features to the specified range of the buffer.
pub fn Engine::gpos(
  self : Engine,
  store : FeatureStore,
  feature_mask : FeatureMask,
  buffer : Buffer,
  buffer_range : IndexRange?,
) -> Bool {
  apply(
    (1).to_byte(),
    self.data,
    self.gpos.base,
    self.coords,
    self.gdef,
    self.storage,
    store,
    feature_mask,
    buffer,
    buffer_range,
  ) ==
  Some(true)
}

///|
/// Converts feature settings into a sorted collection of AAT selectors.
pub fn Engine::collect_selectors(
  self : Engine,
  features : ArrayView[@swash.FeatureSetting],
  selectors : Array[(UInt16, UInt16)],
) -> Unit {
  ignore(self)
  selectors.clear()
  for s in features.iter() {
    match @internal.morx_feature_from_tag(s.tag) {
      None => ()
      Some((selector, (on, off))) => {
        let setting = if s.value == (0).to_uint16() { off } else { on }
        selectors.push((selector, setting))
      }
    }
  }
  selectors.sort_by_key(fn(p) {
    let (a, b) = p
    (a.to_int().reinterpret_as_uint() << 16) | b.to_int().reinterpret_as_uint()
  })
}

///|
/// Applies the extended metamorphosis table.
pub fn Engine::morx(
  self : Engine,
  buffer : Buffer,
  selectors : ArrayView[(UInt16, UInt16)],
) -> Unit {
  if self.morx != 0U {
    apply_morx(self.data, self.morx, buffer, selectors)
    buffer.ensure_order(false)
  }
}

///|
/// Applies the extended kerning table.
pub fn Engine::kerx(
  self : Engine,
  buffer : Buffer,
  disable_kern : Bool,
) -> Unit {
  if self.kerx != 0U {
    apply_kerx(self.data, self.kerx, self.ankr, buffer, disable_kern)
    buffer.ensure_order(false)
  }
}

///|
/// Applies the kerning table.
pub fn Engine::kern(self : Engine, buffer : Buffer) -> Unit {
  if self.kern != 0U {
    apply_kern(self.data, self.kern, buffer)
  }
}

///|
pub extend EngineMode with Eq::{not_equal, equal}

///|
pub extend PosMode with Eq::{not_equal, equal}

///|
pub extend SubMode with Eq::{not_equal, equal}