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

///|
/// Cache for shaped runs (ported from `cosmic-text/src/shape_run_cache.rs`).

///|
/// Key for caching shape runs.
pub struct ShapeRunKey {
  text : String
  default_attrs : Attrs
  attrs_spans : Array[(Int, Int, Attrs)]
}

///|
pub fn ShapeRunKey::new(text : String, attrs_list : AttrsList) -> ShapeRunKey {
  let default_attrs = attrs_list.defaults()
  let spans : Array[(Int, Int, Attrs)] = []
  for s in attrs_list.spans_iter() {
    if s.attrs != default_attrs {
      spans.push((s.start, s.end, s.attrs))
    }
  }
  ShapeRunKey::{ text, default_attrs, attrs_spans: spans }
}

///|
pub impl Eq for ShapeRunKey with equal(self, other) {
  if self.text != other.text ||
    self.default_attrs != other.default_attrs ||
    self.attrs_spans.length() != other.attrs_spans.length() {
    return false
  }
  for i in 0.. {
      hasher.combine_int(0)
      hasher.combine_string(name)
    }
    Serif => hasher.combine_int(1)
    SansSerif => hasher.combine_int(2)
    Cursive => hasher.combine_int(3)
    Fantasy => hasher.combine_int(4)
    Monospace => hasher.combine_int(5)
  }
  hasher.combine_int(self.default_attrs.weight_value().value())
  match self.default_attrs.metrics_opt() {
    Option::None => hasher.combine_int(0)
    Option::Some(m) => {
      hasher.combine_int(1)
      // Hash f32 bits (stable) via CacheMetrics.
      let cm = CacheMetrics::from_metrics(m)
      cm.hash_combine(hasher)
    }
  }
  hasher.combine_int(self.attrs_spans.length())
  for s in self.attrs_spans {
    hasher.combine_int(s.0)
    hasher.combine_int(s.1)
    hasher.combine_int(s.2.metadata())
    match s.2.family_value() {
      Name(name) => {
        hasher.combine_int(0)
        hasher.combine_string(name)
      }
      Serif => hasher.combine_int(1)
      SansSerif => hasher.combine_int(2)
      Cursive => hasher.combine_int(3)
      Fantasy => hasher.combine_int(4)
      Monospace => hasher.combine_int(5)
    }
    hasher.combine_int(s.2.weight_value().value())
    match s.2.metrics_opt() {
      Option::None => hasher.combine_int(0)
      Option::Some(m) => {
        hasher.combine_int(1)
        let cm = CacheMetrics::from_metrics(m)
        cm.hash_combine(hasher)
      }
    }
  }
}

///|
/// A helper structure for caching shape runs.
pub struct ShapeRunCache {
  mut age : UInt64
  cache : @hashmap.HashMap[ShapeRunKey, (UInt64, Array[ShapeGlyph])]
}

///|
pub fn ShapeRunCache::new() -> ShapeRunCache {
  ShapeRunCache::{ age: 0UL, cache: @hashmap.HashMap::default() }
}

///|
/// Get a cache item, updating age if found.
pub fn ShapeRunCache::get(
  self : ShapeRunCache,
  key : ShapeRunKey,
) -> Array[ShapeGlyph]? {
  match self.cache.get(key) {
    None => None
    Some((_, glyphs)) => {
      self.cache.set(key, (self.age, glyphs))
      Some(glyphs)
    }
  }
}

///|
/// Peek a cache item without updating its age.
pub fn ShapeRunCache::peek(
  self : ShapeRunCache,
  key : ShapeRunKey,
) -> Array[ShapeGlyph]? {
  match self.cache.get(key) {
    None => None
    Some((_, glyphs)) => Some(glyphs)
  }
}

///|
/// Insert a cache item with current age.
pub fn ShapeRunCache::insert(
  self : ShapeRunCache,
  key : ShapeRunKey,
  glyphs : Array[ShapeGlyph],
) -> Unit {
  self.cache.set(key, (self.age, glyphs))
}

///|
/// Clear all cached runs.
pub fn ShapeRunCache::clear(self : ShapeRunCache) -> Unit {
  self.cache.clear()
}

///|
/// Remove anything in the cache with an age older than `keep_ages`.
pub fn ShapeRunCache::trim(self : ShapeRunCache, keep_ages : UInt64) -> Unit {
  let entries = self.cache.to_array()
  for e in entries {
    let (k, v) = e
    let age = v.0
    if age + keep_ages < self.age {
      self.cache.remove(k)
    }
  }
  self.age = self.age + 1UL
}

///|
pub impl Show for ShapeRunCache with output(_self, logger) {
  logger.write_string("ShapeRunCache { .. }")
}