(*
 * ExtLib Testing Suite
 * Copyright (C) 2004 Janne Hellsten
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version,
 * with the special exception on linking described in file LICENSE.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *)

open ExtList

module B = BitSet

let biased_rnd_28 () = 
  let n_bits = [| 4; 8; 16; 28 |] in
  let n = n_bits.(Random.int (Array.length n_bits)) in
  Random.int (1 lsl n)

let popcount n = 
  let p = ref 0 in
  for i = 0 to 29 do
    if n land (1 lsl i) <> 0 then
      incr p
  done;
  !p

let set_bitset s n = 
  for i = 0 to 29 do
    if (n land (1 lsl i)) <> 0 then
      B.set s i
  done;
  assert (popcount n = B.count s)

let bitset_of_int n = 
  assert (n <= (1 lsl 29));
  let s = B.create 30 in
  set_bitset s n;
  s

let int_of_bitset s = 
  let n = ref 0 in
  for i = 0 to 29 do
    if B.is_set s i then
      n := !n lor (1 lsl i)
  done;
  !n

let bitset_of_int_scale n scl = 
  assert (n <= (1 lsl 29));
  let s = B.create 30 in
  for i = 0 to 29 do
    if (n land (1 lsl i)) <> 0 then
      B.set s (i*scl)
  done;
  assert (popcount n = B.count s);
  s

let int_of_bitset_scale s scl = 
  let n = ref 0 in
  for i = 0 to 29 do
    if B.is_set s (i*scl) then
      n := !n lor (1 lsl i)
  done;
  !n

let test_rnd_creation () =
  for i = 0 to 255 do
    let r1 = biased_rnd_28 () in
    let s = bitset_of_int r1 in
    let c = B.copy s in
    assert (int_of_bitset s = r1);
    assert (c = s);
    assert (B.compare c s = 0);
    B.unite c s;
    assert (c = s);
    B.intersect c (B.empty ());
    assert (B.count c = 0);
  done

let test_intersect () = 
  for i = 0 to 255 do
    let s = bitset_of_int (biased_rnd_28 ()) in
    B.intersect s (B.empty ());
    assert (B.count s = 0)
  done

let test_diff () = 
  for i = 0 to 255 do
    let r = biased_rnd_28 () in
    let s = bitset_of_int r in
    if r <> 0 then
      assert (B.count s <> 0);
    assert (B.count ((B.diff s s)) = 0);
  done

let test_sym_diff () =
  for i = 0 to 255 do
    let s = (Random.int 3)+1 in
    let r1 = biased_rnd_28 () in
    let r2 = biased_rnd_28 () in
    let s1 = bitset_of_int_scale r1 s in
    let s2 = bitset_of_int_scale r2 s in
    assert (int_of_bitset_scale (bitset_of_int_scale r1 s) s = r1);
    assert (int_of_bitset_scale (B.sym_diff s1 s2) s = r1 lxor r2);
    assert (int_of_bitset_scale (B.sym_diff s2 s1) s = r1 lxor r2);
    assert (B.count (B.sym_diff s1 s1) = 0);
    assert (B.count (B.sym_diff s2 s2) = 0);
  done

let test_compare () =
  assert (B.compare (B.empty ()) (B.empty ()) = 0);
  for i = 0 to 255 do
    let r1 = biased_rnd_28 () in
    let r2 = biased_rnd_28 () in
    let s1 = bitset_of_int r1 
    and s2 = bitset_of_int r2 in
    let sr = B.compare s1 s2
    and ir = compare r1 r2 in
    assert (sr = ir);
    assert (B.compare s1 s1 = 0);
    assert (B.compare s2 s2 = 0)
  done;
  for i = 0 to 255 do
    let scl = (Random.int 15)+1 in
    let r1 = biased_rnd_28 () in
    let r2 = biased_rnd_28 () in
    let s1 = bitset_of_int_scale r1 scl
    and s2 = bitset_of_int_scale r2 scl in
    let sr = B.compare s1 s2
    and ir = compare r1 r2 in
    assert (sr = ir)
  done;
  for i = 1 to 255 do
    let s1 = bitset_of_int (i-1) in
    let s2 = bitset_of_int i in
    assert (B.compare s1 s2 = -1)
  done;
  for i = 1 to 255 do
    let s1 = bitset_of_int (i-1) in
    let s2 = bitset_of_int i in
    assert (B.compare s1 s2 = -1)
  done

module BSSet = Set.Make (struct type t = BitSet.t let compare = B.compare end)

let test_compare_2 () = 
  let nums = List.init 256 Std.identity in
  let num_set = 
    List.fold_left (fun acc e -> BSSet.add (bitset_of_int e) acc) BSSet.empty nums in
  List.iter
    (fun e -> 
       let bs = bitset_of_int e in
       assert (BSSet.mem bs num_set)) nums

let test_compare_3 () = 
  for i = 0 to 63 do
    for j = 0 to 63 do
      let b1 = B.create ((Random.int 128)+32) in
      let b2 = B.create ((Random.int 128)+32) in
      set_bitset b1 i;
      set_bitset b2 j;
      assert (B.compare b1 b2 = compare i j)
    done
  done

let test_empty () = 
  for len = 0 to 63 do
    let s = B.empty () in
    for i = 0 to len do 
      assert (not (B.is_set s i));
      B.set s i
    done;
    assert (not (B.is_set s (len+1)));
    for i = 0 to len do 
      assert (B.is_set s i)
    done
  done

let test_exceptions () = 
  let expect_exn f =
    try 
      f ();
      false (* Should've raised an exception! *)
    with B.Negative_index _ -> true in
  let s = B.create 100 in
  assert (expect_exn (fun () -> B.set s (-15)));
  assert (expect_exn (fun () -> B.unset s (-15)));
  assert (expect_exn (fun () -> B.toggle s (-15)));
  assert (expect_exn 
            (fun () ->
               let s = B.create 8 in
               ignore (B.is_set s (-19))))

module IS = Set.Make (struct type t = int let compare = compare end)

let set_of_int n = 
  let rec loop accu i =
    if i < 30 then
      if ((1 lsl i) land n) <> 0 then
        loop (IS.add i accu) (i+1)
      else 
        loop accu (i+1)
    else accu in
  loop IS.empty 0

let int_of_set s = 
  IS.fold (fun i acc -> (1 lsl i) lor acc) s 0

let test_set_opers () = 
  let rnd_oper () = 
    match Random.int 3 with
      0 -> (IS.inter, B.inter)
    | 1 -> (IS.diff, B.diff)
    | 2 -> (IS.union, B.union)
    | _ -> assert false in
  for i = 0 to 255 do
    let r1 = biased_rnd_28 () in
    let r2 = biased_rnd_28 () in
    let s1 = set_of_int r1
    and s2 = set_of_int r2
    and bs1 = bitset_of_int r1 
    and bs2 = bitset_of_int r2 in
    assert (int_of_set s1 = r1);
    assert (int_of_set s2 = r2);
    assert (int_of_bitset bs1 = r1);
    assert (int_of_bitset bs2 = r2);
    let (isop,bsop) = rnd_oper () in
    let is = isop s1 s2 
    and bs = bsop bs1 bs2 in
    let is_int = int_of_set is in
    let bs_int = int_of_bitset bs in
    assert (is_int = bs_int);
  done

let test_unite () =
  for i = 0 to 255 do
    let r1 = biased_rnd_28 () in
    let s = bitset_of_int r1 in
    let c = B.copy s in
    assert (int_of_bitset s = r1);
    let pop = B.count c in
    B.unite c (B.empty ());
    assert (B.count c = pop);
  done

let test_intersect_2 () =
  for i = 0 to 255 do
    let r1 = biased_rnd_28 () in
    let s = bitset_of_int r1 in
    let c = B.copy s in
    assert (int_of_bitset s = r1);
    B.intersect c (B.empty ());
    assert (B.count c = 0);
  done

let test_differentiate () = 
  for i = 0 to 255 do
    let r1 = biased_rnd_28 () in
    let s = bitset_of_int r1 in
    let d = B.copy s in
    B.differentiate d s;
    assert (B.count d = 0);
    for j = 0 to 32 do
      B.set s (Random.int 256)
    done;
    let d = B.copy s in
    B.differentiate d (B.empty ());
    assert (B.count s = B.count d);
    assert (B.compare d s = 0);
    B.differentiate d s;
    assert (B.count d = 0);
  done

(* TODO *)
let test_differentiate_sym () = 
  for i = 0 to 255 do
    let r1 = biased_rnd_28 () in
    let r2 = biased_rnd_28 () in
    let s = bitset_of_int r1 in
    let d = B.copy s in
    B.differentiate_sym d s;
    assert (B.count d = 0);
    for j = 0 to 32 do
      B.set s (Random.int 256)
    done;
    let d = B.copy s in
    B.differentiate_sym d (B.empty ());
    assert (B.count s = B.count d);
    assert (B.compare d s = 0);
    B.differentiate_sym d s;
    assert (B.count d = 0);

    let s1 = bitset_of_int r1 
    and s2 = bitset_of_int r2 in
    let d1 = B.copy s1 in
    B.differentiate_sym d1 s2;
    assert (r1 lxor r2 = int_of_bitset d1);
  done

let test_bs_1 () = 
  let b = BitSet.empty () in
  BitSet.set b 8;
  BitSet.set b 9;
  assert (not (BitSet.is_set b 7));
  assert (BitSet.is_set b 8);
  assert (BitSet.is_set b 9);
  assert (not (BitSet.is_set b 10));
  ()

let test_enum_1 () = 
  let b = BitSet.empty () in
  BitSet.set b 0;
  BitSet.set b 1;
  let e = BitSet.enum b in
  let a = Enum.get e in
  let b = Enum.get e in
  assert (Option.get a = 0);
  assert (Option.get b = 1);
  ()

let test_enum_2 () = 
  let n = 13 in
  let b = BitSet.empty () in
  for i = 0 to n do
    BitSet.set b i
  done;
  let e = BitSet.enum b in
  for i = 0 to n do
    let a = Enum.get e in
    match a with
      Some v -> assert (v = i)
    | None -> assert false
  done;
  assert (Enum.get e = None);
  ()

let test_enum_3 () = 
  let b = BitSet.empty () in
  BitSet.set b 9;
  BitSet.set b 10;
  let e = BitSet.enum b in
  let i = Enum.get e in
  let j = Enum.get e in
  assert (Option.get i = 9);
  begin
    match j with
      Some v -> 
        assert (v = 10);
    | None -> 
        assert false (* Should NOT come here! *)
  end;
  assert (Enum.get e = None);
  ()

(* Bug reported by Pascal Zimmer on Feb 27, 2007.  The latter assert
   returned None when it should've returned Some 9. *)
let test_enum_regr_pz () = 
  let b = BitSet.empty () in
  BitSet.set b 8;
  BitSet.set b 9;
  let e = BitSet.enum b in
  let i = Enum.get e in
  let j = Enum.get e in
  assert (Option.get i = 8);
  begin
    match j with
      Some v -> 
        assert (v = 9);
    | None -> 
        assert false (* Should NOT come here! *)
  end;
  ()


let register () =
  Util.register "BitSet" [
    "basic", test_bs_1;
    "enum_1", test_enum_1;
    "enum_2", test_enum_2;
    "enum_3", test_enum_3;
    "enum_regr_pz", test_enum_regr_pz;
    "intersect", test_intersect;
    "diff", test_diff;
    "sym_diff", test_sym_diff;
    "rnd_creation", test_rnd_creation;
    "empty", test_empty;
    "exceptions", test_exceptions;
    "compare", test_compare;
    "compare_2", test_compare_2;
    "compare_3", test_compare_3;
    "set_opers", test_set_opers;
    "unite",test_unite;
    "intersect_2",test_intersect_2;
    "differentiate",test_differentiate;
    "differentiate_sym", test_differentiate_sym;
  ]
