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

///|
/// Hinting instance cache.
///
/// Ported from upstream `swash/src/scale/hinting_cache.rs` (Apache-2.0 OR MIT).

///|
const MAX_CACHED_HINT_INSTANCES : Int = 8

///|
fn hinting_options() -> @skrifa_outline.HintingOptions {
  @skrifa_outline.HintingMode::Smooth(
    Some(@skrifa_outline.LcdLayout::Horizontal),
    true,
  ).into()
}

///|
priv struct HintingEntry {
  id0 : UInt64
  id1 : UInt64
  size : @skrifa.Size
  coords : Array[Int]
  instance : @skrifa_outline.HintingInstance
  serial : UInt64
}

///|
fn size_eq(a : @skrifa.Size, b : @skrifa.Size) -> Bool {
  match (a.ppem(), b.ppem()) {
    (None, None) => true
    (Some(x), Some(y)) => x == y
    _ => false
  }
}

///|
fn coords_eq(a : ArrayView[Int], b : ArrayView[Int]) -> Bool {
  if a.length() != b.length() {
    return false
  }
  for i in 0.. Array[Int] {
  let out : Array[Int] = []
  for c in loc.effective_coords().iter() {
    out.push(c)
  }
  out
}

///|
priv struct HintingCache {
  glyf_entries : Array[HintingEntry]
  cff_entries : Array[HintingEntry]
  mut serial : UInt64
}

///|

///|
fn HintingCache::HintingCache() -> HintingCache {
  HintingCache::{ glyf_entries: [], cff_entries: [], serial: 0 }
}

///|
fn find_hinting_entry(
  entries : Array[HintingEntry],
  id0 : UInt64,
  id1 : UInt64,
  size : @skrifa.Size,
  coords : ArrayView[Int],
) -> (Int, Bool)? {
  let mut found_serial : UInt64 = 0xFFFFFFFFFFFFFFFF
  let mut found_index = 0
  for i in 0.. @skrifa_outline.HintingInstance? {
  let fmt = match outlines.format() {
    None => return None
    Some(f) => f
  }
  let entries = match fmt {
    @skrifa_outline.OutlineGlyphFormat::Glyf => self.glyf_entries
    _ => self.cff_entries
  }
  let coords = effective_coords(loc)
  match find_hinting_entry(entries, id0, id1, size, coords.op_as_view()) {
    None => None
    Some((ix, is_current)) => {
      self.serial = self.serial + 1
      if ix == entries.length() {
        match
          @skrifa_outline.HintingInstance::create(
            outlines,
            size,
            loc,
            hinting_options(),
          ) {
          Err(_) => None
          Ok(h) => {
            entries.push(HintingEntry::{
              id0,
              id1,
              size,
              coords,
              instance: h,
              serial: self.serial,
            })
            Some(entries[ix].instance)
          }
        }
      } else {
        let e = entries[ix]
        entries[ix] = HintingEntry::{
          id0: e.id0,
          id1: e.id1,
          size: e.size,
          coords: e.coords,
          instance: e.instance,
          serial: self.serial,
        }
        if is_current {
          Some(entries[ix].instance)
        } else {
          // Reuse the existing instance memory by reconfiguring it in-place.
          let e2 = entries[ix]
          let inst = e2.instance
          match inst.reconfigure(outlines, size, loc, hinting_options()) {
            Err(_) => None
            Ok(_) => {
              entries[ix] = HintingEntry::{
                id0,
                id1,
                size,
                coords,
                instance: inst,
                serial: self.serial,
              }
              Some(entries[ix].instance)
            }
          }
        }
      }
    }
  }
}