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

///|
pub struct VarcTransform {
  translate_x : Double
  translate_y : Double
  rotation : Double
  scale_x : Double
  scale_y : Double
  skew_x : Double
  skew_y : Double
  tcenter_x : Double
  tcenter_y : Double
} derive(Show, ToJson)

///|
pub fn VarcTransform::apply_to_point(
  self : VarcTransform,
  x : Double,
  y : Double,
) -> (Double, Double) {
  let mut px = x + self.translate_x + self.tcenter_x
  let mut py = y + self.translate_y + self.tcenter_y
  if self.rotation != 0.0 {
    let c = @math.cos(self.rotation)
    let s = @math.sin(self.rotation)
    let rx = px * c - py * s
    let ry = px * s + py * c
    px = rx
    py = ry
  }
  px = px * self.scale_x
  py = py * self.scale_y
  if self.skew_x != 0.0 || self.skew_y != 0.0 {
    let skew_x = if self.skew_x == 0.0 { 0.0 } else { @math.tan(-self.skew_x) }
    let skew_y = if self.skew_y == 0.0 { 0.0 } else { @math.tan(self.skew_y) }
    let sx = px + skew_x * py
    let sy = skew_y * px + py
    px = sx
    py = sy
  }
  px = px - self.tcenter_x
  py = py - self.tcenter_y
  (px, py)
}

///|
pub fn VarcTransform::apply_to_bounds(
  self : VarcTransform,
  x_min : Double,
  y_min : Double,
  x_max : Double,
  y_max : Double,
) -> (Double, Double, Double, Double) {
  let (x0, y0) = self.apply_to_point(x_min, y_min)
  let (x1, y1) = self.apply_to_point(x_min, y_max)
  let (x2, y2) = self.apply_to_point(x_max, y_min)
  let (x3, y3) = self.apply_to_point(x_max, y_max)
  let min_x = min4(x0, x1, x2, x3)
  let max_x = max4(x0, x1, x2, x3)
  let min_y = min4(y0, y1, y2, y3)
  let max_y = max4(y0, y1, y2, y3)
  (min_x, min_y, max_x, max_y)
}

fn min4(a : Double, b : Double, c : Double, d : Double) -> Double {
  let ab = if a < b { a } else { b }
  let cd = if c < d { c } else { d }
  if ab < cd { ab } else { cd }
}

fn max4(a : Double, b : Double, c : Double, d : Double) -> Double {
  let ab = if a > b { a } else { b }
  let cd = if c > d { c } else { d }
  if ab > cd { ab } else { cd }
}

///|
pub struct VarcComponent {
  glyph : Int
  visible : Bool
  axis_indices : Array[Int]
  axis_values : Array[Double]
  coords : Array[Int]
  transform : VarcTransform
} derive(Show, ToJson)

priv struct VarcTransformRaw {
  mut translate_x : Double?
  mut translate_y : Double?
  mut rotation : Double?
  mut scale_x : Double?
  mut scale_y : Double?
  mut skew_x : Double?
  mut skew_y : Double?
  mut tcenter_x : Double?
  mut tcenter_y : Double?
}

let varc_flag_reset_unspecified_axes : UInt = 1U << 0
let varc_flag_have_axes : UInt = 1U << 1
let varc_flag_axis_values_have_variation : UInt = 1U << 2
let varc_flag_transform_has_variation : UInt = 1U << 3
let varc_flag_have_translate_x : UInt = 1U << 4
let varc_flag_have_translate_y : UInt = 1U << 5
let varc_flag_have_rotation : UInt = 1U << 6
let varc_flag_have_condition : UInt = 1U << 7
let varc_flag_have_scale_x : UInt = 1U << 8
let varc_flag_have_scale_y : UInt = 1U << 9
let varc_flag_have_tcenter_x : UInt = 1U << 10
let varc_flag_have_tcenter_y : UInt = 1U << 11
let varc_flag_gid_is_24bit : UInt = 1U << 12
let varc_flag_have_skew_x : UInt = 1U << 13
let varc_flag_have_skew_y : UInt = 1U << 14
let varc_flag_reserved_mask : UInt = 0xFFFF8000U
let varc_no_variation : UInt = 0xFFFFFFFFU
let varc_max_axes : Int = 4096
let varc_pi : Double = 3.141592653589793

fn option_or(value : Double?, default_value : Double) -> Double {
  match value {
    None => default_value
    Some(v) => v
  }
}

fn read_glyph_id_24(data : BytesView, offset : Int) -> Result[Int, VarError] {
  if offset < 0 || offset + 3 > data.length() {
    return Err(UnexpectedEof)
  }
  let b0 = data[offset].to_int()
  let b1 = data[offset + 1].to_int()
  let b2 = data[offset + 2].to_int()
  Ok((b0 << 16) | (b1 << 8) | b2)
}

fn apply_axis_overrides(
  base_coords : Array[Int],
  axis_indices : Array[Int],
  axis_values : Array[Double],
) -> Array[Int] {
  let coords = base_coords.copy()
  let count = if axis_indices.length() < axis_values.length() {
    axis_indices.length()
  } else {
    axis_values.length()
  }
  for i in 0..= varc_max_axes {
      continue
    }
    while coords.length() <= axis_index {
      coords.push(0)
    }
    coords[axis_index] = axis_values[i].to_int()
  }
  coords
}

fn finalize_value(raw : Double, mult : Double, divisor : Double) -> Double {
  (raw * mult).round() / divisor
}

fn finalize_transform(flags : UInt, raw : VarcTransformRaw) -> VarcTransform {
  let mut translate_x = 0.0
  let mut translate_y = 0.0
  let mut rotation = 0.0
  let mut scale_x = 1.0
  let mut scale_y = 1.0
  let mut skew_x = 0.0
  let mut skew_y = 0.0
  let mut tcenter_x = 0.0
  let mut tcenter_y = 0.0

  match raw.translate_x {
    Some(value) => translate_x = finalize_value(value, 1.0, 1.0)
    None => ()
  }
  match raw.translate_y {
    Some(value) => translate_y = finalize_value(value, 1.0, 1.0)
    None => ()
  }
  match raw.rotation {
    Some(value) => rotation = finalize_value(value, varc_pi, 4096.0)
    None => ()
  }
  match raw.scale_x {
    Some(value) => scale_x = finalize_value(value, 1.0, 1024.0)
    None => ()
  }
  match raw.scale_y {
    Some(value) => scale_y = finalize_value(value, 1.0, 1024.0)
    None => ()
  }
  match raw.skew_x {
    Some(value) => skew_x = finalize_value(value, varc_pi, 4096.0)
    None => ()
  }
  match raw.skew_y {
    Some(value) => skew_y = finalize_value(value, varc_pi, 4096.0)
    None => ()
  }
  match raw.tcenter_x {
    Some(value) => tcenter_x = finalize_value(value, 1.0, 1.0)
    None => ()
  }
  match raw.tcenter_y {
    Some(value) => tcenter_y = finalize_value(value, 1.0, 1.0)
    None => ()
  }

  if (flags & varc_flag_have_scale_y) == 0U {
    scale_y = scale_x
  }

  VarcTransform::{
    translate_x,
    translate_y,
    rotation,
    scale_x,
    scale_y,
    skew_x,
    skew_y,
    tcenter_x,
    tcenter_y,
  }
}

fn parse_component(
  data : BytesView,
  offset : Int,
  coords : Array[Int],
  base_coords : Array[Int],
  varc : VarcTable,
  var_store : MultiItemVariationStore?,
  conditions : Array[VarcCondition],
) -> Result[(VarcComponent, Int), VarError] {
  let (flags, size) = match read_u32var(data, offset) {
    Err(err) => return Err(err)
    Ok(value) => value
  }
  let mut pos = offset + size

  let glyph = if (flags & varc_flag_gid_is_24bit) != 0U {
    let glyph = match read_glyph_id_24(data, pos) {
      Err(err) => return Err(err)
      Ok(value) => value
    }
    pos = pos + 3
    glyph
  } else {
    let glyph = match read_u16_int(data, pos) {
      Err(err) => return Err(err)
      Ok(value) => value
    }
    pos = pos + 2
    glyph
  }

  let mut show = true
  if (flags & varc_flag_have_condition) != 0U {
    let (condition_index, cond_size) = match read_u32var(data, pos) {
      Err(err) => return Err(err)
      Ok(value) => value
    }
    pos = pos + cond_size
    let cond_idx = condition_index.reinterpret_as_int()
    if cond_idx < 0 || cond_idx >= conditions.length() {
      return Err(InvalidFormat)
    }
    let var_delta = match var_store {
      None => None
      Some(store) => Some((var_idx : Int) => {
        let var_idx_u = var_idx.reinterpret_as_uint()
        if var_idx_u == varc_no_variation {
          return 0.0
        }
        let out : Array[Double] = [0.0]
        store.get_delta(var_idx_u, coords, out)
        out[0]
      })
    }
    show = conditions[cond_idx].evaluate(coords, var_delta)
  }

  let mut axis_indices : Array[Int] = []
  let axis_values : Array[Double] = []
  if (flags & varc_flag_have_axes) != 0U {
    let (axis_index, axis_size) = match read_u32var(data, pos) {
      Err(err) => return Err(err)
      Ok(value) => value
    }
    pos = pos + axis_size
    axis_indices = match varc.axis_indices(axis_index.reinterpret_as_int()) {
      Err(err) => return Err(err)
      Ok(value) => value
    }
    let (values, consumed) = match decode_tuple_values_count(data[pos:], axis_indices.length()) {
      Err(err) => return Err(err)
      Ok(value) => value
    }
    pos = pos + consumed
    for value in values {
      axis_values.push(value.to_double())
    }
  }

  if (flags & varc_flag_axis_values_have_variation) != 0U {
    let (var_idx, var_size) = match read_u32var(data, pos) {
      Err(err) => return Err(err)
      Ok(value) => value
    }
    pos = pos + var_size
    if show && axis_values.length() > 0 {
      match var_store {
        None => ()
        Some(store) =>
          if var_idx != varc_no_variation {
            store.get_delta(var_idx, coords, axis_values)
          }
      }
    }
  }

  let mut transform_var_idx : UInt? = None
  if (flags & varc_flag_transform_has_variation) != 0U {
    let (var_idx, var_size) = match read_u32var(data, pos) {
      Err(err) => return Err(err)
      Ok(value) => value
    }
    pos = pos + var_size
    transform_var_idx = Some(var_idx)
  }

  let transform_raw = VarcTransformRaw::{
    translate_x: None,
    translate_y: None,
    rotation: None,
    scale_x: None,
    scale_y: None,
    skew_x: None,
    skew_y: None,
    tcenter_x: None,
    tcenter_y: None,
  }

  if (flags & varc_flag_have_translate_x) != 0U {
    let value = match read_i16(data, pos) {
      Err(err) => return Err(err)
      Ok(value) => value
    }
    pos = pos + 2
    transform_raw.translate_x = Some(value.to_double())
  }
  if (flags & varc_flag_have_translate_y) != 0U {
    let value = match read_i16(data, pos) {
      Err(err) => return Err(err)
      Ok(value) => value
    }
    pos = pos + 2
    transform_raw.translate_y = Some(value.to_double())
  }
  if (flags & varc_flag_have_rotation) != 0U {
    let value = match read_i16(data, pos) {
      Err(err) => return Err(err)
      Ok(value) => value
    }
    pos = pos + 2
    transform_raw.rotation = Some(value.to_double())
  }
  if (flags & varc_flag_have_scale_x) != 0U {
    let value = match read_i16(data, pos) {
      Err(err) => return Err(err)
      Ok(value) => value
    }
    pos = pos + 2
    transform_raw.scale_x = Some(value.to_double())
  }
  if (flags & varc_flag_have_scale_y) != 0U {
    let value = match read_i16(data, pos) {
      Err(err) => return Err(err)
      Ok(value) => value
    }
    pos = pos + 2
    transform_raw.scale_y = Some(value.to_double())
  }
  if (flags & varc_flag_have_skew_x) != 0U {
    let value = match read_i16(data, pos) {
      Err(err) => return Err(err)
      Ok(value) => value
    }
    pos = pos + 2
    transform_raw.skew_x = Some(value.to_double())
  }
  if (flags & varc_flag_have_skew_y) != 0U {
    let value = match read_i16(data, pos) {
      Err(err) => return Err(err)
      Ok(value) => value
    }
    pos = pos + 2
    transform_raw.skew_y = Some(value.to_double())
  }
  if (flags & varc_flag_have_tcenter_x) != 0U {
    let value = match read_i16(data, pos) {
      Err(err) => return Err(err)
      Ok(value) => value
    }
    pos = pos + 2
    transform_raw.tcenter_x = Some(value.to_double())
  }
  if (flags & varc_flag_have_tcenter_y) != 0U {
    let value = match read_i16(data, pos) {
      Err(err) => return Err(err)
      Ok(value) => value
    }
    pos = pos + 2
    transform_raw.tcenter_y = Some(value.to_double())
  }

  let mut reserved = flags & varc_flag_reserved_mask
  while reserved != 0U {
    let (_, discard_size) = match read_u32var(data, pos) {
      Err(err) => return Err(err)
      Ok(value) => value
    }
    pos = pos + discard_size
    reserved = reserved & (reserved - 1U)
  }

  if show {
  let transform_values : Array[Double] = []
    if (flags & varc_flag_have_translate_x) != 0U {
      transform_values.push(option_or(transform_raw.translate_x, 0.0))
    }
    if (flags & varc_flag_have_translate_y) != 0U {
      transform_values.push(option_or(transform_raw.translate_y, 0.0))
    }
    if (flags & varc_flag_have_rotation) != 0U {
      transform_values.push(option_or(transform_raw.rotation, 0.0))
    }
    if (flags & varc_flag_have_scale_x) != 0U {
      transform_values.push(option_or(transform_raw.scale_x, 0.0))
    }
    if (flags & varc_flag_have_scale_y) != 0U {
      transform_values.push(option_or(transform_raw.scale_y, 0.0))
    }
    if (flags & varc_flag_have_skew_x) != 0U {
      transform_values.push(option_or(transform_raw.skew_x, 0.0))
    }
    if (flags & varc_flag_have_skew_y) != 0U {
      transform_values.push(option_or(transform_raw.skew_y, 0.0))
    }
    if (flags & varc_flag_have_tcenter_x) != 0U {
      transform_values.push(option_or(transform_raw.tcenter_x, 0.0))
    }
    if (flags & varc_flag_have_tcenter_y) != 0U {
      transform_values.push(option_or(transform_raw.tcenter_y, 0.0))
    }

    match (transform_var_idx, var_store) {
      (Some(var_idx), Some(store)) =>
        if var_idx != varc_no_variation && transform_values.length() > 0 {
          store.get_delta(var_idx, coords, transform_values)
        }
      _ => ()
    }

    let mut index = 0
    if (flags & varc_flag_have_translate_x) != 0U {
      transform_raw.translate_x = Some(transform_values[index]);
      index = index + 1
    }
    if (flags & varc_flag_have_translate_y) != 0U {
      transform_raw.translate_y = Some(transform_values[index]);
      index = index + 1
    }
    if (flags & varc_flag_have_rotation) != 0U {
      transform_raw.rotation = Some(transform_values[index]);
      index = index + 1
    }
    if (flags & varc_flag_have_scale_x) != 0U {
      transform_raw.scale_x = Some(transform_values[index]);
      index = index + 1
    }
    if (flags & varc_flag_have_scale_y) != 0U {
      transform_raw.scale_y = Some(transform_values[index]);
      index = index + 1
    }
    if (flags & varc_flag_have_skew_x) != 0U {
      transform_raw.skew_x = Some(transform_values[index]);
      index = index + 1
    }
    if (flags & varc_flag_have_skew_y) != 0U {
      transform_raw.skew_y = Some(transform_values[index]);
      index = index + 1
    }
    if (flags & varc_flag_have_tcenter_x) != 0U {
      transform_raw.tcenter_x = Some(transform_values[index]);
      index = index + 1
    }
    if (flags & varc_flag_have_tcenter_y) != 0U {
      transform_raw.tcenter_y = Some(transform_values[index]);
      index = index + 1
    }
  }

  let mut component_coords = if (flags & varc_flag_reset_unspecified_axes) != 0U || coords.length() > varc_max_axes {
    base_coords.copy()
  } else {
    coords.copy()
  }
  if axis_indices.length() > 0 {
    component_coords = apply_axis_overrides(component_coords, axis_indices, axis_values)
  }

  let transform = finalize_transform(flags, transform_raw)

  let component = VarcComponent::{
    glyph,
    visible: show,
    axis_indices,
    axis_values,
    coords: component_coords,
    transform,
  }

  Ok((component, pos))
}

///|
pub fn VarcTable::glyph_components(
  self : VarcTable,
  glyph : Int,
  coords : Array[Int],
  base_coords : Array[Int],
) -> Result[Array[VarcComponent], VarError] {
  let record = match self.glyph_record(glyph) {
    Err(err) => return Err(err)
    Ok(value) => value
  }
  match record {
    None => Ok([])
    Some(bytes) => {
      let var_store = match self.var_store() {
        Err(err) => return Err(err)
        Ok(value) => value
      }
      let conditions = match self.conditions() {
        Err(err) => return Err(err)
        Ok(value) => value
      }
      let data = bytes[:]
      let components : Array[VarcComponent] = []
      let mut pos = 0
      while pos < data.length() {
        let (component, next_pos) = match parse_component(
          data,
          pos,
          coords,
          base_coords,
          self,
          var_store,
          conditions,
        ) {
          Err(err) => return Err(err)
          Ok(value) => value
        }
        components.push(component)
        if next_pos <= pos {
          return Err(InvalidFormat)
        }
        pos = next_pos
      }
      Ok(components)
    }
  }
}