open OUnit2

module VT = Vytree
module CT = Config_tree

(* Setting a value of a node that doesn't exist should create the node *)
let test_set_create_node test_ctxt =
    let path = ["foo"; "bar"] in
    let node = CT.make "root" in
    let node = CT.set node path (Some "baz") CT.ReplaceValue in
    let values = CT.get_values node path in
    assert_equal values ["baz"]

(* Setting a value with AddValue behaviour should append the new value *)
let test_set_add_value test_ctxt =
    let path = ["foo"; "bar"] in
    let node = CT.make "root" in
    let node = CT.set node path (Some "baz") CT.AddValue in
    let node = CT.set node path (Some "quux") CT.AddValue in
    let values = CT.get_values node path in
    assert_equal values ["baz"; "quux"]

(* Setting a value with ReplaceValue behaviour should replace the value *)
let test_set_replace_value test_ctxt =
    let path = ["foo"; "bar"] in
    let node = CT.make "root" in
    let node = CT.set node path (Some "baz") CT.ReplaceValue in
    let node = CT.set node path (Some "quux") CT.ReplaceValue in
    let values = CT.get_values node path in
    assert_equal values ["quux"]

(* Creating a node without a value should work *)
let test_create_valueless_node test_ctxt =
    let path = ["foo"; "bar"] in
    let node = CT.make "root" in
    let node = CT.set node path None CT.AddValue in
    assert_equal (CT.get_values node path) []

(* Deleting just one of many values should keep all other values intact *)
let test_delete_just_value test_ctxt =
    let path = ["foo"; "bar"] in
    let node = CT.make "root" in
    let node = CT.set node path (Some "baz") CT.AddValue in
    let node = CT.set node path (Some "quux") CT.AddValue in
    let node = CT.delete node path (Some "quux") in
    assert_equal (CT.get_values node path) ["baz"]

(* Deleting the last value should delete the whole leaf *)
let test_delete_last_value test_ctxt =
    let path = ["foo"; "bar"] in
    let node = CT.make "root" in
    let node = CT.set node path (Some "baz") CT.AddValue in
    let node = CT.delete node path (Some "baz") in
    assert_equal ((not (VT.exists node path)) && (VT.exists node ["foo"])) true

(* Deleting a non-leaf node should delete the whole subtree *)
let test_delete_subtree test_ctxt =
    let path = ["foo"; "bar"] in
    let node = CT.make "root" in
    let node = CT.set node path (Some "baz") CT.AddValue in
    let node = CT.delete node ["foo"] None in
    assert_equal (VT.list_children node) []

(* Setting a node comment for an existent node should work *)
let test_set_comment test_ctxt =
    let path = ["foo"; "bar"] in
    let node = CT.make "root" in
    let node = CT.set node path None CT.AddValue in
    let node = CT.set_comment node path (Some "comment") in
    assert_equal (CT.get_comment node path) (Some "comment")

(* Creating a node without a value should default inactive and ephemeral to false *)
let test_valueless_node_inactive_ephemeral test_ctxt =
    let path = ["foo"; "bar"] in
    let node = CT.make "root" in
    let node = CT.set node path None CT.AddValue in
    assert_equal ((not (CT.is_inactive node path)) && (not (CT.is_ephemeral node path))) true

(* Setting a node inactive should work *)
let test_set_inactive test_ctxt = 
    let path = ["foo"; "bar"] in
    let node = CT.make "root" in
    let node = CT.set node path None CT.AddValue in
    let node = CT.set_inactive node path (true) in
    assert_equal (CT.is_inactive node path) true

(* Setting a node ephemeral should work *)
let test_set_ephemeral test_ctxt = 
    let path = ["foo"; "bar"] in
    let node = CT.make "root" in
    let node = CT.set node path None CT.AddValue in
    let node = CT.set_ephemeral node path (true) in
    assert_equal (CT.is_ephemeral node path) true

(*** Refactoring test setup *)
let set ?(how=CT.AddValue) path value node = CT.set node path value how

let config_tree_of_path path = CT.make "root" |> set path None

let set_in_config_tree ~how ?(path=[]) ?value =
    let node = config_tree_of_path path in
    how node path value

let toggle_in_config_tree ~how ?(path=[]) ?(value=false) =
    let node = config_tree_of_path path in
    how node path value

let load_reftree test_ctxt =
    let file_name = "interface_definition_sample.xml" in
    let r = Vytree.make Reference_tree.default_data "root" in
    Reference_tree.load_from_xml r (in_testdata_dir test_ctxt [file_name])

let foobar = ["foo"; "bar"]

(*** Rendering tests *)

(**** Standalone rendering *)
let test_render_nested_empty_with_comment test_ctxt =
    let rendered = CT.render @@
        set_in_config_tree
            ~how:CT.set_comment ~value:"comment"
            ~path:foobar
    in
    assert_equal rendered
"root {
    foo {
        /*comment*/
        bar { }
    }
}"

let test_render_ephemeral_hidden teset_ctxt =
    let rendered = CT.render @@
        toggle_in_config_tree
            ~how:CT.set_ephemeral ~value:true
            ~path:foobar
    in
    assert_equal rendered
"root {
    foo { }
}"

let test_render_ephemeral_shown teset_ctxt =
    let rendered = CT.render ~showephemeral:true @@
        toggle_in_config_tree
            ~how:CT.set_ephemeral ~value:true
            ~path:foobar
    in
    assert_equal rendered
"root {
    foo {
        #EPHEMERAL bar { }
    }
}"

let test_render_at_level test_ctxt =
    let path = ["foo"; "bar"; "baz"] in
    let node = CT.make "root" in
    let node = CT.set node path (Some "quux") CT.AddValue in
    let rendered = CT.render_at_level node ["foo"] in
    assert_equal (String.trim rendered)
"bar {
    baz quux;
}"

let test_render_at_level_top test_ctxt =
    let path1 = ["foo"; "bar"] in
    let path2 = ["baz"; "quux"] in
    let node = CT.make "root" in
    let node = CT.set node path1 (Some "quuux") CT.AddValue in
    let node = CT.set node path2 (Some "xyzzy") CT.AddValue in
    let rendered = CT.render_at_level node [] in
    assert_equal (String.trim rendered)
"baz {
    quux xyzzy;
}
foo {
    bar quuux;
}"

(**** Reftree-based rendering *)
let test_render_rt_tag_node test_ctxt =
    let reftree = load_reftree test_ctxt in
    let path = ["system"; "login"; "user"; "full-name"] in
    let node = CT.make "root" in
    let node = CT.set node path (Some "name here") CT.AddValue in
    let rendered_curly_config = CT.render ~reftree:(Some reftree) node in
    let desired_rendered_form =
"root {
    system {
        login {
             user full-name \"name here\";
        }
    }
}"
    in
    assert_equal rendered_curly_config desired_rendered_form

let test_render_rt_unspecified_node test_ctxt =
    let reftree = load_reftree test_ctxt in
    let path = ["system"; "login"; "user"; "unspecified_node"] in
    let node = CT.make "root" in
    let node = CT.set node path (Some "name here") CT.AddValue in
    let rendered_curly_config = CT.render ~reftree:(Some reftree) node in
    let desired_rendered_form =
"root {
    system {
        login {
             user unspecified_node \"name here\";
        }
    }
}"
    in
    assert_equal rendered_curly_config desired_rendered_form

let suite =
    "VyConf config tree tests" >::: [
        "test_set_create_node" >:: test_set_create_node;
        "test_set_add_value" >:: test_set_add_value;
        "test_set_replace_value" >:: test_set_replace_value;
        "test_create_valueless_node" >:: test_create_valueless_node;
        "test_delete_just_value" >:: test_delete_just_value;
        "test_delete_last_value" >:: test_delete_last_value;
        "test_delete_subtree" >:: test_delete_subtree;
        "test_set_comment" >:: test_set_comment;
        "test_valueless_node_inactive_ephemeral" >:: test_valueless_node_inactive_ephemeral;
        "test_set_inactive" >:: test_set_inactive;
        "test_set_ephemeral" >:: test_set_ephemeral;
        "test_render_nested_empty_with_comment" >:: test_render_nested_empty_with_comment;
        "test_render_ephemeral_hidden " >:: test_render_ephemeral_hidden;
        "test_render_ephemeral_shown"  >:: test_render_ephemeral_shown;
        "test_render_at_level" >:: test_render_at_level;
        "test_render_at_level_top" >:: test_render_at_level_top;
        "test_render_rt_tag_node" >:: test_render_rt_tag_node;
        "test_render_rt_unspecified_node" >:: test_render_rt_unspecified_node
    ]

let () =
    run_test_tt_main suite
