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

///|
/// OpenType (GSUB/GPOS) writing systems and feature enumeration.
///
/// Ported from `swash/src/feature/at.rs` (swash is dual-licensed Apache-2.0 OR MIT).

///|
priv struct AtScript {
  data : Bytes
  gsub : UInt
  gpos : UInt
  gsub_offset : UInt
  gpos_offset : UInt
  tag : Tag
}

///|
fn AtScript::languages(self : AtScript) -> AtLanguages {
  AtLanguages::AtLanguages(
    self.data,
    self.gsub,
    self.gpos,
    self.gsub_offset,
    self.gpos_offset,
  )
}

///|
priv struct AtScripts {
  data : Bytes
  gsub : UInt
  gpos : UInt
}

///|

///|
fn AtScripts::AtScripts(data : Bytes, gsub : UInt, gpos : UInt) -> AtScripts {
  AtScripts::{ data, gsub, gpos }
}

///|
fn AtScripts::iter(self : AtScripts) -> Iter[AtScript] {
  let data = self.data
  let gsub = self.gsub
  let gpos = self.gpos
  let mut in_gsub = true
  let mut len = @internal.script_count(data, gsub).to_int()
  let mut cur = 0
  let mut done = false
  Iter::new(fn() {
    while !done {
      if in_gsub {
        if cur < len {
          let index = cur.to_uint16()
          cur = cur + 1
          match @internal.script_at(data, gsub, index) {
            None => ()
            Some((tag, offset)) => {
              let gpos_offset = @internal.script_by_tag(data, gpos, tag).unwrap_or(
                0U,
              )
              return Some(AtScript::{
                data,
                gsub,
                gpos,
                gsub_offset: offset,
                gpos_offset,
                tag,
              })
            }
          }
        } else {
          in_gsub = false
          cur = 0
          len = @internal.script_count(data, gpos).to_int()
        }
      } else if cur < len {
        let index = cur.to_uint16()
        cur = cur + 1
        match @internal.script_at(data, gpos, index) {
          None => ()
          Some((tag, offset)) =>
            if @internal.script_by_tag(data, gsub, tag) is Some(_) {
              ()
            } else {
              return Some(AtScript::{
                data,
                gsub,
                gpos,
                gsub_offset: 0U,
                gpos_offset: offset,
                tag,
              })
            }
        }
      } else {
        done = true
      }
    }
    None
  })
}

///|
priv struct AtLangSys {
  data : Bytes
  gsub : UInt
  gpos : UInt
  gsub_offset : UInt
  gpos_offset : UInt
  tag : Tag
}

///|
priv struct AtLanguages {
  data : Bytes
  gsub : UInt
  gpos : UInt
  gsub_script : UInt
  gpos_script : UInt
}

///|

///|
fn AtLanguages::AtLanguages(
  data : Bytes,
  gsub : UInt,
  gpos : UInt,
  gsub_script : UInt,
  gpos_script : UInt,
) -> AtLanguages {
  AtLanguages::{ data, gsub, gpos, gsub_script, gpos_script }
}

///|
fn AtLanguages::iter(self : AtLanguages) -> Iter[AtLangSys] {
  let data = self.data
  let gsub = self.gsub
  let gpos = self.gpos
  let gsub_script = self.gsub_script
  let gpos_script = self.gpos_script
  let mut in_gsub = true
  let mut len = @internal.script_language_count(data, gsub_script).to_int()
  let mut cur = 0
  let mut done = false
  Iter::new(fn() {
    while !done {
      if in_gsub {
        if cur < len {
          let index = cur.to_uint16()
          cur = cur + 1
          match @internal.script_language_at(data, gsub_script, index) {
            None => ()
            Some((tag, offset)) => {
              let gsub_default = tag == @internal.DFLT
              let gpos_offset = if gsub_default {
                @internal.script_default_language(data, gpos_script).unwrap_or(
                  0U,
                )
              } else {
                match
                  @internal.script_language_by_tag(data, gpos_script, Some(tag)) {
                  None => 0U
                  Some((o, _)) => o
                }
              }
              return Some(AtLangSys::{
                data,
                gsub,
                gpos,
                gsub_offset: offset,
                gpos_offset,
                tag,
              })
            }
          }
        } else {
          in_gsub = false
          cur = 0
          len = @internal.script_language_count(data, gpos_script).to_int()
        }
      } else if cur < len {
        let index = cur.to_uint16()
        cur = cur + 1
        match @internal.script_language_at(data, gpos_script, index) {
          None => ()
          Some((tag, offset)) =>
            if @internal.script_language_by_tag(data, gsub_script, Some(tag))
              is Some(_) {
              ()
            } else {
              return Some(AtLangSys::{
                data,
                gsub,
                gpos,
                gsub_offset: 0U,
                gpos_offset: offset,
                tag,
              })
            }
        }
      } else {
        done = true
      }
    }
    None
  })
}

///|
priv struct AtWritingSystem {
  lang : AtLangSys
  script_tag : Tag
}

///|
fn AtWritingSystem::script_tag(self : AtWritingSystem) -> Tag {
  self.script_tag
}

///|
fn AtWritingSystem::language_tag(self : AtWritingSystem) -> Tag {
  self.lang.tag
}

///|
fn AtWritingSystem::features(self : AtWritingSystem) -> AtFeatures {
  AtFeatures::AtFeatures(self.lang)
}

///|
priv struct AtWritingSystems {
  scripts : AtScripts
}

///|

///|
fn AtWritingSystems::AtWritingSystems(scripts : AtScripts) -> AtWritingSystems {
  AtWritingSystems::{ scripts, }
}

///|
fn AtWritingSystems::iter(self : AtWritingSystems) -> Iter[AtWritingSystem] {
  let scripts = self.scripts.iter()
  let mut has_langs = false
  let mut langs = Iter::new(fn() { None })
  let mut script_tag : Tag = 0U
  Iter::new(fn() {
    while true {
      if !has_langs {
        match scripts.next() {
          None => return None
          Some(s) => {
            script_tag = s.tag
            langs = s.languages().iter()
            has_langs = true
          }
        }
      }
      match langs.next() {
        None => has_langs = false
        Some(lang) => return Some(AtWritingSystem::{ lang, script_tag })
      }
    }
    None
  })
}

///|
priv struct AtFeature {
  stage : Byte
  tag : Tag
}

///|
priv struct AtFeatures {
  lang : AtLangSys
}

///|

///|
fn AtFeatures::AtFeatures(lang : AtLangSys) -> AtFeatures {
  AtFeatures::{ lang, }
}

///|
fn AtFeatures::iter(self : AtFeatures) -> Iter[AtFeature] {
  let data = self.lang.data
  let gsub = self.lang.gsub
  let gpos = self.lang.gpos
  let gsub_language = self.lang.gsub_offset
  let gpos_language = self.lang.gpos_offset
  let mut stage : Int = 0
  let mut len = @internal.language_feature_count(data, gsub_language).to_int()
  let mut cur = 0
  let mut done = false
  Iter::new(fn() {
    while !done {
      let (table, language) = if stage == 0 {
        (gsub, gsub_language)
      } else {
        (gpos, gpos_language)
      }
      if cur < len {
        let index = cur.to_uint16()
        cur = cur + 1
        match @internal.language_feature_at(data, language, index) {
          None => ()
          Some(feature_index) =>
            match @internal.feature_at(data, table, feature_index) {
              None => ()
              Some((tag, _)) =>
                return Some(AtFeature::{ stage: stage.to_byte(), tag })
            }
        }
      } else if stage == 0 {
        stage = 1
        len = @internal.language_feature_count(data, gpos_language).to_int()
        cur = 0
      } else {
        done = true
      }
    }
    None
  })
}

///|
priv struct AtAllFeatures {
  data : Bytes
  seen : SeenFeatures
  table : UInt
  next_table : UInt
  stage : Int
  len : Int
  cur : Int
}

///|

///|
fn AtAllFeatures::AtAllFeatures(
  data : Bytes,
  gsub : UInt,
  gpos : UInt,
) -> AtAllFeatures {
  AtAllFeatures::{
    data,
    seen: SeenFeatures::SeenFeatures(),
    table: gsub,
    next_table: gpos,
    stage: 0,
    len: @internal.feature_count(data, gsub).to_int(),
    cur: 0,
  }
}

///|
fn AtAllFeatures::iter(self : AtAllFeatures) -> Iter[(Int, Tag)] {
  let data = self.data
  let seen = self.seen
  let mut table = self.table
  let mut next_table = self.next_table
  let mut stage = self.stage
  let mut len = self.len
  let mut cur = self.cur
  Iter::new(fn() {
    while true {
      if cur >= len {
        if next_table == 0U {
          return None
        }
        table = next_table
        next_table = 0U
        stage = 1
        cur = 0
        len = @internal.feature_count(data, table).to_int()
        if len == 0 {
          return None
        }
      }
      let index = cur.to_uint16()
      cur = cur + 1
      match @internal.feature_at(data, table, index) {
        None => continue
        Some((tag, _)) =>
          match desc_from_at(tag) {
            None => continue
            Some((ix, _)) =>
              if seen.mark(ix) {
                return Some((stage, tag))
              } else {
                continue
              }
          }
      }
    }
    None
  })
}