use crate::node::script::NodeScript::*;
use crate::node::script::NodeTest;

use lib_ot::core::{Changeset, NodeData, NodeDataBuilder};

#[test]
fn operation_delete_nested_node_test() {
  let mut test = NodeTest::new();
  let image_a = NodeData::new("image_a");
  let image_b = NodeData::new("image_b");

  let video_a = NodeData::new("video_a");
  let video_b = NodeData::new("video_b");

  let image_1 = NodeDataBuilder::new("image_1")
    .add_node_data(image_a.clone())
    .add_node_data(image_b.clone())
    .build();
  let video_1 = NodeDataBuilder::new("video_1")
    .add_node_data(video_a.clone())
    .add_node_data(video_b)
    .build();

  let text_node_1 = NodeDataBuilder::new("text_1")
    .add_node_data(image_1)
    .add_node_data(video_1.clone())
    .build();

  let image_2 = NodeDataBuilder::new("image_2")
    .add_node_data(image_a)
    .add_node_data(image_b.clone())
    .build();
  let text_node_2 = NodeDataBuilder::new("text_2")
    .add_node_data(image_2)
    .build();

  let scripts = vec![
    InsertNode {
      path: 0.into(),
      node_data: text_node_1,
      rev_id: 1,
    },
    InsertNode {
      path: 1.into(),
      node_data: text_node_2,
      rev_id: 2,
    },
    // 0:text_1
    //      0:image_1
    //             0:image_a
    //             1:image_b
    //      1:video_1
    //             0:video_a
    //             1:video_b
    // 1:text_2
    //      0:image_2
    //             0:image_a
    //             1:image_b
    DeleteNode {
      path: vec![0, 0, 0].into(),
      rev_id: 3,
    },
    AssertNode {
      path: vec![0, 0, 0].into(),
      expected: Some(image_b),
    },
    AssertNode {
      path: vec![0, 1].into(),
      expected: Some(video_1),
    },
    DeleteNode {
      path: vec![0, 1, 1].into(),
      rev_id: 4,
    },
    AssertNode {
      path: vec![0, 1, 0].into(),
      expected: Some(video_a),
    },
  ];
  test.run_scripts(scripts);
}

#[test]
fn operation_delete_node_with_revision_conflict_test() {
  let mut test = NodeTest::new();
  let text_1 = NodeDataBuilder::new("text_1").build();
  let text_2 = NodeDataBuilder::new("text_2").build();
  let text_3 = NodeDataBuilder::new("text_3").build();

  let scripts = vec![
    InsertNode {
      path: 0.into(),
      node_data: text_1.clone(),
      rev_id: 1,
    },
    InsertNode {
      path: 1.into(),
      node_data: text_2,
      rev_id: 2,
    },
    // The node's in the tree will be:
    // 0: text_1
    // 2: text_2
    //
    // The insert action is happened concurrently with the delete action, because they
    // share the same rev_id. aka, 3. The delete action is want to delete the node at index 1,
    // but it was moved to index 2.
    InsertNode {
      path: 1.into(),
      node_data: text_3.clone(),
      rev_id: 3,
    },
    // 0: text_1
    // 1: text_3
    // 2: text_2
    //
    // The path of the delete action will be transformed to a new path that point to the text_2.
    // 1 -> 2
    DeleteNode {
      path: 1.into(),
      rev_id: 3,
    },
    // After perform the delete action, the tree will be:
    // 0: text_1
    // 1: text_3
    AssertNumberOfChildrenAtPath {
      path: None,
      expected: 2,
    },
    AssertNode {
      path: 0.into(),
      expected: Some(text_1),
    },
    AssertNode {
      path: 1.into(),
      expected: Some(text_3),
    },
    AssertNode {
      path: 2.into(),
      expected: None,
    },
  ];
  test.run_scripts(scripts);
}

#[test]
fn operation_update_node_after_delete_test() {
  let mut test = NodeTest::new();
  let text_1 = NodeDataBuilder::new("text_1").build();
  let text_2 = NodeDataBuilder::new("text_2").build();
  let scripts = vec![
    InsertNode {
      path: 0.into(),
      node_data: text_1,
      rev_id: 1,
    },
    InsertNode {
      path: 1.into(),
      node_data: text_2,
      rev_id: 2,
    },
    DeleteNode {
      path: 0.into(),
      rev_id: 3,
    },
    // The node at path 1 is not exist. The following UpdateBody script will do nothing
    AssertNode {
      path: 1.into(),
      expected: None,
    },
    UpdateBody {
      path: 1.into(),
      changeset: Changeset::Delta {
        delta: Default::default(),
        inverted: Default::default(),
      },
    },
  ];
  test.run_scripts(scripts);
}
