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

///|
/// Convert autohint outlines back into `PathElement` streams.
///
/// This reuses the same "FreeType-style" quadratic contour rules as the glyf
/// path conversion.

///|
fn autohint_f26dot6_to_double(bits : Int) -> Double {
  bits.to_double() / 64.0
}

///|
fn autohint_midpoint_i32(a : Int, b : Int) -> Int {
  (a + b) / 2
}

///|
fn autohint_emit_contour(
  out : Array[PathElement],
  outline : AutoHintOutline,
  start : Int,
  end : Int,
  path_style : PathStyle,
) -> Unit {
  let n = end - start + 1
  if n <= 0 {
    return
  }
  let first = outline.points.at(start)
  let last = outline.points.at(end)
  let (sx, sy, start_idx) = if first.is_on_curve() {
    (first.x, first.y, start)
  } else {
    match path_style {
      FreeType =>
        if last.is_on_curve() {
          (last.x, last.y, end)
        } else {
          (
            autohint_midpoint_i32(last.x, first.x),
            autohint_midpoint_i32(last.y, first.y),
            -1,
          )
        }
      HarfBuzz =>
        if n >= 2 {
          let second = outline.points.at(start + 1)
          if second.is_on_curve() {
            (second.x, second.y, start + 1)
          } else {
            (
              autohint_midpoint_i32(first.x, second.x),
              autohint_midpoint_i32(first.y, second.y),
              -1,
            )
          }
        } else {
          (first.x, first.y, start)
        }
    }
  }
  out.push(
    MoveTo(autohint_f26dot6_to_double(sx), autohint_f26dot6_to_double(sy)),
  )
  let mut control : (Int, Int)? = None
  if start_idx == -1 {
    for i in start..<(end + 1) {
      let p = outline.points.at(i)
      if p.is_on_curve() {
        match control {
          None =>
            out.push(
              LineTo(
                autohint_f26dot6_to_double(p.x),
                autohint_f26dot6_to_double(p.y),
              ),
            )
          Some((cx, cy)) => {
            out.push(
              QuadTo(
                autohint_f26dot6_to_double(cx),
                autohint_f26dot6_to_double(cy),
                autohint_f26dot6_to_double(p.x),
                autohint_f26dot6_to_double(p.y),
              ),
            )
            control = None
          }
        }
      } else {
        match control {
          None => control = Some((p.x, p.y))
          Some((cx, cy)) => {
            let mx = autohint_midpoint_i32(cx, p.x)
            let my = autohint_midpoint_i32(cy, p.y)
            out.push(
              QuadTo(
                autohint_f26dot6_to_double(cx),
                autohint_f26dot6_to_double(cy),
                autohint_f26dot6_to_double(mx),
                autohint_f26dot6_to_double(my),
              ),
            )
            control = Some((p.x, p.y))
          }
        }
      }
    }
  } else {
    let base = start_idx - start
    for step in 1..
            out.push(
              LineTo(
                autohint_f26dot6_to_double(p.x),
                autohint_f26dot6_to_double(p.y),
              ),
            )
          Some((cx, cy)) => {
            out.push(
              QuadTo(
                autohint_f26dot6_to_double(cx),
                autohint_f26dot6_to_double(cy),
                autohint_f26dot6_to_double(p.x),
                autohint_f26dot6_to_double(p.y),
              ),
            )
            control = None
          }
        }
      } else {
        match control {
          None => control = Some((p.x, p.y))
          Some((cx, cy)) => {
            let mx = autohint_midpoint_i32(cx, p.x)
            let my = autohint_midpoint_i32(cy, p.y)
            out.push(
              QuadTo(
                autohint_f26dot6_to_double(cx),
                autohint_f26dot6_to_double(cy),
                autohint_f26dot6_to_double(mx),
                autohint_f26dot6_to_double(my),
              ),
            )
            control = Some((p.x, p.y))
          }
        }
      }
    }
  }
  match control {
    Some((cx, cy)) =>
      out.push(
        QuadTo(
          autohint_f26dot6_to_double(cx),
          autohint_f26dot6_to_double(cy),
          autohint_f26dot6_to_double(sx),
          autohint_f26dot6_to_double(sy),
        ),
      )
    None => ()
  }
  out.push(Close)
}

///|
fn autohint_outline_to_path(
  outline : AutoHintOutline,
  path_style : PathStyle,
) -> Array[PathElement] {
  let out : Array[PathElement] = Array::new()
  for contour in outline.contours.iter() {
    let start = contour.first_ix
    let end = contour.last_ix
    if start < 0 || end < start || end >= outline.points.length() {
      continue
    }
    autohint_emit_contour(out, outline, start, end, path_style)
  }
  out
}