external random_seed : unit -> int array = "caml_sys_random_seed"

let seed = random_seed ()
let () = Random.full_init seed
let () = Fmt.epr "seed: %a.\n%!" Fmt.(Dump.array int) seed
let strf = Fmt.str
let invalid_arg = Fmt.invalid_arg

let list_init f l =
  let rec go acc = function
    | 0 -> List.rev acc
    | n -> go (f n :: acc) (pred n) in
  go [] l

let random_string length _ =
  let ic = open_in_bin "/dev/urandom" in
  let rs = really_input_string ic length in
  close_in ic ;
  rs

let hashes = list_init (random_string Digestif.SHA1.digest_size) 32
let hashes = List.map Digestif.SHA1.of_raw_string hashes

let consistent_hex =
  List.map Digestif.SHA1.to_hex (* XXX(dinosaure): an oracle [to_hex]? *) hashes

let random_wsp length =
  let go _ =
    match Random.int 4 with
    | 0 -> ' '
    | 1 -> '\t'
    | 2 -> '\n'
    | 3 -> '\r'
    | _ -> assert false in
  String.init length go

let spaces_expand hex =
  let rt = ref [] in
  String.iter
    (fun chr -> rt := !rt @ [ random_wsp (Random.int 10); String.make 1 chr ])
    hex ;
  String.concat "" !rt

let spaces_hex = List.map spaces_expand consistent_hex

let random_hex length =
  let go _ =
    match Random.int (10 + 26 + 26) with
    | n when n < 10 -> Char.chr (Char.code '0' + n)
    | n when n < 10 + 26 -> Char.chr (Char.code 'a' + n - 10)
    | n -> Char.chr (Char.code 'A' + n - (10 + 26)) in
  String.init length go

let inconsistent_hex =
  let expand hex =
    String.concat ""
      [ spaces_expand hex; spaces_expand (random_hex (5 + Random.int 20)) ]
  in
  List.map expand consistent_hex

let test_consistent_hex_success i hex =
  Alcotest.test_case (strf "consistent hex:%d" i) `Quick @@ fun () ->
  ignore @@ Digestif.SHA1.consistent_of_hex hex

let test_hex_success i hex =
  Alcotest.test_case (strf "hex:%d" i) `Quick @@ fun () ->
  ignore @@ Digestif.SHA1.of_hex hex

let test_consistent_hex_fail i hex =
  Alcotest.test_case (strf "consistent hex fail:%d" i) `Quick @@ fun () ->
  try
    let _ = Digestif.SHA1.consistent_of_hex hex in
    assert false
  with Invalid_argument _ -> ()

let sha1 = Alcotest.testable Digestif.SHA1.pp Digestif.SHA1.equal

let test_hex_iso i random_input =
  Alcotest.test_case (strf "iso:%d" i) `Quick @@ fun () ->
  let hash : Digestif.SHA1.t = Digestif.SHA1.of_raw_string random_input in
  let hex = Digestif.SHA1.to_hex hash in
  Alcotest.(check sha1) "iso hex" (Digestif.SHA1.of_hex hex) hash

let test_consistent_hex_iso i random_input =
  Alcotest.test_case (strf "iso:%d" i) `Quick @@ fun () ->
  let hash : Digestif.SHA1.t = Digestif.SHA1.of_raw_string random_input in
  let hex = Digestif.SHA1.to_hex hash in
  Alcotest.(check sha1)
    "iso consistent hex"
    (Digestif.SHA1.consistent_of_hex hex)
    hash

let tests () =
  Alcotest.run "digestif"
    [
      ("of_hex 0", List.mapi test_hex_success consistent_hex);
      ( "consistent_of_hex 0",
        List.mapi test_consistent_hex_success consistent_hex );
      ("of_hex 1", List.mapi test_hex_success spaces_hex);
      ("consistent_of_hex 1", List.mapi test_consistent_hex_success spaces_hex);
      ("of_hex 2", List.mapi test_hex_success inconsistent_hex);
      ( "consistent_of_hex 2",
        List.mapi test_consistent_hex_fail inconsistent_hex );
      ( "iso of_hex",
        List.mapi test_hex_iso
          (list_init (random_string Digestif.SHA1.digest_size) 64) );
      ( "iso consistent_of_hex",
        List.mapi test_consistent_hex_iso
          (list_init (random_string Digestif.SHA1.digest_size) 64) );
    ]

let () = tests ()
