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

///|
/// Glyph zones for TrueType hinting.
///
/// Ported from `fontations/skrifa/src/outline/glyf/hint/zone.rs` (Apache-2.0 OR MIT).
priv struct TtPoint {
  x : Int
  y : Int
}

///|
priv enum TtZonePointer {
  Twilight
  Glyph
}

///|
impl Eq for TtZonePointer with fn equal(a, b) {
  match (a, b) {
    (Twilight, Twilight) => true
    (Glyph, Glyph) => true
    _ => false
  }
}

///|
fn TtZonePointer::default() -> TtZonePointer {
  Glyph
}

///|
fn TtZonePointer::is_twilight(self : TtZonePointer) -> Bool {
  self is Twilight
}

///|
fn tt_zone_pointer_from_i32(value : Int) -> Result[TtZonePointer, HintError] {
  match value {
    0 => Ok(Twilight)
    1 => Ok(Glyph)
    _ => Err(InvalidZoneIndex(value))
  }
}

///|
const TT_POINT_ON_CURVE : Int = 1

///|
const TT_POINT_TOUCHED_X : Int = 2

///|
const TT_POINT_TOUCHED_Y : Int = 4

///|
priv enum TtAxis {
  X
  Y
}

///|
fn TtAxis::touched_mask(self : TtAxis) -> Int {
  match self {
    X => TT_POINT_TOUCHED_X
    Y => TT_POINT_TOUCHED_Y
  }
}

///|
priv struct TtZone {
  /// Unscaled points in font units. For twilight this can be empty.
  unscaled : Array[TtPoint]
  /// Scaled outline points prior to executing instructions.
  original : Array[TtPoint]
  /// Current scaled outline points.
  points : Array[TtPoint]
  flags : Array[Int]
  contours : Array[Int]
}

///|
fn TtZone::default() -> TtZone {
  {
    unscaled: Array::new(),
    original: Array::new(),
    points: Array::new(),
    flags: Array::new(),
    contours: Array::new(),
  }
}

///|
fn TtZone::point(self : TtZone, index : Int) -> Result[TtPoint, HintError] {
  if index < 0 || index >= self.points.length() {
    Err(InvalidPointIndex(index))
  } else {
    Ok(self.points.at(index))
  }
}

///|
fn TtZone::set_point(
  self : TtZone,
  index : Int,
  p : TtPoint,
) -> Result[Unit, HintError] {
  if index < 0 || index >= self.points.length() {
    Err(InvalidPointIndex(index))
  } else {
    self.points.set(index, p)
    Ok(())
  }
}

///|
fn TtZone::original_point(
  self : TtZone,
  index : Int,
) -> Result[TtPoint, HintError] {
  if index < 0 || index >= self.original.length() {
    Err(InvalidPointIndex(index))
  } else {
    Ok(self.original.at(index))
  }
}

///|
fn TtZone::set_original_point(
  self : TtZone,
  index : Int,
  p : TtPoint,
) -> Result[Unit, HintError] {
  if index < 0 || index >= self.original.length() {
    Err(InvalidPointIndex(index))
  } else {
    self.original.set(index, p)
    Ok(())
  }
}

///|
fn TtZone::unscaled_point(self : TtZone, index : Int) -> TtPoint {
  if index < 0 || index >= self.unscaled.length() {
    // Twilight unscaled points default to (0,0).
    { x: 0, y: 0 }
  } else {
    self.unscaled.at(index)
  }
}

///|
fn TtZone::contour_end(self : TtZone, index : Int) -> Result[Int, HintError] {
  if index < 0 || index >= self.contours.length() {
    Err(InvalidStackValue(index))
  } else {
    Ok(self.contours.at(index))
  }
}

///|
fn TtZone::touch(
  self : TtZone,
  index : Int,
  axis : TtAxis,
) -> Result[Unit, HintError] {
  if index < 0 || index >= self.flags.length() {
    return Err(InvalidPointIndex(index))
  }
  self.flags.set(index, self.flags.at(index) | axis.touched_mask())
  Ok(())
}

///|

///|
fn TtZone::is_touched(
  self : TtZone,
  index : Int,
  axis : TtAxis,
) -> Result[Bool, HintError] {
  if index < 0 || index >= self.flags.length() {
    Err(InvalidPointIndex(index))
  } else {
    Ok((self.flags.at(index) & axis.touched_mask()) != 0)
  }
}

///|
fn TtZone::untouch(
  self : TtZone,
  index : Int,
  axis : TtAxis,
) -> Result[Unit, HintError] {
  if index < 0 || index >= self.flags.length() {
    return Err(InvalidPointIndex(index))
  }
  self.flags.set(index, self.flags.at(index) & (axis.touched_mask() ^ -1))
  Ok(())
}

///|
fn TtZone::flip_on_curve(self : TtZone, index : Int) -> Result[Unit, HintError] {
  if index < 0 || index >= self.flags.length() {
    return Err(InvalidPointIndex(index))
  }
  self.flags.set(index, self.flags.at(index) ^ TT_POINT_ON_CURVE)
  Ok(())
}

///|
fn TtZone::set_on_curve(
  self : TtZone,
  start : Int,
  end_ : Int,
  on : Bool,
) -> Result[Unit, HintError] {
  if start < 0 || end_ < start || end_ > self.flags.length() {
    return Err(InvalidPointRange(start, end_))
  }
  let mask = TT_POINT_ON_CURVE
  for i in start.. Result[Unit, HintError] {
  let mut point = 0
  for i in 0.. return Err(e)
      Ok(v) => v
    }
    let first_point = point
    if end_point >= self.points.length() {
      end_point = self.points.length() - 1
    }
    while point <= end_point {
      match self.is_touched(point, axis) {
        Err(e) => return Err(e)
        Ok(touched) => if touched { break } else { point = point + 1 }
      }
    }
    if point <= end_point {
      let first_touched = point
      let mut cur_touched = point
      point = point + 1
      while point <= end_point {
        match self.is_touched(point, axis) {
          Err(e) => return Err(e)
          Ok(touched) =>
            if touched {
              match
                self.iup_interpolate(
                  axis,
                  cur_touched + 1,
                  point - 1,
                  cur_touched,
                  point,
                ) {
                Err(e) => return Err(e)
                Ok(_) => ()
              }
              cur_touched = point
            }
        }
        point = point + 1
      }
      if cur_touched == first_touched {
        match self.iup_shift(axis, first_point, end_point, cur_touched) {
          Err(e) => return Err(e)
          Ok(_) => ()
        }
      } else {
        match
          self.iup_interpolate(
            axis,
            cur_touched + 1,
            end_point,
            cur_touched,
            first_touched,
          ) {
          Err(e) => return Err(e)
          Ok(_) => ()
        }
        if first_touched > 0 {
          match
            self.iup_interpolate(
              axis,
              first_point,
              first_touched - 1,
              cur_touched,
              first_touched,
            ) {
            Err(e) => return Err(e)
            Ok(_) => ()
          }
        }
      }
    }
  }
  Ok(())
}

///|
fn TtZone::iup_shift(
  self : TtZone,
  axis : TtAxis,
  p1 : Int,
  p2 : Int,
  p : Int,
) -> Result[Unit, HintError] {
  if p1 > p2 || p1 > p || p > p2 {
    return Ok(())
  }
  let (p_now, p_orig) = match (self.point(p), self.original_point(p)) {
    (Ok(a), Ok(b)) => (a, b)
    (Err(e), _) => return Err(e)
    (_, Err(e)) => return Err(e)
  }
  let delta = match axis {
    X => p_now.x - p_orig.x
    Y => p_now.y - p_orig.y
  }
  if delta != 0 {
    for i in p1..<(p2 + 1) {
      if i == p {
        continue
      }
      let mut pt = match self.point(i) {
        Err(e) => return Err(e)
        Ok(v) => v
      }
      match axis {
        X => pt = { x: pt.x + delta, y: pt.y }
        Y => pt = { x: pt.x, y: pt.y + delta }
      }
      match self.set_point(i, pt) {
        Err(e) => return Err(e)
        Ok(_) => ()
      }
    }
  }
  Ok(())
}

///|
fn tt_zone_axis_coord(p : TtPoint, axis : TtAxis) -> Int {
  match axis {
    X => p.x
    Y => p.y
  }
}

///|
fn tt_zone_set_axis_coord(p : TtPoint, axis : TtAxis, v : Int) -> TtPoint {
  match axis {
    X => { x: v, y: p.y }
    Y => { x: p.x, y: v }
  }
}

///|
fn TtZone::iup_interpolate(
  self : TtZone,
  axis : TtAxis,
  p1 : Int,
  p2 : Int,
  ref1_0 : Int,
  ref2_0 : Int,
) -> Result[Unit, HintError] {
  if p1 > p2 {
    return Ok(())
  }
  let mut ref1 = ref1_0
  let mut ref2 = ref2_0
  let max_points = self.points.length()
  if ref1 < 0 || ref2 < 0 || ref1 >= max_points || ref2 >= max_points {
    return Ok(())
  }
  // Unscaled reference coordinates in font units.
  let mut orus1 = tt_zone_axis_coord(self.unscaled_point(ref1), axis)
  let mut orus2 = tt_zone_axis_coord(self.unscaled_point(ref2), axis)
  if orus1 > orus2 {
    let tmp = orus1
    orus1 = orus2
    orus2 = tmp
    let tmpr = ref1
    ref1 = ref2
    ref2 = tmpr
  }
  // Scaled reference coordinates.
  let org1 = tt_zone_axis_coord(
    match self.original_point(ref1) {
      Err(e) => return Err(e)
      Ok(v) => v
    },
    axis,
  )
  let org2 = tt_zone_axis_coord(
    match self.original_point(ref2) {
      Err(e) => return Err(e)
      Ok(v) => v
    },
    axis,
  )
  let cur1 = tt_zone_axis_coord(
    match self.point(ref1) {
      Err(e) => return Err(e)
      Ok(v) => v
    },
    axis,
  )
  let cur2 = tt_zone_axis_coord(
    match self.point(ref2) {
      Err(e) => return Err(e)
      Ok(v) => v
    },
    axis,
  )
  let delta1 = cur1 - org1
  let delta2 = cur2 - org2
  let use_simple = cur1 == cur2 || orus1 == orus2
  let scale = if use_simple {
    0
  } else {
    tt_hint_div_16_16(cur2 - cur1, orus2 - orus1)
  }
  for i in p1..<(p2 + 1) {
    let org = tt_zone_axis_coord(
      match self.original_point(i) {
        Err(e) => return Err(e)
        Ok(v) => v
      },
      axis,
    )
    let pt = match self.point(i) {
      Err(e) => return Err(e)
      Ok(v) => v
    }
    let new_coord = if org <= org1 {
      org + delta1
    } else if org >= org2 {
      org + delta2
    } else if use_simple {
      cur1
    } else {
      let unscaled = tt_zone_axis_coord(self.unscaled_point(i), axis)
      cur1 + tt_hint_mul_16_16(unscaled - orus1, scale)
    }
    match self.set_point(i, tt_zone_set_axis_coord(pt, axis, new_coord)) {
      Err(e) => return Err(e)
      Ok(_) => ()
    }
  }
  Ok(())
}