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

///|
fn push_u16(bytes : Array[Byte], value : Int) -> Unit {
  bytes.push(((value >> 8) & 0xff).to_byte())
  bytes.push((value & 0xff).to_byte())
}

fn push_u32(bytes : Array[Byte], value : Int) -> Unit {
  bytes.push(((value >> 24) & 0xff).to_byte())
  bytes.push(((value >> 16) & 0xff).to_byte())
  bytes.push(((value >> 8) & 0xff).to_byte())
  bytes.push((value & 0xff).to_byte())
}

fn push_count(bytes : Array[Byte], count : Int, count_bytes : Int) -> Result[Unit, CffError] {
  if count < 0 {
    return Err(InvalidFormat)
  }
  if count_bytes == 2 {
    if count > 0xFFFF {
      return Err(InvalidFormat)
    }
    push_u16(bytes, count)
    return Ok(())
  }
  if count_bytes == 4 {
    push_u32(bytes, count)
    return Ok(())
  }
  Err(InvalidFormat)
}

fn offset_size_for(value : Int) -> Result[Int, CffError] {
  if value <= 0 {
    return Err(InvalidFormat)
  }
  if value <= 0xFF {
    return Ok(1)
  }
  if value <= 0xFFFF {
    return Ok(2)
  }
  if value <= 0xFFFFFF {
    return Ok(3)
  }
  if value <= 0x7FFFFFFF {
    return Ok(4)
  }
  Err(InvalidFormat)
}

fn push_offset(bytes : Array[Byte], value : Int, size : Int) -> Result[Unit, CffError] {
  if value < 0 {
    return Err(InvalidFormat)
  }
  if size <= 0 || size > 4 {
    return Err(InvalidFormat)
  }
  for i in 0..> shift) & 0xff).to_byte())
  }
  Ok(())
}

fn write_cff_index(items : Array[Bytes], count_bytes : Int) -> Result[Bytes, CffError] {
  let bytes : Array[Byte] = []
  let count = items.length()
  match push_count(bytes, count, count_bytes) {
    Err(err) => return Err(err)
    Ok(_) => ()
  }
  if count == 0 {
    return Ok(Bytes::from_array(bytes))
  }
  let offsets : Array[Int] = []
  let mut current = 1
  offsets.push(current)
  for item in items {
    current = current + item.length()
    offsets.push(current)
  }
  let off_size = match offset_size_for(current) {
    Err(err) => return Err(err)
    Ok(value) => value
  }
  bytes.push(off_size.to_byte())
  for off in offsets {
    match push_offset(bytes, off, off_size) {
      Err(err) => return Err(err)
      Ok(_) => ()
    }
  }
  for item in items {
    for b in item {
      bytes.push(b)
    }
  }
  Ok(Bytes::from_array(bytes))
}

fn write_cff_int(bytes : Array[Byte], value : Int) -> Unit {
  if value >= -107 && value <= 107 {
    bytes.push((value + 139).to_byte())
    return
  }
  if value >= 108 && value <= 1131 {
    let v = value - 108
    bytes.push(((v / 256) + 247).to_byte())
    bytes.push((v % 256).to_byte())
    return
  }
  if value <= -108 && value >= -1131 {
    let v = -value - 108
    bytes.push(((v / 256) + 251).to_byte())
    bytes.push((v % 256).to_byte())
    return
  }
  if value >= -32768 && value <= 32767 {
    let raw = if value < 0 { value + 0x10000 } else { value }
    bytes.push(28)
    bytes.push(((raw >> 8) & 0xff).to_byte())
    bytes.push((raw & 0xff).to_byte())
    return
  }
  bytes.push(29)
  bytes.push(((value >> 24) & 0xff).to_byte())
  bytes.push(((value >> 16) & 0xff).to_byte())
  bytes.push(((value >> 8) & 0xff).to_byte())
  bytes.push((value & 0xff).to_byte())
}

fn write_cff_real(bytes : Array[Byte], value : Double) -> Result[Unit, CffError] {
  if value.is_nan() || value.is_inf() {
    return Err(InvalidFormat)
  }
  let text = value.to_string()
  if text == "NaN" || text == "Infinity" || text == "-Infinity" {
    return Err(InvalidFormat)
  }
  let chars = text.to_array()
  let nibbles : Array[Int] = []
  let mut i = 0
  while i < chars.length() {
    let ch = chars[i]
    if ch >= '0' && ch <= '9' {
      nibbles.push(ch.to_int() - '0'.to_int())
      i = i + 1
      continue
    }
    match ch {
      '.' => nibbles.push(0xA)
      '-' => nibbles.push(0xE)
      'e' | 'E' => {
        if i + 1 < chars.length() && chars[i + 1] == '-' {
          nibbles.push(0xC)
          i = i + 1
        } else {
          nibbles.push(0xB)
          if i + 1 < chars.length() && chars[i + 1] == '+' {
            i = i + 1
          }
        }
      }
      '+' => ()
      _ => return Err(InvalidFormat)
    }
    i = i + 1
  }
  nibbles.push(0xF)
  if (nibbles.length() % 2) != 0 {
    nibbles.push(0xF)
  }
  bytes.push(30)
  let mut idx = 0
  while idx + 1 < nibbles.length() {
    let byte = (nibbles[idx] << 4) | nibbles[idx + 1]
    bytes.push(byte.to_byte())
    idx = idx + 2
  }
  Ok(())
}

fn write_cff_number(bytes : Array[Byte], value : CffNumber) -> Result[Unit, CffError] {
  match value {
    Int(value) => {
      write_cff_int(bytes, value)
      Ok(())
    }
    Real(value) => write_cff_real(bytes, value)
  }
}

fn sort_ints(values : Array[Int]) -> Array[Int] {
  let sorted = values.copy()
  let n = sorted.length()
  for i in 1..= 0 && sorted[j] > key {
      sorted[j + 1] = sorted[j]
      j = j - 1
    }
    sorted[j + 1] = key
  }
  sorted
}

fn write_cff_dict(entries : Map[Int, Array[CffNumber]]) -> Result[Bytes, CffError] {
  let ops = sort_ints(entries.keys().to_array())
  let bytes : Array[Byte] = []
  for op in ops {
    let operands = match entries.get(op) {
      None => continue
      Some(values) => values
    }
    for operand in operands {
      match write_cff_number(bytes, operand) {
        Err(err) => return Err(err)
        Ok(_) => ()
      }
    }
    if op >= 0x100 {
      bytes.push(12)
      bytes.push((op & 0xff).to_byte())
    } else {
      bytes.push(op.to_byte())
    }
  }
  Ok(Bytes::from_array(bytes))
}