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

let f16dot16_scale : Double = 1.0 / 65536.0

///|
/// Track table entry.
pub(all) struct TrackTableEntry {
  track : Int
  name_id : Int
  values : Array[Int]
} derive(Eq, Show, ToJson)

fn TrackTableEntry::track_value(self : TrackTableEntry) -> Double {
  self.track.to_double() * f16dot16_scale
}

fn TrackTableEntry::value_for_size(
  self : TrackTableEntry,
  ptem : Double,
  size_table : Array[Int],
) -> Double {
  let n_sizes = size_table.length()
  if n_sizes == 0 {
    return 0.0
  }
  if n_sizes == 1 {
    return self.values[0].to_double()
  }

  let mut i = 0
  for idx in 0..= ptem {
      i = idx
      break
    }
    if idx == n_sizes - 1 {
      i = n_sizes
    }
  }

  if i == 0 {
    return self.values[0].to_double()
  }
  if i >= n_sizes {
    return self.values[n_sizes - 1].to_double()
  }

  let s0 = size_table[i - 1].to_double() * f16dot16_scale
  let s1 = size_table[i].to_double() * f16dot16_scale
  let v0 = self.values[i - 1].to_double()
  let v1 = self.values[i].to_double()

  if s1 < s0 {
    if ptem <= s1 {
      return v1
    }
    if ptem >= s0 {
      return v0
    }
    if s0 == s1 {
      return (v0 + v1) * 0.5
    }
    let t = (ptem - s1) / (s0 - s1)
    return v1 + t * (v0 - v1)
  }

  if ptem <= s0 {
    return v0
  }
  if ptem >= s1 {
    return v1
  }
  if s0 == s1 {
    return (v0 + v1) * 0.5
  }
  let t = (ptem - s0) / (s1 - s0)
  v0 + t * (v1 - v0)
}

///|
/// Track data for a direction.
pub(all) struct TrackData {
  track_table : Array[TrackTableEntry]
  size_table : Array[Int]
} derive(Eq, Show, ToJson)

fn TrackData::tracking(
  self : TrackData,
  ptem : Double,
  track : Double,
) -> Double {
  let count = self.track_table.length()
  if count == 0 {
    return 0.0
  }
  if count == 1 {
    return self.track_table[0].value_for_size(ptem, self.size_table)
  }

  let mut i = 0
  let mut j = count - 1
  while i + 1 < count && self.track_table[i + 1].track_value() <= track {
    i = i + 1
  }
  while j > 0 && self.track_table[j - 1].track_value() >= track {
    j = j - 1
  }

  if i == j {
    return self.track_table[i].value_for_size(ptem, self.size_table)
  }

  let t0 = self.track_table[i].track_value()
  let t1 = self.track_table[j].track_value()
  let t = (track - t0) / (t1 - t0)
  let a = self.track_table[i].value_for_size(ptem, self.size_table)
  let b = self.track_table[j].value_for_size(ptem, self.size_table)
  a + t * (b - a)
}

///|
/// Parsed trak table.
pub struct TrakTable {
  version : Int
  format : Int
  horiz : TrackData?
  vert : TrackData?
} derive(Eq, Show, ToJson)

///|
/// Parse a trak table.
pub fn TrakTable::parse(data : BytesView) -> Result[TrakTable, AatError] {
  let version = read_i32(data, 0)
  let format = read_u16_int(data, 4)
  let horiz_offset = read_u16_int(data, 6)
  let vert_offset = read_u16_int(data, 8)
  let reserved = read_u16_int(data, 10)
  match (version, format, horiz_offset, vert_offset, reserved) {
    (Err(err), _, _, _, _) => Err(err)
    (_, Err(err), _, _, _) => Err(err)
    (_, _, Err(err), _, _) => Err(err)
    (_, _, _, Err(err), _) => Err(err)
    (_, _, _, _, Err(err)) => Err(err)
    (Ok(version), Ok(format), Ok(horiz_offset), Ok(vert_offset), Ok(_)) => {
      let mut horiz : TrackData? = None
      let mut vert : TrackData? = None
      if horiz_offset != 0 {
        let parsed = parse_track_data(data, horiz_offset)
        match parsed {
          Err(err) => return Err(err)
          Ok(value) => horiz = Some(value)
        }
      }
      if vert_offset != 0 {
        let parsed = parse_track_data(data, vert_offset)
        match parsed {
          Err(err) => return Err(err)
          Ok(value) => vert = Some(value)
        }
      }
      Ok(TrakTable::{ version, format, horiz, vert })
    }
  }
}

///|
/// True if trak table has usable data.
pub fn TrakTable::has_data(self : TrakTable) -> Bool {
  self.version != 0
}

///|
/// Compute tracking adjustment in design units.
pub fn TrakTable::tracking(
  self : TrakTable,
  ptem : Double,
  horizontal : Bool,
  track? : Double = 0.0,
) -> Double {
  let data = if horizontal { self.horiz } else { self.vert }
  match data {
    None => 0.0
    Some(data) => data.tracking(ptem, track)
  }
}

fn parse_track_data(
  data : BytesView,
  offset : Int,
) -> Result[TrackData, AatError] {
  if offset < 0 || offset + 8 > data.length() {
    return Err(UnexpectedEof)
  }
  let n_tracks = read_u16_int(data, offset)
  let n_sizes = read_u16_int(data, offset + 2)
  let size_table_offset = read_u32_int(data, offset + 4)
  match (n_tracks, n_sizes, size_table_offset) {
    (Err(err), _, _) => Err(err)
    (_, Err(err), _) => Err(err)
    (_, _, Err(err)) => Err(err)
    (Ok(n_tracks), Ok(n_sizes), Ok(size_table_offset)) => {
      if n_tracks < 0 || n_sizes < 0 {
        return Err(InvalidFormat)
      }
      let size_abs = size_table_offset
      if size_abs < 0 || size_abs + n_sizes * 4 > data.length() {
        return Err(UnexpectedEof)
      }
      let size_table : Array[Int] = []
      for i in 0.. return Err(err)
          Ok(value) => size_table.push(value)
        }
      }

      let entry_size = 8
      let table_offset = offset + 8
      if table_offset < 0 || table_offset + n_tracks * entry_size > data.length() {
        return Err(UnexpectedEof)
      }
      let track_table : Array[TrackTableEntry] = []
      for i in 0.. return Err(err)
          (_, Err(err), _) => return Err(err)
          (_, _, Err(err)) => return Err(err)
          (Ok(track), Ok(name_id), Ok(values_offset)) => {
            let values_abs = values_offset
            if values_abs < 0 || values_abs + n_sizes * 2 > data.length() {
              return Err(UnexpectedEof)
            }
            let values : Array[Int] = []
            for j in 0.. return Err(err)
                Ok(value) => values.push(value)
              }
            }
            track_table.push(TrackTableEntry::{ track, name_id, values })
          }
        }
      }
      Ok(TrackData::{ track_table, size_table })
    }
  }
}