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

///|
/// Base coordinate formats.
pub enum BaseCoord {
  Format1(Int)
  Format2(Int, Int, Int)
  Format3(Int, Int)
} derive(Show, ToJson)

///|
/// Feature min/max record.
pub struct FeatMinMaxRecord {
  tag : @common.Tag
  min_coord : BaseCoord?
  max_coord : BaseCoord?
} derive(Show, ToJson)

///|
/// Min/max table.
pub struct MinMax {
  min_coord : BaseCoord?
  max_coord : BaseCoord?
  records : Array[FeatMinMaxRecord]
} derive(Show, ToJson)

///|
/// Base values table.
pub struct BaseValues {
  default_index : Int
  base_coords : Array[BaseCoord?]
} derive(Show, ToJson)

///|
/// Base language system record.
pub struct BaseLangSysRecord {
  tag : @common.Tag
  min_max : MinMax?
} derive(Show, ToJson)

///|
/// Base script table.
pub struct BaseScript {
  base_values : BaseValues?
  default_min_max : MinMax?
  lang_sys_records : Array[BaseLangSysRecord]
} derive(Show, ToJson)

///|
/// Base script record.
pub struct BaseScriptRecord {
  tag : @common.Tag
  base_script : BaseScript?
} derive(Show, ToJson)

///|
/// Base script list.
pub struct BaseScriptList {
  records : Array[BaseScriptRecord]
} derive(Show, ToJson)

///|
/// Base tag list.
pub struct BaseTagList {
  tags : Array[@common.Tag]
} derive(Show, ToJson)

///|
/// Axis table.
pub struct Axis {
  base_tags : BaseTagList?
  base_scripts : BaseScriptList?
} derive(Show, ToJson)

///|
/// Parsed BASE table.
pub struct BaseTable {
  major_version : Int
  minor_version : Int
  h_axis : Axis?
  v_axis : Axis?
  var_store_offset : Int?
} derive(Show, ToJson)

///|
fn parse_base_coord(data : BytesView, offset : Int) -> Result[BaseCoord, SfntError] {
  let format = read_u16_int(data, offset)
  match format {
    Err(err) => Err(err)
    Ok(format) =>
      if format == 1 {
        match read_i16(data, offset + 2) {
          Err(err) => Err(err)
          Ok(value) => Ok(Format1(value))
        }
      } else if format == 2 {
        let coord = read_i16(data, offset + 2)
        let reference_glyph = read_u16_int(data, offset + 4)
        let coord_point = read_u16_int(data, offset + 6)
        match (coord, reference_glyph, coord_point) {
          (Err(err), _, _) => Err(err)
          (_, Err(err), _) => Err(err)
          (_, _, Err(err)) => Err(err)
          (Ok(coord), Ok(reference_glyph), Ok(coord_point)) =>
            Ok(Format2(coord, reference_glyph, coord_point))
        }
      } else if format == 3 {
        let coord = read_i16(data, offset + 2)
        let device_offset = read_u16_int(data, offset + 4)
        match (coord, device_offset) {
          (Err(err), _) => Err(err)
          (_, Err(err)) => Err(err)
          (Ok(coord), Ok(device_offset)) => {
            if device_offset < 0 {
              return Err(InvalidFormat)
            }
            if device_offset != 0 {
              let absolute = offset + device_offset
              if absolute < 0 || absolute > data.length() {
                return Err(UnexpectedEof)
              }
            }
            Ok(Format3(coord, device_offset))
          }
        }
      } else {
        Err(InvalidFormat)
      }
  }
}

///|
fn parse_feat_min_max_record(
  data : BytesView,
  offset : Int,
  base : Int,
) -> Result[FeatMinMaxRecord, SfntError] {
  if offset < 0 || offset + 8 > data.length() {
    return Err(UnexpectedEof)
  }
  let tag = @common.Tag::from_bytes(
    data[offset],
    data[offset + 1],
    data[offset + 2],
    data[offset + 3],
  )
  let min_offset = read_u16_int(data, offset + 4)
  let max_offset = read_u16_int(data, offset + 6)
  match (min_offset, max_offset) {
    (Err(err), _) => Err(err)
    (_, Err(err)) => Err(err)
    (Ok(min_offset), Ok(max_offset)) => {
      if min_offset < 0 || max_offset < 0 {
        return Err(InvalidFormat)
      }
      let min_coord = if min_offset == 0 {
        None
      } else {
        Some(match parse_base_coord(data, base + min_offset) {
          Err(err) => return Err(err)
          Ok(value) => value
        })
      }
      let max_coord = if max_offset == 0 {
        None
      } else {
        Some(match parse_base_coord(data, base + max_offset) {
          Err(err) => return Err(err)
          Ok(value) => value
        })
      }
      Ok(FeatMinMaxRecord::{ tag, min_coord, max_coord })
    }
  }
}

///|
fn parse_min_max(data : BytesView, offset : Int) -> Result[MinMax, SfntError] {
  let min_offset = read_u16_int(data, offset)
  let max_offset = read_u16_int(data, offset + 2)
  let count = read_u16_int(data, offset + 4)
  match (min_offset, max_offset, count) {
    (Err(err), _, _) => Err(err)
    (_, Err(err), _) => Err(err)
    (_, _, Err(err)) => Err(err)
    (Ok(min_offset), Ok(max_offset), Ok(count)) => {
      if min_offset < 0 || max_offset < 0 || count < 0 {
        return Err(InvalidFormat)
      }
      let min_coord = if min_offset == 0 {
        None
      } else {
        Some(match parse_base_coord(data, offset + min_offset) {
          Err(err) => return Err(err)
          Ok(value) => value
        })
      }
      let max_coord = if max_offset == 0 {
        None
      } else {
        Some(match parse_base_coord(data, offset + max_offset) {
          Err(err) => return Err(err)
          Ok(value) => value
        })
      }
      let records : Array[FeatMinMaxRecord] = []
      let record_base = offset + 6
      if record_base + count * 8 > data.length() {
        return Err(UnexpectedEof)
      }
      for i in 0.. return Err(err)
          Ok(value) => value
        }
        records.push(record)
      }
      Ok(MinMax::{ min_coord, max_coord, records })
    }
  }
}

///|
fn parse_base_values(data : BytesView, offset : Int) -> Result[BaseValues, SfntError] {
  let default_index = read_u16_int(data, offset)
  let count = read_u16_int(data, offset + 2)
  match (default_index, count) {
    (Err(err), _) => Err(err)
    (_, Err(err)) => Err(err)
    (Ok(default_index), Ok(count)) => {
      if default_index < 0 || count < 0 {
        return Err(InvalidFormat)
      }
      let base_coords : Array[BaseCoord?] = []
      let offsets_base = offset + 4
      if offsets_base + count * 2 > data.length() {
        return Err(UnexpectedEof)
      }
      for i in 0.. return Err(err)
          Ok(coord_offset) => {
            if coord_offset < 0 {
              return Err(InvalidFormat)
            }
            if coord_offset == 0 {
              base_coords.push(None)
            } else {
              let coord = match parse_base_coord(data, offset + coord_offset) {
                Err(err) => return Err(err)
                Ok(value) => value
              }
              base_coords.push(Some(coord))
            }
          }
        }
      }
      Ok(BaseValues::{ default_index, base_coords })
    }
  }
}

///|
fn parse_base_lang_sys_record(
  data : BytesView,
  offset : Int,
  base : Int,
) -> Result[BaseLangSysRecord, SfntError] {
  if offset < 0 || offset + 6 > data.length() {
    return Err(UnexpectedEof)
  }
  let tag = @common.Tag::from_bytes(
    data[offset],
    data[offset + 1],
    data[offset + 2],
    data[offset + 3],
  )
  let min_max_offset = read_u16_int(data, offset + 4)
  match min_max_offset {
    Err(err) => Err(err)
    Ok(min_max_offset) => {
      if min_max_offset < 0 {
        return Err(InvalidFormat)
      }
      let min_max = if min_max_offset == 0 {
        None
      } else {
        Some(match parse_min_max(data, base + min_max_offset) {
          Err(err) => return Err(err)
          Ok(value) => value
        })
      }
      Ok(BaseLangSysRecord::{ tag, min_max })
    }
  }
}

///|
fn parse_base_script(data : BytesView, offset : Int) -> Result[BaseScript, SfntError] {
  let base_values_offset = read_u16_int(data, offset)
  let default_minmax_offset = read_u16_int(data, offset + 2)
  let count = read_u16_int(data, offset + 4)
  match (base_values_offset, default_minmax_offset, count) {
    (Err(err), _, _) => Err(err)
    (_, Err(err), _) => Err(err)
    (_, _, Err(err)) => Err(err)
    (Ok(base_values_offset), Ok(default_minmax_offset), Ok(count)) => {
      if base_values_offset < 0 || default_minmax_offset < 0 || count < 0 {
        return Err(InvalidFormat)
      }
      let base_values = if base_values_offset == 0 {
        None
      } else {
        Some(match parse_base_values(data, offset + base_values_offset) {
          Err(err) => return Err(err)
          Ok(value) => value
        })
      }
      let default_min_max = if default_minmax_offset == 0 {
        None
      } else {
        Some(match parse_min_max(data, offset + default_minmax_offset) {
          Err(err) => return Err(err)
          Ok(value) => value
        })
      }
      let lang_sys_records : Array[BaseLangSysRecord] = []
      let record_base = offset + 6
      if record_base + count * 6 > data.length() {
        return Err(UnexpectedEof)
      }
      for i in 0.. return Err(err)
          Ok(value) => value
        }
        lang_sys_records.push(record)
      }
      Ok(BaseScript::{ base_values, default_min_max, lang_sys_records })
    }
  }
}

///|
fn parse_base_script_list(data : BytesView, offset : Int) -> Result[BaseScriptList, SfntError] {
  let count = read_u16_int(data, offset)
  match count {
    Err(err) => Err(err)
    Ok(count) => {
      if count < 0 {
        return Err(InvalidFormat)
      }
      let records : Array[BaseScriptRecord] = []
      let record_base = offset + 2
      if record_base + count * 6 > data.length() {
        return Err(UnexpectedEof)
      }
      for i in 0.. return Err(err)
          Ok(script_offset) => {
            if script_offset < 0 {
              return Err(InvalidFormat)
            }
            let base_script = if script_offset == 0 {
              None
            } else {
              Some(match parse_base_script(data, offset + script_offset) {
                Err(err) => return Err(err)
                Ok(value) => value
              })
            }
            records.push(BaseScriptRecord::{ tag, base_script })
          }
        }
      }
      Ok(BaseScriptList::{ records })
    }
  }
}

///|
fn parse_base_tag_list(data : BytesView, offset : Int) -> Result[BaseTagList, SfntError] {
  let count = read_u16_int(data, offset)
  match count {
    Err(err) => Err(err)
    Ok(count) => {
      if count < 0 {
        return Err(InvalidFormat)
      }
      let tags : Array[@common.Tag] = []
      let tag_base = offset + 2
      if tag_base + count * 4 > data.length() {
        return Err(UnexpectedEof)
      }
      for i in 0.. Result[Axis, SfntError] {
  let base_tag_offset = read_u16_int(data, offset)
  let base_script_offset = read_u16_int(data, offset + 2)
  match (base_tag_offset, base_script_offset) {
    (Err(err), _) => Err(err)
    (_, Err(err)) => Err(err)
    (Ok(base_tag_offset), Ok(base_script_offset)) => {
      if base_tag_offset < 0 || base_script_offset < 0 {
        return Err(InvalidFormat)
      }
      let base_tags = if base_tag_offset == 0 {
        None
      } else {
        Some(match parse_base_tag_list(data, offset + base_tag_offset) {
          Err(err) => return Err(err)
          Ok(value) => value
        })
      }
      let base_scripts = if base_script_offset == 0 {
        None
      } else {
        Some(match parse_base_script_list(data, offset + base_script_offset) {
          Err(err) => return Err(err)
          Ok(value) => value
        })
      }
      Ok(Axis::{ base_tags, base_scripts })
    }
  }
}

///|
/// Parse BASE table bytes.
pub fn BaseTable::parse(data : BytesView) -> Result[BaseTable, SfntError] {
  if data.length() < 8 {
    return Err(UnexpectedEof)
  }
  let version = read_u32_int(data, 0)
  let h_axis_offset = read_u16_int(data, 4)
  let v_axis_offset = read_u16_int(data, 6)
  match (version, h_axis_offset, v_axis_offset) {
    (Err(err), _, _) => Err(err)
    (_, Err(err), _) => Err(err)
    (_, _, Err(err)) => Err(err)
    (Ok(version), Ok(h_axis_offset), Ok(v_axis_offset)) => {
      if h_axis_offset < 0 || v_axis_offset < 0 {
        return Err(InvalidFormat)
      }
      let major = version >> 16
      let minor = version & 0xffff
      let var_store_offset = if version >= 0x00010001 {
        let value = read_u32_int(data, 8)
        match value {
          Err(err) => return Err(err)
          Ok(value) => Some(value)
        }
      } else {
        None
      }
      let h_axis = if h_axis_offset == 0 {
        None
      } else {
        Some(match parse_axis(data, h_axis_offset) {
          Err(err) => return Err(err)
          Ok(value) => value
        })
      }
      let v_axis = if v_axis_offset == 0 {
        None
      } else {
        Some(match parse_axis(data, v_axis_offset) {
          Err(err) => return Err(err)
          Ok(value) => value
        })
      }
      Ok(BaseTable::{
        major_version: major,
        minor_version: minor,
        h_axis,
        v_axis,
        var_store_offset,
      })
    }
  }
}