open OUnit2
open Vytree

(* Destructuting a freshly made node gives us what
   we made it from *)
let test_make_node test_ctxt =
    let node = make () "root" in
    assert_equal (name_of_node node) "root";
    assert_equal (data_of_node node) ();
    assert_equal (children_of_node node) []

(* XXX: these comparisons are awkward, but this is
        probably the only way to track the problem
        down to insert if insert breaks *)

(* Inserting at single-item path adds a child to
   children list *)
let test_insert_immediate_child test_ctxt =
    let node = make () "root" in
    let node' = insert node ["foo"] () in
    assert_equal (children_of_node node')
                 [make () "foo"]

(* Inserting one child after another works.
   The default behaviour is to insert new items at the beginning.  *)
let test_insert_multiple_children test_ctxt =
    let node = make () "root" in
    let node' = insert node ["foo"] () in
    let node'' = insert node' ["bar"] () in
    assert_equal (children_of_node node'')
                 [make () "bar"; make () "foo"]

(* Inserting a child at a two-item path creates a tree
   two levels deep *)
let test_insert_multi_level test_ctxt =
    let node = make () "root" in
    let node = insert node ["foo"] () in
    let node = insert node ["foo"; "bar"] () in
    let bar = make () "bar" in
    let foo = make_full () "foo" [bar] in
    let root = make_full () "root" [foo] in
    assert_equal root node

(* Inserting duplicate child fails *)
let test_insert_duplicate_child test_ctxt =
    let node = make () "root" in
    let node = insert node ["foo"] () in
    assert_raises Duplicate_child (fun () -> insert node ["foo"] ())

(* Inserting a child at the end works *)
let test_insert_multiple_children_end test_ctxt =
    let node = make () "root" in
    let node = insert node ["foo"] () in
    let node = insert ~position:End node ["bar"] () in
    assert_equal (children_of_node node)
                 [make () "foo"; make () "bar"]

(* list_children correctly returns a list of children names *)
let test_list_children test_ctxt =
    let node = make () "root" in
    let node = insert node ["foo"] () in
    let node = insert node ["bar"] () in
    assert_equal (list_children node) ["bar"; "foo"]

(* Deleting a child, well, deletes it *)
let test_delete_immediate_child test_ctxt =
    let node = make () "root" in
    let node' = insert node ["foo"] () in
    let node' = delete node' ["foo"] in
    assert_equal node node'

(* Deleting a child at multi-level path works *)
let test_delete_multi_level test_ctxt =
    let node = make () "root" in
    let node' = insert node ["foo"] () in
    let node' = insert node' ["foo"; "bar"] () in
    let foo_node = insert node ["foo"] () in
    let node' = delete node' ["foo"; "bar"] in
    assert_equal node' foo_node

(* Attempt to delete a node at non-existent path raises an exception *)
let test_delete_nonexistent test_ctxt =
    let node = make () "root" in
    assert_raises Nonexistent_path (fun () -> delete node ["foo"; "bar"])

(* get_child works with immediate children *)
let test_get_immediate_child test_ctxt =
    let node = make () "root" in
    let node' = insert node ["foo"] () in
    assert_equal (name_of_node (get node' ["foo"])) "foo"

(* get_child works with multi-level paths *)
let test_get_child_multilevel test_ctxt =
    let node = make () "root" in
    let node = insert node ["foo"] () in
    let node = insert node ["foo"; "bar"] () in
    assert_equal (name_of_node (get node ["foo"; "bar"])) "bar"

(* get_child raises Nonexistent_path for non-existent paths *)
let test_get_child_nonexistent test_ctxt =
    let node = make () "root" in
    assert_raises Nonexistent_path (fun () -> get node ["foo"; "bar"])

(* update works *)
let test_update test_ctxt =
    let node = make 0 "root" in
    let node = insert node ["foo"] 1 in
    assert_equal (data_of_node (get (update node ["foo"] 9) ["foo"])) 9

(* get_existent_path works *)
let test_get_existent_path test_ctxt =
    let node = make () "root" in
    let node = insert node ["foo"] () in
    let node = insert node ["foo"; "bar"] () in
    assert_equal (get_existent_path node ["foo"; "bar"; "baz"]) ["foo"; "bar"]

let test_exists_existent test_ctxt =
    let node = make () "root" in
    let node = insert node ["foo"] () in
    let node = insert node ["foo"; "bar"] () in
    assert_equal (exists node ["foo"; "bar"]) true

let test_exists_nonexistent test_ctxt =
    let node = make () "root" in
    let node = insert node ["foo"] () in
    let node = insert node ["foo"; "bar"] () in
    assert_equal (exists node ["foo"; "bar"; "baz"]) false

let test_get_data test_ctxt =
    let node = make 0 "root" in
    let node = insert node ["foo"] 1 in
    let node = insert node ["foo"; "bar"] 42 in
    assert_equal (get_data node ["foo"; "bar"]) 42

(* merge_children should have no effect if there are
   no children with duplicate names *)
let test_merge_children_no_duplicates test_ctxt =
    let node = make_full () "root"
      [make_full () "foo" [make () "bar"];
       make () "bar";
       make_full () "baz" [make () "quuz"]] in
    let node' = merge_children (fun x y -> x) node in
    assert_equal (list_children node') ["foo"; "bar"; "baz"]


(* If node has children with duplicate names, then
   1. Only the first should be left
   2. Children of all other nodes should be appended to its own *)
let test_merge_children_has_duplicates test_ctxt =
    let node = make_full () "root"
      [make_full () "foo" [make () "bar"];
       make () "quux";
       make_full () "foo" [make () "baz"]] in
    let node' = merge_children (fun x y -> x) node in
    assert_equal (list_children node') ["foo"; "quux"];
    assert_equal (get node' ["foo"] |> list_children) ["bar"; "baz"]

let suite =
    "VyConf tree tests" >::: [
        "test_make_node" >:: test_make_node;
        "test_insert_immediate_child" >:: test_insert_immediate_child;
        "test_insert_multiple_children" >:: test_insert_multiple_children;
        "test_insert_multi_level" >:: test_insert_multi_level;
        "test_insert_duplicate_child" >:: test_insert_duplicate_child;
        "test_insert_multiple_children_end" >:: test_insert_multiple_children_end;
        "test_list_children" >:: test_list_children;
        "test_delete_immediate_child" >:: test_delete_immediate_child;
        "test_delete_multi_level" >:: test_delete_multi_level;
        "test_delete_nonexistent" >:: test_delete_nonexistent;
        "test_get_immediate_child" >:: test_get_immediate_child;
        "test_get_child_multilevel" >:: test_get_child_multilevel;
        "test_get_child_nonexistent" >:: test_get_child_nonexistent;
        "test_update" >:: test_update;
        "test_get_existent_path" >:: test_get_existent_path;
        "test_exists_existent" >:: test_exists_existent;
        "test_exists_nonexistent" >:: test_exists_nonexistent;
        "test_get_data" >:: test_get_data;
        "test_merge_children_has_duplicates" >:: test_merge_children_has_duplicates;
        "test_merge_children_no_duplicates" >:: test_merge_children_no_duplicates;
    ]

let () =
  run_test_tt_main suite