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

///|
/// Unicode character properties.
///
/// Ported from `swash/src/text/unicode.rs` (swash is dual-licensed Apache-2.0 OR MIT).
const RECORD_MASK : UInt = 0x1FFFU

///|
const BOUNDARY_SHIFT : Int = 13

///|
/// Compact reference to Unicode properties for a character.
pub struct Properties {
  bits : UInt
}

///|
fn Properties::new(cp : Int) -> Properties {
  Properties::{ bits: get_record_index(cp).reinterpret_as_uint() }
}

///|
fn Properties::record(self : Properties) -> Record {
  // Index is produced by `get_record_index` and masked to 13 bits.
  records[(self.bits & RECORD_MASK).reinterpret_as_int()]
}

///|
pub fn Properties::category(self : Properties) -> Category {
  self.record().category
}

///|
pub fn Properties::block(self : Properties) -> Block {
  self.record().block
}

///|
pub fn Properties::script(self : Properties) -> Script {
  self.record().script
}

///|
pub fn Properties::combining_class(self : Properties) -> UInt {
  self.record().combining_class
}

///|
pub fn Properties::bidi_class(self : Properties) -> BidiClass {
  self.record().bidi_class
}

///|
pub fn Properties::joining_type(self : Properties) -> JoiningType {
  self.record().joining_type
}

///|
pub fn Properties::cluster_break(self : Properties) -> ClusterBreak {
  self.record().cluster_break
}

///|
pub fn Properties::word_break(self : Properties) -> WordBreak {
  self.record().word_break
}

///|
pub fn Properties::line_break(self : Properties) -> LineBreak {
  self.record().line_break
}

///|
pub fn Properties::is_emoji(self : Properties) -> Bool {
  self.record().flags.is_emoji()
}

///|
pub fn Properties::is_extended_pictographic(self : Properties) -> Bool {
  self.record().flags.is_extended_pictographic()
}

///|
pub fn Properties::is_open_bracket(self : Properties) -> Bool {
  self.record().flags.is_open_bracket()
}

///|
pub fn Properties::is_close_bracket(self : Properties) -> Bool {
  self.record().flags.is_close_bracket()
}

///|
pub fn Properties::is_ignorable(self : Properties) -> Bool {
  self.record().flags.is_ignorable()
}

///|
pub fn Properties::is_variation_selector(self : Properties) -> Bool {
  self.record().flags.is_variation_selector()
}

///|
pub fn Properties::contributes_to_shaping(self : Properties) -> Bool {
  self.record().flags.contributes_to_shaping()
}

///|
pub fn Properties::with_boundary(
  self : Properties,
  boundary : UInt,
) -> Properties {
  Properties::{
    bits: (self.bits & RECORD_MASK) | ((boundary & 0b11U) << BOUNDARY_SHIFT),
  }
}

///|
pub fn Properties::boundary(self : Properties) -> UInt {
  self.bits >> BOUNDARY_SHIFT
}

///|
pub fn Properties::use_class(self : Properties) -> (UseClass, Bool, Bool) {
  let r = self.record()
  (r.use_class, r.flags.needs_decomp(), r.flags.is_extended_pictographic())
}

///|
pub fn Properties::myanmar_class(self : Properties) -> (MyanmarClass, Bool) {
  let r = self.record()
  (r.myanmar_class, r.flags.is_extended_pictographic())
}

///|
pub fn Properties::cluster_class(self : Properties) -> (ClusterBreak, Bool) {
  let r = self.record()
  (r.cluster_break, r.flags.is_extended_pictographic())
}

///|
pub(all) enum BracketType {
  None
  Open(Char)
  Close(Char)
}

///|
fn text_unicode_bsearch_first(
  table : ReadOnlyArray[(UInt, UInt)],
  key : UInt,
) -> Int? {
  let mut l = 0
  let mut h = table.length()
  while l < h {
    let mid = (l + h) / 2
    let (k, _) = table[mid]
    if k < key {
      l = mid + 1
    } else if k > key {
      h = mid
    } else {
      return Some(mid)
    }
  }
  None
}

///|
fn text_unicode_bsearch_second(
  table : ReadOnlyArray[(UInt, UInt)],
  key : UInt,
) -> Int? {
  let mut l = 0
  let mut h = table.length()
  while l < h {
    let mid = (l + h) / 2
    let (_, k) = table[mid]
    if k < key {
      l = mid + 1
    } else if k > key {
      h = mid
    } else {
      return Some(mid)
    }
  }
  None
}

///|
pub(open) trait Codepoint {
  properties(Self) -> Properties
  opening_bracket(Self) -> Char?
  closing_bracket(Self) -> Char?
  mirror(Self) -> Char?
  bracket_type(Self) -> BracketType
  decompose(Self) -> Decompose
  decompose_compatible(Self) -> Decompose
}

///|
pub impl Codepoint for Char with properties(self) {
  Properties::new(self.to_int())
}

///|
pub impl Codepoint for Char with opening_bracket(self) {
  let c = self.to_int().reinterpret_as_uint()
  match text_unicode_bsearch_second(brackets, c) {
    None => None
    Some(ix) => Some(brackets[ix].0.reinterpret_as_int().unsafe_to_char())
  }
}

///|
pub impl Codepoint for Char with closing_bracket(self) {
  let c = self.to_int().reinterpret_as_uint()
  match text_unicode_bsearch_first(brackets, c) {
    None => None
    Some(ix) => Some(brackets[ix].1.reinterpret_as_int().unsafe_to_char())
  }
}

///|
pub impl Codepoint for Char with mirror(self) {
  let c = self.to_int().reinterpret_as_uint()
  match text_unicode_bsearch_first(mirrors, c) {
    None => None
    Some(ix) => Some(mirrors[ix].1.reinterpret_as_int().unsafe_to_char())
  }
}

///|
pub impl Codepoint for Char with bracket_type(self) {
  match Codepoint::closing_bracket(self) {
    Some(other) => BracketType::Open(other)
    None =>
      match Codepoint::opening_bracket(self) {
        Some(other) => BracketType::Close(other)
        None => BracketType::None
      }
  }
}

///|
pub impl Codepoint for Char with decompose(self) {
  decompose(self)
}

///|
pub impl Codepoint for Char with decompose_compatible(self) {
  decompose_compat(self)
}

///|
pub fn BidiClass::needs_resolution(self : BidiClass) -> Bool {
  match self {
    RLE | LRE | RLO | LRO | RLI | LRI | FSI | R | AL | AN => true
    _ => false
  }
}