module Test = (val Containers_testlib.make ~__FILE__ ())
open Test
open CCRingBuffer
open Q.Gen

let g_char = map Char.chr (Char.code 'A' -- Char.code 'z')
let g_str = string_size ~gen:g_char (0 -- 10)
let a_str = Q.set_gen g_str Q.string;;

t @@ fun () ->
let b = Byte.of_array (Bytes.of_string "abc") in
let b' = Byte.copy b in
Byte.clear b;
Byte.to_array b' = Bytes.of_string "abc" && Byte.to_array b = Bytes.empty
;;

q a_str (fun s ->
    let s = Bytes.of_string s in
    let s_len = Bytes.length s in
    let b = Byte.create (max s_len 64) in
    Byte.blit_from b s 0 s_len;
    Byte.capacity b >= s_len)
;;

q a_str (fun s ->
    let s = Bytes.of_string s in
    let s_len = Bytes.length s in
    let b = Byte.create (max s_len 64) in
    Byte.blit_from b s 0 s_len;
    let b' = Byte.copy b in
    try
      Byte.iteri b ~f:(fun i c -> if Byte.get_front b' i <> c then raise Exit);
      true
    with Exit -> false)
;;

q a_str (fun s ->
    let s = Bytes.of_string s in
    let s_len = Bytes.length s in
    let b = Byte.create (max s_len 64) in
    Byte.blit_from b s 0 s_len;
    Byte.push_back b 'X';
    Byte.peek_back_exn b = 'X')
;;

q (Q.pair a_str a_str) (fun (s, s') ->
    let b = Byte.create (max (String.length s + String.length s') 64) in
    let s = Bytes.of_string s in
    let s' = Bytes.of_string s' in
    Byte.blit_from b s 0 (Bytes.length s);
    Byte.blit_from b s' 0 (Bytes.length s');
    Byte.length b = Bytes.length s + Bytes.length s')
;;

q (Q.pair a_str a_str) (fun (s, s') ->
    let s = Bytes.of_string s in
    let s' = Bytes.of_string s' in
    let b = Byte.create (max (Bytes.length s + Bytes.length s') 64) in
    Byte.blit_from b s 0 (Bytes.length s);
    Byte.blit_from b s' 0 (Bytes.length s');
    Byte.length b = Bytes.length s + Bytes.length s')
;;

q a_str (fun s ->
    let s = Bytes.of_string s in
    let b = Byte.create (max 64 (Bytes.length s)) in
    Byte.blit_from b s 0 (Bytes.length s);
    let to_buf = Bytes.create (Bytes.length s) in
    let len = Byte.blit_into b to_buf 0 (Bytes.length s) in
    to_buf = s && len = Bytes.length s)
;;

q a_str (fun s ->
    let s = Bytes.of_string s in
    let s_len = Bytes.length s in
    let b = Byte.create (max s_len 64) in
    Byte.blit_from b s 0 s_len;
    Byte.skip b s_len;
    Byte.is_empty b)
;;

q a_str (fun s ->
    let s = Bytes.of_string s in
    let s_len = Bytes.length s in
    let b = Byte.create (max s_len 64) in
    Byte.blit_from b s 0 s_len;
    try
      let front = Byte.take_front_exn b in
      front = Bytes.get s 0
    with Byte.Empty -> s_len = 0)
;;

q a_str (fun s ->
    let s = Bytes.of_string s in
    let s_len = Bytes.length s in
    let b = Byte.create (max s_len 64) in
    Byte.blit_from b s 0 s_len;
    try
      let back = Byte.take_back_exn b in
      back = Bytes.get s (Bytes.length s - 1)
    with Byte.Empty -> s_len = 0)
;;

q a_str (fun s ->
    let s = Bytes.of_string s in
    let s_len = Bytes.length s in
    let b = Byte.create (max s_len 64) in
    Byte.blit_from b s 0 s_len;
    try
      let () = Byte.junk_front b in
      s_len - 1 = Byte.length b
    with Byte.Empty -> s_len = 0)
;;

q a_str (fun s ->
    let s = Bytes.of_string s in
    let s_len = Bytes.length s in
    let b = Byte.create (max s_len 64) in
    Byte.blit_from b s 0 s_len;
    try
      let () = Byte.junk_back b in
      s_len - 1 = Byte.length b
    with Byte.Empty -> s_len = 0)
;;

q (Q.pair a_str a_str) (fun (s, s') ->
    let s = Bytes.of_string s in
    let s' = Bytes.of_string s' in
    let b = Byte.create (max (Bytes.length s + Bytes.length s') 64) in
    Byte.blit_from b s 0 (Bytes.length s);
    Byte.blit_from b s' 0 (Bytes.length s');
    let h = Bytes.of_string "hello world" in
    Byte.blit_from b h 0 (Bytes.length h);
    (* big enough *)
    let l = Byte.length b in
    let l' = l / 2 in
    Byte.skip b l';
    Byte.length b + l' = l)
;;

q a_str (fun s ->
    let s = Bytes.of_string s in
    let s_len = Bytes.length s in
    let b = Byte.create (max s_len 64) in
    Byte.blit_from b s 0 s_len;
    Byte.clear b;
    Byte.length b = 0)
;;

q a_str (fun s ->
    let s = Bytes.of_string s in
    let s_len = Bytes.length s in
    let b = Byte.create (max s_len 64) in
    Byte.blit_from b s 0 s_len;
    try
      Byte.iteri b ~f:(fun i c -> if Byte.get_front b i <> c then raise Exit);
      true
    with Exit -> false)
;;

q (Q.pair Q.nat_small a_str) (fun (i, s) ->
    let s = Bytes.of_string (s ^ " ") in
    let s_len = Bytes.length s in
    let b = Byte.create (max s_len 64) in
    Byte.blit_from b s 0 s_len;
    let index = abs (i mod Byte.length b) in
    let front = Byte.get_front b index in
    front = Bytes.get s index)
;;

q (Q.pair Q.nat_small a_str) (fun (i, s) ->
    let s = Bytes.of_string (s ^ " ") in
    let s_len = Bytes.length s in
    let b = Byte.create (max s_len 64) in
    Byte.blit_from b s 0 s_len;
    let index = abs (i mod Byte.length b) in
    let back = Byte.get_back b index in
    back = Bytes.get s (s_len - index - 1))
;;

q a_str (fun s ->
    let s = Bytes.of_string s in
    let s_len = Bytes.length s in
    let b = Byte.create (max s_len 64) in
    Byte.blit_from b s 0 s_len;
    let l = Byte.to_list b in
    let explode s =
      let rec exp i l =
        if i < 0 then
          l
        else
          exp (i - 1) (Bytes.get s i :: l)
      in
      exp (Bytes.length s - 1) []
    in
    explode s = l)
;;

q a_str (fun s ->
    let s = Bytes.of_string s in
    let s_len = Bytes.length s in
    let b = Byte.create (max s_len 64) in
    Byte.blit_from b s 0 s_len;
    try
      let back = Byte.peek_front_exn b in
      back = Bytes.get s 0
    with Byte.Empty -> s_len = 0)
;;

q a_str (fun s ->
    let s = Bytes.of_string s in
    let s_len = Bytes.length s in
    let b = Byte.create (max s_len 64) in
    Byte.blit_from b s 0 s_len;
    try
      let back = Byte.peek_back_exn b in
      back = Bytes.get s (s_len - 1)
    with Byte.Empty -> s_len = 0)
;;

q a_str (fun s ->
    let s = Bytes.of_string s in
    let b = Byte.of_array s in
    let s' = Byte.to_array b in
    s = s')

module BI = CCRingBuffer.Make (struct
  type t = int

  let dummy = 0
end)
;;

(* try to trigger an error on resize
   see issue #126 *)
t @@ fun () ->
let b = BI.create 50 in
let st = Random.State.make [| 0 |] in
for _i = 1 to 100_000 do
  if Random.State.float st 1.0 < 0.5 then
    BI.push_back b 0
  else (
    let _ = BI.take_front b in
    ()
  )
done;
true

(* Test against reference implementation (lists) on a succession of
   operations.

   Remarks on semantics:

   JUNK_FRONT/JUNK_BACK: try to remove if not empty
   SKIP: if at least n elements, skip; else nop
*)

module BS = CCRingBuffer.Byte

type op =
  | Push_back of char
  | Take_front
  | Take_back
  | Peek_front
  | Peek_back
  | Junk_front
  | Junk_back
  | Skip of int
  | Blit of string * int * int
  | Z_if_full

let str_of_op = function
  | Push_back c -> Printf.sprintf "push_back(%C)" c
  | Take_front -> Printf.sprintf "take_front"
  | Take_back -> Printf.sprintf "take_back"
  | Peek_front -> Printf.sprintf "peek_front"
  | Peek_back -> Printf.sprintf "peek_back"
  | Junk_front -> Printf.sprintf "junk_front"
  | Junk_back -> Printf.sprintf "junk_back"
  | Skip n -> Printf.sprintf "skip(%d)" n
  | Blit (s, i, len) -> Printf.sprintf "blit(%S,%d,%d)" s i len
  | Z_if_full -> "zero_if_full"

let push_back c = Push_back c

let skip n =
  assert (n >= 0);
  Skip n

let blit s i len =
  if i < 0 || len < 0 || i + len > String.length s then
    failwith ("wrong blit: " ^ str_of_op (Blit (s, i, len)));
  Blit (s, i, len)

let shrink_op =
  let open Q.Iter in
  function
  | Push_back c -> Q.Shrink.char c >|= push_back
  | Take_front | Take_back | Junk_back | Junk_front | Z_if_full | Peek_front
  | Peek_back ->
    empty
  | Skip n -> Q.Shrink.int n >|= skip
  | Blit (s, i, len) ->
    let s_i =
      Q.Shrink.int i >>= fun i' ->
      assert (i' <= i && i' + len <= String.length s);
      if i' <= 0 then
        empty
      else
        return (blit s i' len)
    and s_len =
      Q.Shrink.int len >>= fun len' ->
      assert (len' <= len && i + len' <= String.length s);
      if len' <= 0 then
        empty
      else
        return (blit s i len')
    and s_s =
      Q.Shrink.string s >>= fun s' ->
      if i + len > String.length s' then
        empty
      else
        return (blit s' i len)
    in
    append s_i (append s_len s_s)

let len_op size acc = function
  | Push_back _ -> min size (acc + 1)
  | Take_front | Take_back | Junk_front | Junk_back -> max (acc - 1) 0
  | Skip n ->
    if acc >= n then
      acc - n
    else
      acc
  | Z_if_full | Peek_front | Peek_back -> acc
  | Blit (_, _, len) -> min size (acc + len)

let apply_op b = function
  | Push_back c ->
    BS.push_back b c;
    None
  | Take_front -> BS.take_front b
  | Take_back -> BS.take_back b
  | Junk_front ->
    (try BS.junk_front b with BS.Empty -> ());
    None
  | Junk_back ->
    (try BS.junk_back b with BS.Empty -> ());
    None
  | Peek_front -> BS.peek_front b
  | Peek_back -> BS.peek_back b
  | Skip n ->
    if n <= BS.length b then BS.skip b n;
    None
  | Blit (s, i, len) ->
    assert (i + len <= String.length s);
    BS.blit_from b (Bytes.unsafe_of_string s) i len;
    None
  | Z_if_full ->
    if BS.is_full b then
      Some '0'
    else
      None

let gen_op =
  let open Q.Gen in
  let g_blit =
    string_size ~gen:g_char (5 -- 20) >>= fun s ->
    0 -- String.length s >>= fun len ->
    assert (len >= 0 && len <= String.length s);
    0 -- (String.length s - len) >|= fun i -> blit s i len
  in
  oneof_weighted
    [
      3, return Take_back;
      3, return Take_front;
      1, return Junk_back;
      1, return Junk_front;
      1, return Peek_front;
      1, return Peek_back;
      2, g_blit;
      1, 0 -- 5 >|= skip;
      2, map push_back g_char;
      1, return Z_if_full;
    ]

let arb_op = Q.make ~shrink:shrink_op ~print:str_of_op gen_op
let arb_ops = Q.list_size Q.Gen.(0 -- 20) arb_op

module L_impl = struct
  type t = {
    size: int;
    mutable l: char list;
  }

  let create size = { size; l = [] }

  let normalize_ b =
    let n = List.length b.l in
    if n > b.size then b.l <- CCList.drop (n - b.size) b.l

  let push_back b c =
    b.l <- b.l @ [ c ];
    normalize_ b

  let take_front b =
    match b.l with
    | [] -> None
    | c :: l ->
      b.l <- l;
      Some c

  let peek_front b =
    match b.l with
    | [] -> None
    | x :: _ -> Some x

  let take_back b =
    let n = List.length b.l in
    if n = 0 then
      None
    else (
      let init, last = CCList.take_drop (n - 1) b.l in
      let x = List.hd last in
      b.l <- init;
      Some x
    )

  let peek_back b =
    match b.l with
    | [] -> None
    | l -> Some (List.hd (List.rev l))

  let junk_front b = ignore (take_front b)
  let junk_back b = ignore (take_back b)

  let skip b n =
    if n <= List.length b.l then CCInt.range' 0 n (fun _ -> junk_front b)

  let blit b s i len =
    for j = i to i + len - 1 do
      push_back b (String.get s j)
    done

  let apply_op b = function
    | Push_back c ->
      push_back b c;
      None
    | Take_front -> take_front b
    | Take_back -> take_back b
    | Peek_front -> peek_front b
    | Peek_back -> peek_back b
    | Junk_back ->
      junk_back b;
      None
    | Junk_front ->
      junk_front b;
      None
    | Skip n ->
      skip b n;
      None
    | Blit (s, i, len) ->
      blit b s i len;
      None
    | Z_if_full ->
      if b.size = List.length b.l then
        Some '0'
      else
        None

  let to_list b = b.l
end
;;

(* check that a lot of operations can be applied without failure,
   and that the result has correct length *)
q ~count:3_000 arb_ops (fun ops ->
    let size = 64 in
    let b = BS.create size in
    List.iter (fun o -> ignore (apply_op b o)) ops;
    BS.length b = List.fold_left (len_op size) 0 ops)
;;

(* check identical behavior with list implem *)
q ~count:3_000 arb_ops (fun ops ->
    let size = 64 in
    let b = BS.create size in
    let l = L_impl.create size in
    let l1 = CCList.filter_map (apply_op b) ops in
    let l2 = CCList.filter_map (L_impl.apply_op l) ops in
    l1 = l2 && BS.to_list b = L_impl.to_list l)

(* check that deleted elements can be GCed *)
module BO = CCRingBuffer.Make (struct
  type t = int option

  let dummy = None
end)

let make_bo () =
  let b = BO.create 1000 in
  for i = 1 to BO.capacity b do
    BO.push_back b (Some i)
  done;
  b

let test_no_major_blocks clear =
  Gc.full_major ();
  let live_blocks_before = (Gc.stat ()).live_blocks in
  let b = make_bo () in
  clear b;
  Gc.full_major ();
  let live_blocks_after = (Gc.stat ()).live_blocks in
  assert (BO.length b = 0);
  let diff = live_blocks_after - live_blocks_before in
  diff < BO.capacity b / 2
;;

t @@ fun () ->
test_no_major_blocks (fun b ->
    for _ = 1 to BO.length b do
      BO.junk_front b
    done)
;;

t @@ fun () ->
test_no_major_blocks (fun b ->
    for _ = 1 to BO.length b do
      BO.junk_back b
    done)
;;

t @@ fun () ->
test_no_major_blocks (fun b ->
    for _ = 1 to BO.length b do
      ignore (BO.take_front b)
    done)
;;

t @@ fun () ->
test_no_major_blocks (fun b ->
    for _ = 1 to BO.length b do
      ignore (BO.take_back b)
    done)
;;

t @@ fun () -> test_no_major_blocks (fun b -> BO.skip b (BO.length b));;
t @@ fun () -> test_no_major_blocks (fun b -> BO.clear b)
