///|
const MSF_MAGIC : Bytes = b"Microsoft C/C++ MSF 7.00\r\n\x1aDS\x00\x00\x00"

///|
/// The fixed MSF 7.0 superblock fields that describe the container layout.
pub struct MsfHeader {
  block_size : Int32
  free_block_map_block : Int32
  num_blocks : Int32
  num_directory_bytes : Int32
  block_map_addr : Int32
}

///|
pub fn MsfHeader::block_size(self : MsfHeader) -> Int32 {
  self.block_size
}

///|
pub fn MsfHeader::free_block_map_block(self : MsfHeader) -> Int32 {
  self.free_block_map_block
}

///|
pub fn MsfHeader::num_blocks(self : MsfHeader) -> Int32 {
  self.num_blocks
}

///|
pub fn MsfHeader::num_directory_bytes(self : MsfHeader) -> Int32 {
  self.num_directory_bytes
}

///|
pub fn MsfHeader::block_map_addr(self : MsfHeader) -> Int32 {
  self.block_map_addr
}

///|
/// Parse and validate the MSF superblock.
pub fn parse_msf_header(
  data : Bytes,
  limits : MsfLimits,
) -> MsfHeader raise MsfError {
  if data.length() < 56 {
    raise TooSmall(data.length())
  }
  if data.length() > limits.max_file_bytes {
    raise LimitExceeded("file byte limit exceeded")
  }
  if !bytes_equal(data, 0, MSF_MAGIC, 32) {
    raise BadMagic
  }
  let block_size = uint_to_int(read_u32_le_at(data, 32), "block size")
  let valid_block_size = block_size == 512 ||
    block_size == 1024 ||
    block_size == 2048 ||
    block_size == 4096
  if !valid_block_size {
    raise UnsupportedBlockSize(block_size)
  }
  let free_block_map_block = uint_to_int(
    read_u32_le_at(data, 36),
    "free block map block",
  )
  if free_block_map_block != 1 && free_block_map_block != 2 {
    raise InvalidFreeBlockMap(free_block_map_block)
  }
  let num_blocks = uint_to_int(read_u32_le_at(data, 40), "block count")
  if num_blocks <= 0 {
    raise InvalidBlockCount(num_blocks)
  }
  let expected_size = checked_mul(num_blocks, block_size, "file size")
  if expected_size != data.length() {
    raise InvalidBlockCount(num_blocks)
  }
  let num_directory_bytes = uint_to_int(
    read_u32_le_at(data, 44),
    "directory byte count",
  )
  if num_directory_bytes <= 0 {
    raise InvalidDirectory("directory must not be empty")
  }
  if num_directory_bytes > limits.max_directory_bytes {
    raise LimitExceeded("directory byte limit exceeded")
  }
  let block_map_addr = uint_to_int(
    read_u32_le_at(data, 52),
    "block map address",
  )
  if block_map_addr < 0 || block_map_addr >= num_blocks {
    raise InvalidBlockIndex(block_map_addr)
  }
  MsfHeader::{
    block_size,
    free_block_map_block,
    num_blocks,
    num_directory_bytes,
    block_map_addr,
  }
}

///|
/// One stream and the physical file blocks that contain it.
pub struct MsfStream {
  index : Int32
  size : Int32
  blocks : Array[Int32]
}

///|
pub fn MsfStream::index(self : MsfStream) -> Int32 {
  self.index
}

///|
pub fn MsfStream::size(self : MsfStream) -> Int32 {
  self.size
}

///|
pub fn MsfStream::block_count(self : MsfStream) -> Int32 {
  self.blocks.length()
}

///|
/// A parsed MSF container with bounded stream access.
pub struct MsfFile {
  data : Bytes
  header : MsfHeader
  streams : Array[MsfStream]
}

///|
pub fn MsfFile::header(self : MsfFile) -> MsfHeader {
  self.header
}

///|
pub fn MsfFile::stream_count(self : MsfFile) -> Int32 {
  self.streams.length()
}

///|
pub fn MsfFile::stream_info(
  self : MsfFile,
  index : Int32,
) -> MsfStream raise MsfError {
  if index < 0 || index >= self.streams.length() {
    raise InvalidStream(index)
  }
  self.streams[index]
}

///|
/// Return the logical bytes of one stream.
pub fn MsfFile::stream(self : MsfFile, index : Int32) -> Bytes raise MsfError {
  let stream = self.stream_info(index)
  if stream.size == 0 {
    return b""
  }
  let buffer = Buffer::Buffer(size_hint=stream.size)
  let mut remaining = stream.size
  let block_size = self.header.block_size
  for block in stream.blocks {
    let available = block_size
    let take = if remaining < available { remaining } else { available }
    let start = checked_mul(block, block_size, "stream block offset")
    let end = checked_add(start, take, "stream block end")
    if end > self.data.length() {
      raise Truncated(end)
    }
    buffer.write_bytes(self.data.exact_view(start~, end~))
    remaining = remaining - take
  }
  if remaining != 0 {
    raise InvalidStream(index)
  }
  buffer.to_bytes()
}

///|
/// Parse the MSF container and its stream directory.
pub fn parse_msf(data : Bytes, limits : MsfLimits) -> MsfFile raise MsfError {
  let header = parse_msf_header(data, limits)
  let directory = read_stream_directory(data, header)
  let (streams, next) = parse_stream_directory(directory, header, limits)
  if next != directory.length() {
    raise InvalidDirectory("unexpected trailing directory bytes")
  }
  MsfFile::{ data, header, streams, }
}

///|
fn read_stream_directory(
  data : Bytes,
  header : MsfHeader,
) -> Bytes raise MsfError {
  let directory_blocks = be_div_ceil(
    header.num_directory_bytes,
    header.block_size,
  )
  let block_map_size = checked_mul(
    directory_blocks, 4, "directory block map size",
  )
  if block_map_size > header.block_size {
    raise LimitExceeded("directory block map exceeds one block")
  }
  let block_map_offset = checked_mul(
    header.block_map_addr,
    header.block_size,
    "directory block map offset",
  )
  let block_map_end = checked_add(
    block_map_offset, block_map_size, "directory block map end",
  )
  if block_map_end > data.length() {
    raise Truncated(block_map_end)
  }
  let directory = Buffer::Buffer(size_hint=header.num_directory_bytes)
  let mut cursor = block_map_offset
  for _ in 0..= header.num_blocks {
      raise InvalidBlockIndex(block)
    }
    let start = checked_mul(block, header.block_size, "directory block offset")
    let end = checked_add(start, header.block_size, "directory block end")
    if end > data.length() {
      raise Truncated(end)
    }
    directory.write_bytes(data.exact_view(start~, end~))
  }
  let bytes = directory.to_bytes()
  bytes.exact_view(end=header.num_directory_bytes).to_owned()
}

///|
fn parse_stream_directory(
  directory : Bytes,
  header : MsfHeader,
  limits : MsfLimits,
) -> (Array[MsfStream], Int32) raise MsfError {
  let count_and_cursor = read_u32_le(directory, 0, directory.length())
  let stream_count_raw = count_and_cursor.0
  let mut cursor = count_and_cursor.1
  let stream_count = uint_to_int(stream_count_raw, "stream count")
  if stream_count > limits.max_streams {
    raise LimitExceeded("stream count limit exceeded")
  }
  let sizes = []
  for _ in 0.. limits.max_stream_bytes {
      raise LimitExceeded("stream byte limit exceeded")
    }
    sizes.push(size)
    cursor = next
  }
  let streams = []
  for index in 0..= header.num_blocks {
        raise InvalidBlockIndex(block)
      }
      blocks.push(block)
      cursor = next
    }
    streams.push(MsfStream::{ index, size, blocks, })
  }
  (streams, cursor)
}

///|
fn bytes_equal(
  left : Bytes,
  left_offset : Int32,
  right : Bytes,
  length : Int32,
) -> Bool {
  if left_offset < 0 {
    return false
  }
  if length < 0 {
    return false
  }
  if left_offset + length > left.length() {
    return false
  }
  if length > right.length() {
    return false
  }
  let mut index = 0
  while index < length {
    if left.at(left_offset + index) != right.at(index) {
      return false
    }
    index = index + 1
  }
  true
}