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

///|
/// Typographic features and writing systems.
///
/// Ported from `swash/src/feature/*` (swash is dual-licensed Apache-2.0 OR MIT).

///|
const MARK : Tag = 0x6D61726B // "mark"

///|
const MKMK : Tag = 0x6D6B6D6B // "mkmk"

///|
const KERN : Tag = 0x6B65726E // "kern"

///|
const DFLT : Tag = 0x44464C54 // "DFLT"

///|
priv enum Kind {
  Empty
  At(UInt, UInt)
  Aat(UInt, Bool)
}

///|
fn Kind::from_font(font : FontRef) -> Kind {
  let gsub = font.table_offset(@internal.GSUB)
  let gpos = font.table_offset(@internal.GPOS)
  if gsub != 0U || gpos != 0U {
    return Kind::At(gsub, gpos)
  }
  let morx = font.table_offset(0x6D6F7278) // "morx"
  if morx != 0U {
    let kern = font.table_offset(0x6B65726E) != 0U || // "kern"
      font.table_offset(0x6B657278) != 0U // "kerx"
    return Kind::Aat(morx, kern)
  }
  Kind::Empty
}

///|
/// Modification performed by a feature.
pub(all) enum Action {
  /// Replaces one or more glyphs such as in ligation.
  Substitution
  /// Attaches one glyph to another such as in accent mark placement.
  Attachment
  /// Adjusts the position of one or more glyphs such as in kerning.
  Adjustment
}

///|
/// Typographic rule that produces modifications to a sequence of glyphs.
struct Feature {
  tag : Tag
  name : String?
  action : Action
}

///|
fn Feature::from_tag(tag : Tag, action : Action) -> Feature {
  let name = match desc_from_at(tag) {
    Option::None => Option::None
    Option::Some((_, desc)) => Option::Some(desc)
  }
  Feature::{ tag, name, action }
}

///|
/// Returns the feature tag.
pub fn Feature::tag(self : Feature) -> Tag {
  self.tag
}

///|
/// Returns the name of the feature, if available.
pub fn Feature::name(self : Feature) -> String? {
  self.name
}

///|
/// Returns the action of the feature.
pub fn Feature::action(self : Feature) -> Action {
  self.action
}

///|
priv enum WritingSystemKind {
  At(AtWritingSystem)
  Aat(AatWritingSystem)
}

///|
priv struct AatWritingSystem {
  data : Bytes
  morx : UInt
  kern : Bool
}

///|
fn AatWritingSystem::features(self : AatWritingSystem) -> AatFeatures {
  AatFeatures::AatFeatures(self.data, self.morx, self.kern)
}

///|
/// Script, language and associated typographic features available in a font.
struct WritingSystem {
  kind : WritingSystemKind
  script_tag : Tag
  lang_tag : Tag
  lang : Language?
}

///|
/// Returns the OpenType script tag for the writing system.
pub fn WritingSystem::script_tag(self : WritingSystem) -> Tag {
  self.script_tag
}

///|
/// Returns the OpenType language tag for the writing system.
pub fn WritingSystem::language_tag(self : WritingSystem) -> Tag {
  self.lang_tag
}

///|
/// Returns the script for the writing system.
pub fn WritingSystem::script(self : WritingSystem) -> Script? {
  Script::from_opentype(self.script_tag)
}

///|
/// Returns the language for the writing system.
pub fn WritingSystem::language(self : WritingSystem) -> Language? {
  self.lang
}

///|
/// Returns an iterator over the features provided by the writing system.
pub fn WritingSystem::features(self : WritingSystem) -> Features {
  Features::{
    kind: match self.kind {
      WritingSystemKind::At(ws) => FeaturesKind::At(ws.features())
      WritingSystemKind::Aat(ws) => FeaturesKind::Aat(ws.features())
    },
  }
}

///|
priv enum FeaturesKind {
  Empty
  At(AtFeatures)
  AtAll(AtAllFeatures)
  Aat(AatFeatures)
}

///|
/// Iterator over a collection of typographic features.
struct Features {
  kind : FeaturesKind
}

///|
fn action_for_aat_tag(tag : Tag) -> Action {
  if tag == KERN {
    Adjustment
  } else {
    Substitution
  }
}

///|
fn action_for_stage_tag(stage : Int, tag : Tag) -> Action {
  if stage == 0 {
    Action::Substitution
  } else if tag == MARK || tag == MKMK {
    Action::Attachment
  } else {
    Action::Adjustment
  }
}

///|
pub fn Features::from_font(font : FontRef) -> Features {
  match Kind::from_font(font) {
    Kind::At(gsub, gpos) =>
      Features::{
        kind: FeaturesKind::AtAll(
          AtAllFeatures::AtAllFeatures(font.data(), gsub, gpos),
        ),
      }
    Kind::Aat(morx, kern) =>
      Features::{
        kind: FeaturesKind::Aat(
          AatFeatures::AatFeatures(font.data(), morx, kern),
        ),
      }
    Kind::Empty => Features::{ kind: FeaturesKind::Empty }
  }
}

///|
pub fn Features::iter(self : Features) -> Iter[Feature] {
  match self.kind {
    FeaturesKind::Empty => Iter::new(fn() { None })
    FeaturesKind::At(af) => {
      let it = af.iter()
      Iter::new(fn() {
        match it.next() {
          None => None
          Some(f) => {
            let stage = f.stage.to_int()
            let action = action_for_stage_tag(stage, f.tag)
            Some(Feature::from_tag(f.tag, action))
          }
        }
      })
    }
    FeaturesKind::AtAll(af) => {
      let it = af.iter()
      Iter::new(fn() {
        match it.next() {
          None => None
          Some((stage, tag)) => {
            let action = action_for_stage_tag(stage, tag)
            Some(Feature::from_tag(tag, action))
          }
        }
      })
    }
    FeaturesKind::Aat(af) => {
      let it = af.iter()
      Iter::new(fn() {
        match it.next() {
          None => None
          Some((tag, desc)) =>
            Some(Feature::{
              tag,
              name: Option::Some(desc),
              action: action_for_aat_tag(tag),
            })
        }
      })
    }
  }
}

///|
pub fn FontRef::features(self : FontRef) -> Features {
  Features::from_font(self)
}

///|
priv enum WritingSystemsKind {
  Empty
  At(AtWritingSystems)
  Aat(AatWritingSystems)
}

///|
priv struct AatWritingSystems {
  data : Bytes
  morx : UInt
  kern : Bool
}

///|
fn AatWritingSystems::iter(self : AatWritingSystems) -> Iter[AatWritingSystem] {
  let data = self.data
  let morx = self.morx
  let kern = self.kern
  let mut done = false
  Iter::new(fn() {
    if done {
      None
    } else {
      done = true
      Some(AatWritingSystem::{ data, morx, kern })
    }
  })
}

///|
/// Iterator over a collection of writing systems.
struct WritingSystems {
  kind : WritingSystemsKind
}

///|
pub fn WritingSystems::from_font(font : FontRef) -> WritingSystems {
  match Kind::from_font(font) {
    Kind::At(gsub, gpos) => {
      let scripts = AtScripts::AtScripts(font.data(), gsub, gpos)
      WritingSystems::{
        kind: WritingSystemsKind::At(
          AtWritingSystems::AtWritingSystems(scripts),
        ),
      }
    }
    Kind::Aat(morx, kern) =>
      WritingSystems::{
        kind: WritingSystemsKind::Aat(AatWritingSystems::{
          data: font.data(),
          morx,
          kern,
        }),
      }
    Kind::Empty => WritingSystems::{ kind: WritingSystemsKind::Empty }
  }
}

///|
pub fn WritingSystems::iter(self : WritingSystems) -> Iter[WritingSystem] {
  match self.kind {
    WritingSystemsKind::Empty => Iter::new(fn() { None })
    WritingSystemsKind::At(ws) => {
      let it = ws.iter()
      Iter::new(fn() {
        match it.next() {
          None => None
          Some(v) => {
            let script_tag = v.script_tag()
            let lang_tag = v.language_tag()
            Some(WritingSystem::{
              kind: WritingSystemKind::At(v),
              script_tag,
              lang_tag,
              lang: Language::from_opentype(lang_tag),
            })
          }
        }
      })
    }
    WritingSystemsKind::Aat(ws) => {
      let it = ws.iter()
      Iter::new(fn() {
        match it.next() {
          None => None
          Some(v) =>
            Some(WritingSystem::{
              kind: WritingSystemKind::Aat(v),
              script_tag: DFLT,
              lang_tag: DFLT,
              lang: Option::None,
            })
        }
      })
    }
  }
}

///|
pub fn FontRef::writing_systems(self : FontRef) -> WritingSystems {
  WritingSystems::from_font(self)
}