open OUnit
open GapiMonad

let print_array arr =
  let len = Bigarray.Array1.dim arr in
  let r = Bytes.make len ' ' in
  for i = 0 to len - 1 do
    Bytes.set r i arr.{i}
  done;
  Bytes.to_string r

let session =
  {
    GapiConversation.Session.curl = GapiCurl.Initialized;
    config = GapiConfig.default;
    auth = GapiConversation.Session.NoAuth;
    cookies = [];
    etag = "";
  }

let test_with_lock_m () =
  let counter1 = ref 0 in
  let counter2 = ref 0 in
  let x = ref false in
  let mutex = Mutex.create () in
  let switch _ =
    Utils.with_lock_m mutex
      ( if !x = false then (
          x := true;
          counter1 := !counter1 + 1 )
        else counter2 := !counter2 + 1;
        SessionM.return () )
  in
  let tq = Queue.create () in
  for _ = 1 to 10 do
    let t = Thread.create switch session in
    Queue.push t tq
  done;
  Queue.iter (fun t -> Thread.join t) tq;
  assert_equal ~printer:string_of_bool true !x;
  assert_equal ~printer:string_of_int 1 !counter1;
  assert_equal ~printer:string_of_int 9 !counter2

let test_read_block () =
  let remote_id = "test" in
  let resource_size = 24L in
  let pool_size = 160 in
  let block_size = 16 in
  let stream_block_size = 8 in

  let fill_array offset arr =
    if offset = 0L then (
      Bigarray.Array1.fill arr 'a';
      arr.{stream_block_size} <- 'c' )
    else Bigarray.Array1.fill arr 'b';
    SessionM.return ()
  in

  let memory_buffers = Buffering.MemoryBuffers.create block_size pool_size in
  let destination =
    Bigarray.Array1.create Bigarray.char Bigarray.c_layout
      (Int64.to_int resource_size)
  in
  let init_subs i =
    Bigarray.Array1.sub destination (i * stream_block_size) stream_block_size
  in
  let dest_arrs = Array.init 3 init_subs in
  let stream buffer offset =
    Buffering.MemoryBuffers.read_block remote_id offset resource_size
      (fun start_pos block_buffer -> fill_array start_pos block_buffer)
      ~dest_arr:buffer memory_buffers
  in
  for i = 0 to (Int64.to_int resource_size / stream_block_size) - 1 do
    let offset = Int64.of_int (i * stream_block_size) in
    stream dest_arrs.(i) offset session |> ignore;
    let result =
      let arr =
        Bigarray.Array1.create Bigarray.char Bigarray.c_layout stream_block_size
      in
      if i = 2 then Bigarray.Array1.fill arr 'b'
      else (
        Bigarray.Array1.fill arr 'a';
        if i = 1 then arr.{0} <- 'c' );
      arr
    in
    assert_equal ~printer:print_array result dest_arrs.(i)
  done

let test_thread_m () =
  let start_thread_m f s =
    let t = Thread.create f s in
    (t, s)
  in
  let b = ref false in
  let action_m s =
    b := true;
    ((), s)
  in
  let t = start_thread_m action_m session |> fst in
  Thread.join t;
  assert_equal ~printer:string_of_bool true !b

let suite =
  "Buffering test"
  >::: [
         "test_with_lock_m" >:: test_with_lock_m;
         "test_read_block" >:: test_read_block;
         "test_thread_m" >:: test_thread_m;
       ]
