// Copyright 2018 The PDFium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "core/fxcrt/fx_string.h"
#include "core/fxcrt/xml/cfx_xmlelement.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace {

WideString ChildrenString(CFX_XMLElement* pParent) {
  WideString result;
  for (CFX_XMLNode* pChild = pParent->GetFirstChild(); pChild;
       pChild = pChild->GetNextSibling()) {
    result += static_cast<CFX_XMLElement*>(pChild)->GetName();
  }
  return result;
}

WideString ReverseChildrenString(CFX_XMLElement* pParent) {
  WideString result;
  for (CFX_XMLNode* pChild = pParent->GetLastChild(); pChild;
       pChild = pChild->GetPrevSibling()) {
    result = static_cast<CFX_XMLElement*>(pChild)->GetName() + result;
  }
  return result;
}

}  // namespace

TEST(CFXXMLNodeTest, GetParent) {
  CFX_XMLElement node1(L"node");
  CFX_XMLElement node2(L"node2");
  CFX_XMLElement node3(L"node3");

  node1.AppendLastChild(&node2);
  node2.AppendLastChild(&node3);

  EXPECT_FALSE(node1.GetParent());
  EXPECT_EQ(&node1, node2.GetParent());
  EXPECT_EQ(&node2, node3.GetParent());
}

TEST(CFXXMLNodeTest, GetRoot) {
  CFX_XMLElement node1(L"node");
  CFX_XMLElement node2(L"node2");
  CFX_XMLElement node3(L"node3");

  node1.AppendLastChild(&node2);
  node2.AppendLastChild(&node3);

  EXPECT_EQ(&node1, node1.GetRoot());
  EXPECT_EQ(&node1, node2.GetRoot());
  EXPECT_EQ(&node1, node3.GetRoot());
}

TEST(CFXXMLNodeTest, GetChildren) {
  CFX_XMLElement node1(L"node");
  CFX_XMLElement node2(L"node2");
  CFX_XMLElement node3(L"node3");
  CFX_XMLElement node4(L"node4");

  node1.AppendLastChild(&node2);
  node1.AppendLastChild(&node4);
  node2.AppendLastChild(&node3);

  EXPECT_EQ(&node2, node1.GetFirstChild());

  EXPECT_EQ(&node4, node2.GetNextSibling());
  EXPECT_EQ(&node3, node2.GetFirstChild());

  EXPECT_TRUE(node3.GetNextSibling() == nullptr);
  EXPECT_TRUE(node3.GetFirstChild() == nullptr);

  EXPECT_TRUE(node4.GetNextSibling() == nullptr);
  EXPECT_TRUE(node4.GetFirstChild() == nullptr);
}

TEST(CFXXMLNodeTest, DeleteChildren) {
  CFX_XMLElement node1(L"node");
  CFX_XMLElement node2(L"node2");
  CFX_XMLElement node3(L"node3");
  CFX_XMLElement node4(L"node4");

  node1.AppendLastChild(&node2);
  node1.AppendLastChild(&node4);
  node2.AppendLastChild(&node3);

  node1.RemoveAllChildren();
  EXPECT_TRUE(node1.GetFirstChild() == nullptr);
  EXPECT_TRUE(node2.GetParent() == nullptr);
  EXPECT_TRUE(node4.GetParent() == nullptr);

  // node2 and node4 should no longer be siblings.
  EXPECT_TRUE(node2.GetNextSibling() == nullptr);
  EXPECT_TRUE(node4.GetPrevSibling() == nullptr);

  // Deleting children doesn't change deleted substructure
  EXPECT_EQ(&node3, node2.GetFirstChild());
  EXPECT_TRUE(node3.GetParent() == &node2);
}

TEST(CFXXMLNodeTest, AddingChildren) {
  CFX_XMLElement parent(L"Root");
  CFX_XMLElement nodeA(L"A");
  CFX_XMLElement nodeB(L"B");

  parent.AppendLastChild(&nodeA);
  parent.AppendLastChild(&nodeB);

  EXPECT_EQ(L"AB", ChildrenString(&parent));
  EXPECT_EQ(L"AB", ReverseChildrenString(&parent));
  EXPECT_EQ(&parent, nodeA.GetParent());
  EXPECT_EQ(&parent, nodeB.GetParent());
  EXPECT_EQ(&nodeA, parent.GetFirstChild());
  EXPECT_EQ(&nodeB, nodeA.GetNextSibling());
  EXPECT_TRUE(nodeB.GetNextSibling() == nullptr);

  // Insert to negative appends last child.
  CFX_XMLElement nodeC(L"C");
  parent.InsertChildNode(&nodeC, -1);
  EXPECT_EQ(L"ABC", ChildrenString(&parent));
  EXPECT_EQ(L"ABC", ReverseChildrenString(&parent));
  EXPECT_EQ(&parent, nodeC.GetParent());
  EXPECT_EQ(&nodeC, nodeB.GetNextSibling());
  EXPECT_TRUE(nodeC.GetNextSibling() == nullptr);

  // Insertion occurs before a zero based index.
  CFX_XMLElement nodeD(L"D");
  parent.InsertChildNode(&nodeD, 1);
  EXPECT_EQ(L"ADBC", ChildrenString(&parent));
  EXPECT_EQ(L"ADBC", ReverseChildrenString(&parent));

  // Insert to 0 appends first child.
  CFX_XMLElement nodeE(L"E");
  parent.InsertChildNode(&nodeE, 0);
  EXPECT_EQ(L"EADBC", ChildrenString(&parent));
  EXPECT_EQ(L"EADBC", ReverseChildrenString(&parent));

  // Insert to out-of-bounds index appends last child.
  CFX_XMLElement nodeF(L"F");
  parent.InsertChildNode(&nodeF, 10);
  EXPECT_EQ(L"EADBCF", ChildrenString(&parent));
  EXPECT_EQ(L"EADBCF", ReverseChildrenString(&parent));
}

TEST(CFXXMLNodeTest, RemovingMiddleChild) {
  CFX_XMLElement node1(L"node1");
  CFX_XMLElement node2(L"node2");
  CFX_XMLElement node3(L"node3");
  CFX_XMLElement node4(L"node4");

  node1.AppendLastChild(&node2);
  node1.AppendLastChild(&node3);
  node1.AppendLastChild(&node4);

  EXPECT_EQ(L"node2node3node4", ChildrenString(&node1));
  EXPECT_EQ(L"node2node3node4", ReverseChildrenString(&node1));
  EXPECT_EQ(&node2, node1.GetFirstChild());
  EXPECT_EQ(&node3, node2.GetNextSibling());
  EXPECT_EQ(&node4, node3.GetNextSibling());
  EXPECT_TRUE(node4.GetNextSibling() == nullptr);

  node1.RemoveChild(&node3);

  EXPECT_EQ(L"node2node4", ChildrenString(&node1));
  EXPECT_EQ(L"node2node4", ReverseChildrenString(&node1));
  EXPECT_TRUE(node3.GetParent() == nullptr);
  EXPECT_TRUE(node3.GetNextSibling() == nullptr);
  EXPECT_TRUE(node3.GetPrevSibling() == nullptr);
  EXPECT_EQ(&node2, node1.GetFirstChild());
  EXPECT_EQ(&node4, node2.GetNextSibling());
  EXPECT_EQ(&node2, node4.GetPrevSibling());
  EXPECT_TRUE(node4.GetNextSibling() == nullptr);
}

TEST(CFXXMLNodeTest, RemovingFirstChild) {
  CFX_XMLElement node1(L"node1");
  CFX_XMLElement node2(L"node2");
  CFX_XMLElement node3(L"node3");
  CFX_XMLElement node4(L"node4");

  node1.AppendLastChild(&node2);
  node1.AppendLastChild(&node3);
  node1.AppendLastChild(&node4);

  EXPECT_EQ(L"node2node3node4", ChildrenString(&node1));
  EXPECT_EQ(L"node2node3node4", ReverseChildrenString(&node1));
  EXPECT_EQ(&node2, node1.GetFirstChild());
  EXPECT_EQ(&node3, node2.GetNextSibling());
  EXPECT_EQ(&node4, node3.GetNextSibling());
  EXPECT_TRUE(node4.GetNextSibling() == nullptr);

  node1.RemoveChild(&node2);

  EXPECT_EQ(L"node3node4", ChildrenString(&node1));
  EXPECT_EQ(L"node3node4", ReverseChildrenString(&node1));
  EXPECT_TRUE(node2.GetParent() == nullptr);
  EXPECT_TRUE(node2.GetNextSibling() == nullptr);
  EXPECT_TRUE(node2.GetPrevSibling() == nullptr);
  EXPECT_EQ(&node3, node1.GetFirstChild());
  EXPECT_TRUE(node3.GetPrevSibling() == nullptr);
  EXPECT_EQ(&node4, node3.GetNextSibling());
  EXPECT_TRUE(node4.GetNextSibling() == nullptr);
}

TEST(CFXXMLNodeTest, RemovingLastChild) {
  CFX_XMLElement node1(L"node1");
  CFX_XMLElement node2(L"node2");
  CFX_XMLElement node3(L"node3");
  CFX_XMLElement node4(L"node4");

  node1.AppendLastChild(&node2);
  node1.AppendLastChild(&node3);
  node1.AppendLastChild(&node4);

  EXPECT_EQ(L"node2node3node4", ChildrenString(&node1));
  EXPECT_EQ(L"node2node3node4", ReverseChildrenString(&node1));
  EXPECT_EQ(&node2, node1.GetFirstChild());
  EXPECT_EQ(&node3, node2.GetNextSibling());
  EXPECT_EQ(&node4, node3.GetNextSibling());
  EXPECT_TRUE(node4.GetNextSibling() == nullptr);

  node1.RemoveChild(&node4);

  EXPECT_EQ(L"node2node3", ChildrenString(&node1));
  EXPECT_EQ(L"node2node3", ReverseChildrenString(&node1));
  EXPECT_TRUE(node4.GetParent() == nullptr);
  EXPECT_TRUE(node4.GetNextSibling() == nullptr);
  EXPECT_TRUE(node4.GetPrevSibling() == nullptr);
  EXPECT_EQ(&node2, node1.GetFirstChild());
  EXPECT_EQ(&node3, node2.GetNextSibling());
  EXPECT_TRUE(node3.GetNextSibling() == nullptr);
}

TEST(CFXXMLNodeTest, RemovingOnlyChild) {
  CFX_XMLElement node1(L"node1");
  CFX_XMLElement node2(L"node2");

  node1.AppendLastChild(&node2);

  EXPECT_EQ(&node2, node1.GetFirstChild());
  EXPECT_TRUE(node2.GetNextSibling() == nullptr);

  node1.RemoveChild(&node2);
  EXPECT_TRUE(node2.GetParent() == nullptr);

  EXPECT_TRUE(node1.GetFirstChild() == nullptr);
  EXPECT_TRUE(node2.GetNextSibling() == nullptr);
  EXPECT_TRUE(node2.GetPrevSibling() == nullptr);
}

TEST(CFXXMLNodeTest, RemoveMissingChild) {
  CFX_XMLElement node1(L"node1");
  CFX_XMLElement node2(L"node2");
  CFX_XMLElement node3(L"node3");

  node1.AppendLastChild(&node2);
  EXPECT_DEATH(node1.RemoveChild(&node3), "");
}
