// 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 enum CffNumber {
  Int(Int)
  Real(Double)
} derive(Eq, Show, ToJson)

///|
pub struct CffDict {
  entries : Map[Int, Array[CffNumber]]
} derive(Eq, Show, ToJson)

///|
pub fn parse_cff_dict(data : BytesView) -> Result[CffDict, CffError] {
  let mut pos = 0
  let mut operands : Array[CffNumber] = []
  let entries : Map[Int, Array[CffNumber]] = {}
  while pos < data.length() {
    let b0 = data[pos].to_int()
    if b0 == 12 {
      if pos + 1 >= data.length() {
        return Err(UnexpectedEof)
      }
      let b1 = data[pos + 1].to_int()
      let op = (12 << 8) | b1
      entries[op] = operands.copy()
      operands = []
      pos = pos + 2
      continue
    }
    if b0 <= 27 {
      entries[b0] = operands.copy()
      operands = []
      pos = pos + 1
      continue
    }
    let parsed = parse_cff_number(data, pos)
    match parsed {
      Err(err) => return Err(err)
      Ok((num, next_pos)) => {
        operands.push(num)
        pos = next_pos
      }
    }
  }
  Ok(CffDict::{ entries })
}

///|
pub fn CffDict::get_numbers(self : CffDict, op : Int) -> Array[CffNumber]? {
  self.entries.get(op)
}

///|
pub fn CffDict::entries_map(self : CffDict) -> Map[Int, Array[CffNumber]] {
  let next : Map[Int, Array[CffNumber]] = {}
  self.entries.eachi((_, key, value) => {
    next[key] = value.copy()
  })
  next
}

///|
pub fn CffDict::get_int(self : CffDict, op : Int, index? : Int = 0) -> Int? {
  match self.entries.get(op) {
    None => None
    Some(values) => {
      if index < 0 || index >= values.length() {
        return None
      }
      match values[index] {
        Int(value) => Some(value)
        Real(value) => Some(value.round().to_int())
      }
    }
  }
}

///|
pub fn CffDict::get_real(self : CffDict, op : Int, index? : Int = 0) -> Double? {
  match self.entries.get(op) {
    None => None
    Some(values) => {
      if index < 0 || index >= values.length() {
        return None
      }
      match values[index] {
        Int(value) => Some(value.to_double())
        Real(value) => Some(value)
      }
    }
  }
}

fn parse_cff_number(
  data : BytesView,
  pos : Int,
) -> Result[(CffNumber, Int), CffError] {
  if pos < 0 || pos >= data.length() {
    return Err(UnexpectedEof)
  }
  let b0 = data[pos].to_int()
  if b0 >= 32 && b0 <= 246 {
    return Ok((Int(b0 - 139), pos + 1))
  }
  if b0 >= 247 && b0 <= 250 {
    if pos + 1 >= data.length() {
      return Err(UnexpectedEof)
    }
    let b1 = data[pos + 1].to_int()
    let value = (b0 - 247) * 256 + b1 + 108
    return Ok((Int(value), pos + 2))
  }
  if b0 >= 251 && b0 <= 254 {
    if pos + 1 >= data.length() {
      return Err(UnexpectedEof)
    }
    let b1 = data[pos + 1].to_int()
    let value = -(b0 - 251) * 256 - b1 - 108
    return Ok((Int(value), pos + 2))
  }
  if b0 == 28 {
    if pos + 2 >= data.length() {
      return Err(UnexpectedEof)
    }
    let hi = data[pos + 1].to_int()
    let lo = data[pos + 2].to_int()
    let raw = (hi << 8) | lo
    let value = if raw >= 0x8000 { raw - 0x10000 } else { raw }
    return Ok((Int(value), pos + 3))
  }
  if b0 == 29 {
    if pos + 4 >= data.length() {
      return Err(UnexpectedEof)
    }
    let b1 = data[pos + 1].to_int()
    let b2 = data[pos + 2].to_int()
    let b3 = data[pos + 3].to_int()
    let b4 = data[pos + 4].to_int()
    let value = (b1 << 24) | (b2 << 16) | (b3 << 8) | b4
    return Ok((Int(value), pos + 5))
  }
  if b0 == 30 {
    return parse_cff_real(data, pos)
  }
  Err(InvalidFormat)
}

fn parse_cff_real(
  data : BytesView,
  pos : Int,
) -> Result[(CffNumber, Int), CffError] {
  let digits : Array[Char] = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
  let chars : Array[Char] = []
  let mut idx = pos + 1
  let mut done = false
  while idx < data.length() && !done {
    let byte = data[idx].to_int()
    let hi = (byte >> 4) & 0xF
    let lo = byte & 0xF
    let nibbles : Array[Int] = [hi, lo]
    for nib in nibbles {
      if done {
        break
      }
      if nib >= 0 && nib <= 9 {
        chars.push(digits[nib])
        continue
      }
      match nib {
        0xA => chars.push('.')
        0xB => chars.push('E')
        0xC => {
          chars.push('E')
          chars.push('-')
        }
        0xE => chars.push('-')
        0xF => done = true
        0xD => return Err(InvalidFormat)
        _ => return Err(InvalidFormat)
      }
    }
    idx = idx + 1
  }
  if !done {
    return Err(UnexpectedEof)
  }
  let value_str = String::from_array(chars)
  match @common.parse_double(value_str[:], whole=true) {
    None => Err(InvalidFormat)
    Some((value, _)) => Ok((Real(value), idx))
  }
}