///|
pub type! Bzip2Error {
  SequenceError
  ParamError
  MemError
  DataError
  DataErrorMagic
  IOError
  UnexpectedEof
  OutBuffFull
  ConfigError
} derive(Show)

///|
fn Bzip2Error::make(code : Int) -> Bzip2Error {
  match code {
    -1 => SequenceError
    -2 => ParamError
    -3 => MemError
    -4 => DataError
    -5 => DataErrorMagic
    -6 => IOError
    -7 => UnexpectedEof
    -8 => OutBuffFull
    -9 => ConfigError
    _ => abort("unreachable")
  }
}

///|
#borrow(input, code_buf)
extern "C" fn compress_ffi(
  input : Bytes,
  input_offset : Int,
  input_len : Int,
  level : Int,
  verbosity : Int,
  work_factor : Int,
  code_buf : FixedArray[Int]
) -> Bytes = "compress_ffi"

///|
pub fn compress(
  input : @bytes.View,
  level : Int,
  verbosity : Int,
  work_factor : Int
) -> Bytes!Bzip2Error {
  let code_buf : FixedArray[Int] = [0]
  let output = compress_ffi(
    input.data(),
    input.start_offset(),
    input.length(),
    level,
    verbosity,
    work_factor,
    code_buf,
  )
  match code_buf {
    [0] => output
    [code] => raise Bzip2Error::make(code)
    _ => abort("unreachable")
  }
}

///|
#borrow(input, code_buf)
extern "C" fn decompress_ffi(
  input : Bytes,
  input_offset : Int,
  input_len : Int,
  small : Int,
  verbosity : Int,
  code_buf : FixedArray[Int]
) -> Bytes = "decompress_ffi"

///|
pub fn decompress(
  input : @bytes.View,
  small : Int,
  verbosity : Int
) -> Bytes!Bzip2Error {
  let code_buf : FixedArray[Int] = [0]
  let output = decompress_ffi(
    input.data(),
    input.start_offset(),
    input.length(),
    small,
    verbosity,
    code_buf,
  )
  match code_buf {
    [0] => output
    [code] => raise Bzip2Error::make(code)
    _ => abort("unreachable")
  }
}

///|
test "round trip" {
  for _ in 0..<10 {
    let origin = Bytes::from_array(@quickcheck.samples(10000))
    let compressed = compress!(origin, 9, 0, 0)
    let decompressed = decompress!(compressed, 0, 0)
    assert_eq!(origin, decompressed)
  }
}