///|
/// The fixed 24-byte MARC 21 record leader.
pub struct Leader {
  raw : String
} derive(Eq, Debug)

///|
pub(all) suberror LeaderError {
  InvalidLength(Int)
  NonAscii(Int)
  InvalidEntryMap(String)
  InvalidRecordLength(String)
  InvalidBaseAddress(String)
} derive(Eq, Debug)

///|
pub impl Show for LeaderError with fn output(self, logger) {
  logger.write_string(self.to_string())
}

///|
pub fn LeaderError::to_string(self : LeaderError) -> String {
  match self {
    InvalidLength(length) => "MARC leader must contain 24 bytes, got \{length}"
    NonAscii(offset) =>
      "MARC leader contains a non-ASCII byte at offset \{offset}"
    InvalidEntryMap(value) => "invalid MARC leader entry map: \{value}"
    InvalidRecordLength(value) => "invalid leader record length: \{value}"
    InvalidBaseAddress(value) => "invalid leader base address: \{value}"
  }
}

///|
pub fn parse(raw : Bytes) -> Result[Leader, LeaderError] {
  if raw.length() != 24 {
    return Err(InvalidLength(raw.length()))
  }
  let chars : Array[Char] = []
  let mut index = 0
  while index < 24 {
    let value = raw[index].to_int()
    if value > 0x7F {
      return Err(NonAscii(index))
    }
    chars.push(value.unsafe_to_char())
    index = index + 1
  }
  Ok({ raw: String::from_array(chars) })
}

///|
fn field(raw : String, start : Int, end : Int) -> String {
  raw.unsafe_substring(start~, end~)
}

///|
fn decimal(raw : String, start : Int, end : Int) -> Int? {
  let mut result = 0
  let mut index = start
  while index < end {
    let value = raw[index].to_int()
    if value < 48 || value > 57 {
      return None
    }
    result = result * 10 + value - 48
    index = index + 1
  }
  Some(result)
}

///|
pub fn Leader::raw(self : Leader) -> String {
  self.raw
}

///|
pub fn Leader::record_length(self : Leader) -> Result[Int, LeaderError] {
  match decimal(self.raw, 0, 5) {
    Some(value) => Ok(value)
    None => Err(InvalidRecordLength(field(self.raw, 0, 5)))
  }
}

///|
pub fn Leader::status(self : Leader) -> Char {
  self.raw.get_char(5).unwrap()
}

///|
pub fn Leader::record_type(self : Leader) -> Char {
  self.raw.get_char(6).unwrap()
}

///|
pub fn Leader::bibliographic_level(self : Leader) -> Char {
  self.raw.get_char(7).unwrap()
}

///|
pub fn Leader::type_of_control(self : Leader) -> Char {
  self.raw.get_char(8).unwrap()
}

///|
pub fn Leader::character_coding_scheme(self : Leader) -> Char {
  self.raw.get_char(9).unwrap()
}

///|
pub fn Leader::indicator_count(self : Leader) -> Result[Int, LeaderError] {
  match decimal(self.raw, 10, 11) {
    Some(value) => Ok(value)
    None => Err(InvalidEntryMap(field(self.raw, 10, 11)))
  }
}

///|
pub fn Leader::subfield_code_length(self : Leader) -> Result[Int, LeaderError] {
  match decimal(self.raw, 11, 12) {
    Some(value) => Ok(value)
    None => Err(InvalidEntryMap(field(self.raw, 11, 12)))
  }
}

///|
pub fn Leader::base_address(self : Leader) -> Result[Int, LeaderError] {
  match decimal(self.raw, 12, 17) {
    Some(value) => Ok(value)
    None => Err(InvalidBaseAddress(field(self.raw, 12, 17)))
  }
}

///|
pub fn Leader::encoding_level(self : Leader) -> Char {
  self.raw.get_char(17).unwrap()
}

///|
pub fn Leader::cataloging_form(self : Leader) -> Char {
  self.raw.get_char(18).unwrap()
}

///|
pub fn Leader::multipart_level(self : Leader) -> Char {
  self.raw.get_char(19).unwrap()
}

///|
pub fn Leader::entry_map(self : Leader) -> Result[(Int, Int, Int), LeaderError] {
  let length_of_length = match decimal(self.raw, 20, 21) {
    Some(value) => value
    None => return Err(InvalidEntryMap(field(self.raw, 20, 21)))
  }
  let length_of_start = match decimal(self.raw, 21, 22) {
    Some(value) => value
    None => return Err(InvalidEntryMap(field(self.raw, 21, 22)))
  }
  let length_of_implementation = match decimal(self.raw, 22, 23) {
    Some(value) => value
    None => return Err(InvalidEntryMap(field(self.raw, 22, 23)))
  }
  if self.raw.get_char(23).unwrap() != '0' {
    return Err(InvalidEntryMap(field(self.raw, 20, 24)))
  }
  Ok((length_of_length, length_of_start, length_of_implementation))
}