/*
 * Copyright 2017 Open Source Robotics Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

#include <gtest/gtest.h>

#include "sdf/Element.hh"
#include "sdf/Filesystem.hh"
#include "sdf/Param.hh"

/////////////////////////////////////////////////
TEST(Element, New)
{
  sdf::Element elem;

  ASSERT_FALSE(elem.GetCopyChildren());
  ASSERT_EQ(elem.ReferenceSDF(), "");
  ASSERT_EQ(elem.GetParent(), nullptr);
}

/////////////////////////////////////////////////
TEST(Element, Child)
{
  sdf::Element child;
  sdf::ElementPtr parent = std::make_shared<sdf::Element>();
  parent->SetFilePath("/parent/path/model.sdf");
  parent->SetOriginalVersion("1.5");

  ASSERT_EQ(child.GetParent(), nullptr);

  child.SetParent(parent);

  ASSERT_NE(child.GetParent(), nullptr);
  EXPECT_EQ("/parent/path/model.sdf", child.FilePath());
  EXPECT_EQ("1.5", child.OriginalVersion());
}

/////////////////////////////////////////////////
TEST(Element, Name)
{
  sdf::Element elem;

  ASSERT_EQ(elem.GetName(), "");

  elem.SetName("test");

  ASSERT_EQ(elem.GetName(), "test");
}

/////////////////////////////////////////////////
TEST(Element, Required)
{
  sdf::Element elem;

  ASSERT_EQ(elem.GetRequired(), "");

  elem.SetRequired("1");

  ASSERT_EQ(elem.GetRequired(), "1");
}

/////////////////////////////////////////////////
TEST(Element, SetExplicitlySetInFile)
{
  // The heirarchy in xml:
  // <parent>
  //   <elem>
  //     <child>
  //       <child2>
  //         <grandChild/>
  //       <child2>
  //     </child>
  //     <sibling/>
  //     <sibling2/>
  //   </elem>
  //   <elem2/>
  // </parent>

  sdf::ElementPtr parent = std::make_shared<sdf::Element>();
  sdf::ElementPtr elem = std::make_shared<sdf::Element>();
  elem->SetParent(parent);
  parent->InsertElement(elem);
  sdf::ElementPtr elem2 = std::make_shared<sdf::Element>();
  elem2->SetParent(parent);
  parent->InsertElement(elem2);

  EXPECT_TRUE(elem->GetExplicitlySetInFile());

  elem->SetExplicitlySetInFile(false);

  EXPECT_FALSE(elem->GetExplicitlySetInFile());

  elem->SetExplicitlySetInFile(true);

  EXPECT_TRUE(elem->GetExplicitlySetInFile());

  // the childs and siblings of the element should all be
  // set to the same value when using this function
  sdf::ElementPtr child = std::make_shared<sdf::Element>();
  child->SetParent(elem);
  elem->InsertElement(child);

  sdf::ElementPtr sibling = std::make_shared<sdf::Element>();
  sibling->SetParent(elem);
  elem->InsertElement(sibling);

  sdf::ElementPtr sibling2 = std::make_shared<sdf::Element>();
  sibling2->SetParent(elem);
  elem->InsertElement(sibling2);

  sdf::ElementPtr child2 = std::make_shared<sdf::Element>();
  child2->SetParent(child);
  child->InsertElement(child2);

  sdf::ElementPtr grandChild = std::make_shared<sdf::Element>();
  grandChild->SetParent(child);
  child->InsertElement(grandChild);

  EXPECT_TRUE(elem->GetExplicitlySetInFile());
  EXPECT_TRUE(child->GetExplicitlySetInFile());
  EXPECT_TRUE(sibling->GetExplicitlySetInFile());
  EXPECT_TRUE(sibling2->GetExplicitlySetInFile());
  EXPECT_TRUE(child2->GetExplicitlySetInFile());
  EXPECT_TRUE(grandChild->GetExplicitlySetInFile());
  EXPECT_TRUE(elem2->GetExplicitlySetInFile());

  elem->SetExplicitlySetInFile(false);
  EXPECT_FALSE(elem->GetExplicitlySetInFile());
  EXPECT_FALSE(child->GetExplicitlySetInFile());
  EXPECT_FALSE(sibling->GetExplicitlySetInFile());
  EXPECT_FALSE(sibling2->GetExplicitlySetInFile());
  EXPECT_FALSE(child2->GetExplicitlySetInFile());
  EXPECT_FALSE(grandChild->GetExplicitlySetInFile());

  // SetExplicitlySetInFile(false) is be called only on `elem`. We expect
  // GetExplicitlySetInFile() to be false for all children and grandchildren of
  // `elem`, but true for `elem2`, which is a sibling of `elem`.
  EXPECT_TRUE(elem2->GetExplicitlySetInFile());
}

/////////////////////////////////////////////////
TEST(Element, SetExplicitlySetInFileWithInsert)
{
  sdf::ElementPtr parent = std::make_shared<sdf::Element>();
  parent->SetExplicitlySetInFile(false);
  sdf::ElementPtr child = std::make_shared<sdf::Element>();
  child->SetParent(parent);
  parent->InsertElement(child);

  EXPECT_FALSE(parent->GetExplicitlySetInFile());
  EXPECT_TRUE(child->GetExplicitlySetInFile());
}

/////////////////////////////////////////////////
TEST(Element, CopyChildren)
{
  sdf::Element elem;

  ASSERT_FALSE(elem.GetCopyChildren());

  elem.SetCopyChildren(true);

  ASSERT_TRUE(elem.GetCopyChildren());
}

/////////////////////////////////////////////////
TEST(Element, ReferenceSDF)
{
  sdf::Element elem;

  ASSERT_EQ(elem.ReferenceSDF(), "");

  elem.SetReferenceSDF("test");

  ASSERT_EQ(elem.ReferenceSDF(), "test");
}

/////////////////////////////////////////////////
TEST(Element, AddValue)
{
  sdf::Element elem;

  elem.SetName("test");
  elem.AddValue("string", "foo", false, "foo description");

  sdf::ParamPtr param = elem.GetValue();
  ASSERT_TRUE(param->IsType<std::string>());
  ASSERT_EQ(param->GetKey(), "test");
  ASSERT_EQ(param->GetTypeName(), "string");
  ASSERT_EQ(param->GetDefaultAsString(), "foo");
  ASSERT_EQ(param->GetDescription(), "foo description");
}

/////////////////////////////////////////////////
TEST(Element, AddAttribute)
{
  sdf::Element elem;

  ASSERT_EQ(elem.GetAttributeCount(), 0UL);

  elem.AddAttribute("test", "string", "foo", false, "foo description");
  ASSERT_EQ(elem.GetAttributeCount(), 1UL);

  elem.AddAttribute("attr", "float", "0.0", false, "float description");
  ASSERT_EQ(elem.GetAttributeCount(), 2UL);

  sdf::ParamPtr param = elem.GetAttribute("test");
  ASSERT_TRUE(param->IsType<std::string>());
  ASSERT_EQ(param->GetKey(), "test");
  ASSERT_EQ(param->GetTypeName(), "string");
  ASSERT_EQ(param->GetDefaultAsString(), "foo");
  ASSERT_EQ(param->GetDescription(), "foo description");

  param = elem.GetAttribute("attr");
  ASSERT_TRUE(param->IsType<float>());
  ASSERT_EQ(param->GetKey(), "attr");
  ASSERT_EQ(param->GetTypeName(), "float");
  ASSERT_EQ(param->GetDefaultAsString(), "0");
  ASSERT_EQ(param->GetDescription(), "float description");
}

/////////////////////////////////////////////////
TEST(Element, GetAttributeSet)
{
  sdf::Element elem;
  ASSERT_EQ(elem.GetAttributeCount(), 0UL);
  elem.AddAttribute("test", "string", "foo", false, "foo description");
  ASSERT_EQ(elem.GetAttributeCount(), 1UL);

  EXPECT_FALSE(elem.GetAttributeSet("test"));
  elem.GetAttribute("test")->Set("asdf");
  EXPECT_TRUE(elem.GetAttributeSet("test"));
}

/////////////////////////////////////////////////
TEST(Element, RemoveAttribute)
{
  sdf::Element elem;
  ASSERT_EQ(elem.GetAttributeCount(), 0UL);

  elem.AddAttribute("test", "string", "foo", false, "foo description");
  elem.AddAttribute("attr", "float", "0.0", false, "float description");
  ASSERT_EQ(elem.GetAttributeCount(), 2UL);

  elem.RemoveAttribute("test");
  EXPECT_EQ(elem.GetAttributeCount(), 1UL);
  EXPECT_EQ(elem.GetAttribute("test"), nullptr);
  EXPECT_NE(elem.GetAttribute("attr"), nullptr);
}

/////////////////////////////////////////////////
TEST(Element, RemoveAllAttributes)
{
  sdf::Element elem;
  ASSERT_EQ(elem.GetAttributeCount(), 0UL);

  elem.AddAttribute("test", "string", "foo", false, "foo description");
  elem.AddAttribute("test2", "string", "foo", false, "foo description");
  elem.AddAttribute("attr", "float", "0.0", false, "float description");
  ASSERT_EQ(elem.GetAttributeCount(), 3UL);

  elem.RemoveAllAttributes();
  EXPECT_EQ(elem.GetAttributeCount(), 0UL);
  EXPECT_EQ(elem.GetAttribute("test"), nullptr);
  EXPECT_EQ(elem.GetAttribute("test2"), nullptr);
  EXPECT_EQ(elem.GetAttribute("attr"), nullptr);
}

/////////////////////////////////////////////////
TEST(Element, Include)
{
  sdf::Element elem;

  ASSERT_EQ(elem.GetInclude(), "");

  elem.SetInclude("foo.txt");

  ASSERT_EQ(elem.GetInclude(), "foo.txt");
}

/////////////////////////////////////////////////
TEST(Element, Description)
{
  sdf::Element elem;

  ASSERT_EQ(elem.GetDescription(), "");

  elem.SetDescription("Element description");

  ASSERT_EQ(elem.GetDescription(), "Element description");
}

/////////////////////////////////////////////////
TEST(Element, AddElementNoDescription)
{
  sdf::Element elem;

  sdf::ElementPtr ptr = elem.AddElement("foo");

  ASSERT_EQ(ptr, sdf::ElementPtr());
}

/////////////////////////////////////////////////
TEST(Element, AddElementReferenceSDF)
{
  // TODO(clalancette): The Element class uses shared_from_this(), and one of
  // the prerequisites of it is that the object must *already* have a
  // shared_ptr to it before calling shared_from_this().  The fallout from that
  // is that you cannot instantiate an object using the normal constructor,
  // and then call the .AddElement() method.  You must make a shared_ptr,
  // and then you can call ->AddElement(), which works.  This is kind of a
  // weird restriction on a class, so we might want to make this better.
  sdf::ElementPtr child = std::make_shared<sdf::Element>();
  sdf::ElementPtr parent = std::make_shared<sdf::Element>();
  sdf::ElementPtr desc = std::make_shared<sdf::Element>();
  desc->SetName("parent");
  parent->SetName("parent");
  parent->AddElementDescription(desc);

  child->SetParent(parent);

  child->SetReferenceSDF("refsdf");
  child->SetName("parent");

  ASSERT_EQ(child->GetElementDescriptionCount(), 0UL);
  sdf::ElementPtr ptr = child->AddElement("parent");

  ASSERT_EQ(child->GetElementDescriptionCount(), 1UL);

  sdf::ElementPtr check = child->GetElementDescription(4);
  ASSERT_EQ(check, sdf::ElementPtr());

  check = child->GetElementDescription(0);
  ASSERT_NE(check, sdf::ElementPtr());
  ASSERT_EQ(check->GetName(), "parent");

  check = child->GetElementDescription("bad");
  ASSERT_EQ(check, sdf::ElementPtr());

  check = child->GetElementDescription("parent");
  ASSERT_NE(check, sdf::ElementPtr());
  ASSERT_EQ(check->GetName(), "parent");

  ASSERT_FALSE(child->HasElement("foo"));
  ASSERT_TRUE(child->HasElement("parent"));

  child->Reset();
  ASSERT_EQ(child->GetElementDescriptionCount(), 0UL);
  ASSERT_EQ(child->GetAttributeCount(), 0UL);
}

/////////////////////////////////////////////////
TEST(Element, GetTemplates)
{
  sdf::ElementPtr elem = std::make_shared<sdf::Element>();

  elem->AddAttribute("test", "string", "foo", false, "foo description");

  std::string out = elem->Get<std::string>("test");
  ASSERT_EQ(out, "foo");

  std::pair<std::string, bool> pairout = elem->Get<std::string>("test", "def");
  ASSERT_EQ(pairout.first, "foo");
  ASSERT_EQ(pairout.second, true);

  bool found = elem->Get<std::string>("test", out, "def");
  ASSERT_EQ(out, "foo");
  ASSERT_EQ(found, true);
}

/////////////////////////////////////////////////
TEST(Element, Clone)
{
  sdf::ElementPtr parent = std::make_shared<sdf::Element>();
  sdf::ElementPtr child = std::make_shared<sdf::Element>();
  sdf::ElementPtr desc = std::make_shared<sdf::Element>();

  parent->InsertElement(child);
  ASSERT_NE(parent->GetFirstElement(), nullptr);

  parent->AddElementDescription(desc);
  ASSERT_EQ(parent->GetElementDescriptionCount(), 1UL);

  parent->AddAttribute("test", "string", "foo", false, "foo description");
  ASSERT_EQ(parent->GetAttributeCount(), 1UL);

  parent->AddValue("string", "foo", false, "foo description");

  parent->SetFilePath("/path/to/file.sdf");
  parent->SetOriginalVersion("1.5");

  sdf::ElementPtr newelem = parent->Clone();

  EXPECT_EQ("/path/to/file.sdf", newelem->FilePath());
  EXPECT_EQ("1.5", newelem->OriginalVersion());
  ASSERT_NE(newelem->GetFirstElement(), nullptr);
  ASSERT_EQ(newelem->GetElementDescriptionCount(), 1UL);
  ASSERT_EQ(newelem->GetAttributeCount(), 1UL);
  ASSERT_TRUE(newelem->GetExplicitlySetInFile());
}

/////////////////////////////////////////////////
TEST(Element, ClearElements)
{
  sdf::ElementPtr parent = std::make_shared<sdf::Element>();
  sdf::ElementPtr child = std::make_shared<sdf::Element>();

  parent->SetFilePath("/path/to/file.sdf");
  parent->SetOriginalVersion("1.5");
  child->SetParent(parent);
  parent->InsertElement(child);

  EXPECT_EQ("/path/to/file.sdf", parent->FilePath());
  EXPECT_EQ("1.5", parent->OriginalVersion());
  ASSERT_NE(parent->GetFirstElement(), nullptr);
  EXPECT_EQ("/path/to/file.sdf", parent->GetFirstElement()->FilePath());
  EXPECT_EQ("1.5", parent->GetFirstElement()->OriginalVersion());

  parent->ClearElements();

  ASSERT_EQ(parent->GetFirstElement(), nullptr);
  EXPECT_EQ("/path/to/file.sdf", parent->FilePath());
  EXPECT_EQ("1.5", parent->OriginalVersion());
}

/////////////////////////////////////////////////
TEST(Element, Clear)
{
  sdf::ElementPtr parent = std::make_shared<sdf::Element>();
  sdf::ElementPtr child = std::make_shared<sdf::Element>();

  parent->SetFilePath("/path/to/file.sdf");
  parent->SetOriginalVersion("1.5");
  child->SetParent(parent);
  parent->InsertElement(child);

  EXPECT_EQ("/path/to/file.sdf", parent->FilePath());
  EXPECT_EQ("1.5", parent->OriginalVersion());
  ASSERT_NE(parent->GetFirstElement(), nullptr);
  EXPECT_EQ("/path/to/file.sdf", parent->GetFirstElement()->FilePath());
  EXPECT_EQ("1.5", parent->GetFirstElement()->OriginalVersion());

  parent->Clear();

  ASSERT_EQ(parent->GetFirstElement(), nullptr);
  EXPECT_TRUE(parent->FilePath().empty());
  EXPECT_TRUE(parent->OriginalVersion().empty());
}

/////////////////////////////////////////////////
TEST(Element, ToStringElements)
{
  sdf::ElementPtr parent = std::make_shared<sdf::Element>();
  sdf::ElementPtr child = std::make_shared<sdf::Element>();

  parent->SetName("parent");
  child->SetName("child");

  parent->InsertElement(child);
  ASSERT_NE(parent->GetFirstElement(), nullptr);

  parent->AddAttribute("test", "string", "foo", false, "foo description");
  ASSERT_EQ(parent->GetAttributeCount(), 1UL);

  // attribute won't print unless it has been set
  EXPECT_FALSE(parent->GetAttributeSet("test"));
  EXPECT_EQ(parent->ToString("<!-- prefix -->"),
    "<!-- prefix --><parent>\n"
    "<!-- prefix -->  <child/>\n"
    "<!-- prefix --></parent>\n");

  sdf::ParamPtr test = parent->GetAttribute("test");
  ASSERT_NE(nullptr, test);
  EXPECT_FALSE(test->GetSet());
  EXPECT_TRUE(test->SetFromString("foo"));
  EXPECT_TRUE(test->GetSet());
  EXPECT_TRUE(parent->GetAttributeSet("test"));

  EXPECT_EQ(parent->ToString("<!-- prefix -->"),
    "<!-- prefix --><parent test='foo'>\n"
    "<!-- prefix -->  <child/>\n"
    "<!-- prefix --></parent>\n");
}

/////////////////////////////////////////////////
TEST(Element, ToStringRequiredAttributes)
{
  sdf::ElementPtr parent = std::make_shared<sdf::Element>();
  parent->AddAttribute("test", "string", "foo", true, "foo description");
  ASSERT_EQ(parent->GetAttributeCount(), 1UL);

  // A required attribute should print even if it has not been set
  EXPECT_FALSE(parent->GetAttributeSet("test"));
  EXPECT_EQ(parent->ToString("myprefix"), "myprefix< test='foo'/>\n");

  sdf::ParamPtr test = parent->GetAttribute("test");
  ASSERT_NE(nullptr, test);
  EXPECT_FALSE(test->GetSet());
  EXPECT_TRUE(test->SetFromString("bar"));
  EXPECT_TRUE(test->GetSet());
  EXPECT_TRUE(parent->GetAttributeSet("test"));

  EXPECT_EQ(parent->ToString("myprefix"), "myprefix< test='bar'/>\n");
}

/////////////////////////////////////////////////
TEST(Element, ToStringValue)
{
  sdf::ElementPtr parent = std::make_shared<sdf::Element>();

  parent->AddAttribute("test", "string", "foo", false, "foo description");
  ASSERT_EQ(parent->GetAttributeCount(), 1UL);

  parent->AddValue("string", "val", false, "val description");

  EXPECT_FALSE(parent->GetAttributeSet("test"));
  EXPECT_EQ(parent->ToString("myprefix"),
            "myprefix<>val</>\n");

  sdf::ParamPtr test = parent->GetAttribute("test");
  ASSERT_NE(nullptr, test);
  EXPECT_FALSE(test->GetSet());
  EXPECT_TRUE(test->SetFromString("foo"));
  EXPECT_TRUE(test->GetSet());
  EXPECT_TRUE(parent->GetAttributeSet("test"));
  EXPECT_EQ(parent->ToString("myprefix"),
            "myprefix< test='foo'>val</>\n");
}

/////////////////////////////////////////////////
TEST(Element, ToStringClonedElement)
{
  sdf::ElementPtr parent = std::make_shared<sdf::Element>();

  parent->AddAttribute("test", "string", "foo", false, "foo description");
  ASSERT_EQ(parent->GetAttributeCount(), 1UL);

  sdf::ParamPtr test = parent->GetAttribute("test");
  ASSERT_NE(nullptr, test);
  EXPECT_FALSE(test->GetSet());
  EXPECT_TRUE(test->SetFromString("foo"));
  EXPECT_TRUE(test->GetSet());

  sdf::ElementPtr parentClone = parent->Clone();
  EXPECT_TRUE(parentClone->GetAttributeSet("test"));
  EXPECT_EQ(parent->ToString("myprefix"), parentClone->ToString("myprefix"));
}

/////////////////////////////////////////////////
TEST(Element, ToStringInclude)
{
  sdf::Element elem;

  ASSERT_EQ(elem.GetInclude(), "");

  elem.SetInclude("foo.txt");

  ASSERT_EQ(elem.GetInclude(), "foo.txt");

  std::string stringval = elem.ToString("myprefix");
  ASSERT_EQ(stringval, "myprefix<include filename='foo.txt'/>\n");
}

/////////////////////////////////////////////////
TEST(Element, ToStringDefaultElements)
{
  sdf::ElementPtr parent = std::make_shared<sdf::Element>();
  parent->SetName("parent");
  sdf::ElementPtr elem = std::make_shared<sdf::Element>();
  elem->SetName("elem");
  elem->SetParent(parent);
  parent->InsertElement(elem);
  sdf::ElementPtr elem2 = std::make_shared<sdf::Element>();
  elem2->SetName("elem2");
  elem2->SetParent(parent);
  parent->InsertElement(elem2);

  std::ostringstream stream;
  stream
    << "<parent>\n"
    << "  <elem/>\n"
    << "  <elem2/>\n"
    << "</parent>\n";

  EXPECT_EQ(parent->ToString("", false, false), stream.str());
  EXPECT_EQ(parent->ToString(""), stream.str());
  EXPECT_EQ(parent->ToString("", true, false), stream.str());

  elem->SetExplicitlySetInFile(false);

  std::ostringstream stream2;
  stream2
    << "<parent>\n"
    << "  <elem2/>\n"
    << "</parent>\n";

  EXPECT_EQ(parent->ToString("", false, false), stream2.str());
  EXPECT_EQ(parent->ToString(""), stream.str());
  EXPECT_EQ(parent->ToString("", true, false), stream.str());

  parent->SetExplicitlySetInFile(false);

  EXPECT_EQ(parent->ToString("", false, false), "");
  EXPECT_EQ(parent->ToString(""), stream.str());
  EXPECT_EQ(parent->ToString("", true, false), stream.str());
}

/////////////////////////////////////////////////
TEST(Element, ToStringDefaultAttributes)
{
  sdf::ElementPtr element = std::make_shared<sdf::Element>();
  element->SetName("foo");
  element->AddAttribute("test", "string", "foo", false, "foo description");
  element->AddAttribute("test2", "string", "bar", true, "bar description");

  EXPECT_EQ(element->ToString(""), element->ToString("", true, false));
  EXPECT_EQ(element->ToString(""), element->ToString("", false, false));

  std::ostringstream stream;
  stream << "<foo test2='bar'/>\n";

  EXPECT_EQ(element->ToString(""), stream.str());
  EXPECT_EQ(element->ToString("", true, false), stream.str());
  EXPECT_EQ(element->ToString("", false, false), stream.str());

  std::ostringstream stream2;
  stream2 << "<foo test='foo' test2='bar'/>\n";

  EXPECT_EQ(element->ToString("", true, true), stream2.str());
  EXPECT_EQ(element->ToString("", false, true), stream2.str());
}

/////////////////////////////////////////////////
TEST(Element, DocLeftPane)
{
  sdf::Element elem;

  elem.SetDescription("Element description");
  elem.AddElementDescription(std::make_shared<sdf::Element>());

  std::string html;
  int index = 1;
  elem.PrintDocLeftPane(html, 0, index);
  ASSERT_EQ(html,
            "<a id='1' onclick='highlight(1);' href=\"#1\">&lt&gt</a>"
            "<div style='padding-left:0px;'>\n"
            "<a id='2' onclick='highlight(2);' href=\"#2\">&lt&gt</a>"
            "<div style='padding-left:4px;'>\n</div>\n</div>\n");
}

/////////////////////////////////////////////////
TEST(Element, DocRightPane)
{
  sdf::Element elem;

  elem.SetDescription("Element description");
  elem.AddAttribute("test", "string", "foo", false, "foo description");
  elem.AddValue("string", "val", false, "val description");
  elem.AddElementDescription(std::make_shared<sdf::Element>());

  std::string html;
  int index = 1;
  elem.PrintDocRightPane(html, 0, index);
  ASSERT_EQ(html,
            "<a name=\"1\">&lt&gt</a><div style='padding-left:0px;'>\n"
            "<div style='background-color: #ffffff'>\n"
            "<font style='font-weight:bold'>Description: </font>"
            "Element description<br>\n"
            "<font style='font-weight:bold'>Required: </font>"
            "&nbsp;&nbsp;&nbsp;\n<font style='font-weight:bold'>Type: </font>"
            "string&nbsp;&nbsp;&nbsp;\n"
            "<font style='font-weight:bold'>Default: </font>val\n"
            "</div>"
            "<div style='background-color: #dedede; padding-left:10px; "
            "display:inline-block;'>\n"
            "<font style='font-weight:bold'>Attributes</font><br>"
            "<div style='display: inline-block;padding-bottom: 4px;'>\n"
            "<div style='float:left; width: 80px;'>\n"
            "<font style='font-style: italic;'>test</font>: "
            "</div>\n"
            "<div style='float:left; padding-left: 4px; width: 300px;'>\n"
            "foo description<br>\n<font style='font-weight:bold'>Type: </font>"
            "string&nbsp;&nbsp;&nbsp;"
            "<font style='font-weight:bold'>Default: </font>"
            "foo<br>"
            "</div>\n"
            "</div>\n"
            "</div>\n"
            "<br>\n<a name=\"2\">&lt&gt</a>"
            "<div style='padding-left:4px;'>\n"
            "<div style='background-color: #ffffff'>\n"
            "<font style='font-weight:bold'>Description: </font>"
            "none<br>\n<font style='font-weight:bold'>Required: </font>"
            "&nbsp;&nbsp;&nbsp;\n"
            "<font style='font-weight:bold'>Type: </font>n/a\n"
            "</div>"
            "</div>\n"
            "</div>\n");
}

/////////////////////////////////////////////////
TEST(Element, SetEmpty)
{
  sdf::Element elem;

  ASSERT_FALSE(elem.Set<std::string>(""));
}

/////////////////////////////////////////////////
TEST(Element, Set)
{
  sdf::Element elem;

  elem.AddValue("string", "val", false, "val description");

  ASSERT_TRUE(elem.Set<std::string>("hello"));
}

/////////////////////////////////////////////////
TEST(Element, Copy)
{
  sdf::ElementPtr src = std::make_shared<sdf::Element>();
  sdf::ElementPtr dest = std::make_shared<sdf::Element>();

  src->SetName("test");
  src->SetFilePath("/path/to/file.sdf");
  src->SetOriginalVersion("1.5");
  src->AddValue("string", "val", false, "val description");
  src->AddAttribute("test", "string", "foo", false, "foo description");
  src->InsertElement(std::make_shared<sdf::Element>());

  dest->Copy(src);

  EXPECT_EQ("/path/to/file.sdf", dest->FilePath());
  EXPECT_EQ("1.5", dest->OriginalVersion());

  sdf::ParamPtr param = dest->GetValue();
  ASSERT_TRUE(param->IsType<std::string>());
  ASSERT_EQ(param->GetKey(), "test");
  ASSERT_EQ(param->GetTypeName(), "string");
  ASSERT_EQ(param->GetDefaultAsString(), "val");
  ASSERT_EQ(param->GetDescription(), "val description");

  ASSERT_EQ(dest->GetAttributeCount(), 1UL);
  ASSERT_TRUE(dest->GetExplicitlySetInFile());
  param = dest->GetAttribute("test");
  ASSERT_TRUE(param->IsType<std::string>());
  ASSERT_EQ(param->GetKey(), "test");
  ASSERT_EQ(param->GetTypeName(), "string");
  ASSERT_EQ(param->GetDefaultAsString(), "foo");
  ASSERT_EQ(param->GetDescription(), "foo description");

  ASSERT_NE(dest->GetFirstElement(), nullptr);
}

/////////////////////////////////////////////////
TEST(Element, CopyDestValue)
{
  sdf::ElementPtr src = std::make_shared<sdf::Element>();
  sdf::ElementPtr dest = std::make_shared<sdf::Element>();

  src->AddValue("string", "val", false, "val description");
  src->AddAttribute("test", "string", "foo", false, "foo description");
  src->InsertElement(std::make_shared<sdf::Element>());

  dest->AddValue("string", "val", false, "val description");
  dest->Copy(src);

  sdf::ParamPtr param = dest->GetValue();
  ASSERT_TRUE(param->IsType<std::string>());
  ASSERT_EQ(param->GetKey(), "");
  ASSERT_EQ(param->GetTypeName(), "string");
  ASSERT_EQ(param->GetDefaultAsString(), "val");
  ASSERT_EQ(param->GetDescription(), "val description");

  ASSERT_EQ(dest->GetAttributeCount(), 1UL);
  param = dest->GetAttribute("test");
  ASSERT_TRUE(param->IsType<std::string>());
  ASSERT_EQ(param->GetKey(), "test");
  ASSERT_EQ(param->GetTypeName(), "string");
  ASSERT_EQ(param->GetDefaultAsString(), "foo");
  ASSERT_EQ(param->GetDescription(), "foo description");

  ASSERT_NE(dest->GetFirstElement(), nullptr);
}

/////////////////////////////////////////////////
TEST(Element, GetNextElement)
{
  sdf::ElementPtr child = std::make_shared<sdf::Element>();
  sdf::ElementPtr parent = std::make_shared<sdf::Element>();

  child->SetParent(parent);

  ASSERT_EQ(child->GetNextElement("foo"), nullptr);
}

/////////////////////////////////////////////////
TEST(Element, GetNextElementMultiple)
{
  sdf::ElementPtr child1 = std::make_shared<sdf::Element>();
  sdf::ElementPtr child2 = std::make_shared<sdf::Element>();
  sdf::ElementPtr parent = std::make_shared<sdf::Element>();

  child1->SetParent(parent);
  child2->SetParent(parent);

  parent->InsertElement(child1);
  parent->InsertElement(child2);

  ASSERT_NE(child1->GetNextElement(""), nullptr);
  ASSERT_EQ(child2->GetNextElement(""), nullptr);
}

/////////////////////////////////////////////////
/// Helper function to add child elements without having to create descriptions
sdf::ElementPtr addChildElement(sdf::ElementPtr _parent,
                                const std::string &_elementName,
                                const bool _addNameAttribute,
                                const std::string &_childName)
{
  sdf::ElementPtr child = std::make_shared<sdf::Element>();
  child->SetParent(_parent);
  child->SetName(_elementName);
  _parent->InsertElement(child);

  if (_addNameAttribute)
  {
    child->AddAttribute("name", "string", _childName, false, "description");
  }
  return child;
}

/////////////////////////////////////////////////
TEST(Element, CountNamedElements)
{
  sdf::ElementPtr parent = std::make_shared<sdf::Element>();
  // expect empty map element with no children
  EXPECT_TRUE(parent->CountNamedElements().empty());
  EXPECT_TRUE(parent->CountNamedElements("child").empty());
  // expect empty set of element type names
  EXPECT_TRUE(parent->GetElementTypeNames().empty());
  // since there are no child names, they must be unique
  EXPECT_TRUE(parent->HasUniqueChildNames());
  EXPECT_TRUE(parent->HasUniqueChildNames("child"));


  // The following calls should make the following child elements:
  // <child name="child1"/>
  // <child name="child2"/>
  // <element name="child2"/>
  // <element name="child3"/>
  // <element />
  addChildElement(parent, "child", true, "child1");
  addChildElement(parent, "child", true, "child2");
  addChildElement(parent, "element", true, "child2");
  addChildElement(parent, "element", true, "child3");
  addChildElement(parent, "element", false, "unset");

  // test GetElementTypeNames
  auto typeNames = parent->GetElementTypeNames();
  EXPECT_EQ(typeNames.size(), 2u);
  EXPECT_EQ(typeNames.count("child"), 1u);
  EXPECT_EQ(typeNames.count("element"), 1u);

  // test HasUniqueChildNames
  EXPECT_TRUE(parent->HasUniqueChildNames("empty"));
  EXPECT_TRUE(parent->HasUniqueChildNames("child"));
  EXPECT_TRUE(parent->HasUniqueChildNames("element"));
  // The following have matching names that are detected when passing
  // default "" to HasUniqueChildNames().
  // <child name="child2"/>
  // <element name="child2"/>
  EXPECT_FALSE(parent->HasUniqueChildNames());
  EXPECT_FALSE(parent->HasUniqueChildNames(""));

  EXPECT_TRUE(parent->CountNamedElements("empty").empty());

  auto childMap = parent->CountNamedElements("child");
  EXPECT_FALSE(childMap.empty());
  EXPECT_EQ(childMap.size(), 2u);
  EXPECT_EQ(childMap.count("child1"), 1u);
  EXPECT_EQ(childMap.count("child2"), 1u);
  EXPECT_EQ(childMap.at("child1"), 1u);
  EXPECT_EQ(childMap.at("child2"), 1u);

  auto elementMap = parent->CountNamedElements("element");
  EXPECT_FALSE(elementMap.empty());
  EXPECT_EQ(elementMap.size(), 2u);
  EXPECT_EQ(elementMap.count("child2"), 1u);
  EXPECT_EQ(elementMap.count("child3"), 1u);
  EXPECT_EQ(elementMap.at("child2"), 1u);
  EXPECT_EQ(elementMap.at("child3"), 1u);

  auto allMap = parent->CountNamedElements("");
  EXPECT_FALSE(allMap.empty());
  EXPECT_EQ(allMap.size(), 3u);
  EXPECT_EQ(allMap.count("child1"), 1u);
  EXPECT_EQ(allMap.count("child2"), 1u);
  EXPECT_EQ(allMap.count("child3"), 1u);
  EXPECT_EQ(allMap.count("unset"), 0u);
  EXPECT_EQ(allMap.at("child1"), 1u);
  EXPECT_EQ(allMap.at("child2"), 2u);
  EXPECT_EQ(allMap.at("child3"), 1u);
}

TEST(Element, FindElement)
{
  // <root>
  //   <elem_A>
  //     <child_elem_A />
  //   </elem_A>
  //
  //   <elem_B>
  //     <child_elem_B name="first_child"/>
  //     <child_elem_B/>
  //   </elem_B>
  // </root>
  sdf::ElementPtr root = std::make_shared<sdf::Element>();
  root->SetName("root");

  // Create elements
  {
    auto elemA = addChildElement(root, "elem_A", false, "");
    addChildElement(elemA, "child_elem_A", false, "");

    auto elemB = addChildElement(root, "elem_B", false, "");
    auto firstChildElemB =
        addChildElement(elemB, "child_elem_B", true, "first_child");

    addChildElement(elemB, "child_elem_B", false, "");
  }

  {
    auto elemA = root->FindElement("elem_A");
    ASSERT_NE(nullptr, elemA);
    EXPECT_NE(nullptr, elemA->FindElement("child_elem_A"));
    EXPECT_EQ(nullptr, elemA->FindElement("non_existent_elem"));

    auto elemB = root->FindElement("elem_B");
    ASSERT_NE(nullptr, elemB);
    // This should find the first "child_elem_B" element, which has the name
    // attribute
    auto childElemB = elemB->FindElement("child_elem_B");
    ASSERT_TRUE(childElemB->HasAttribute("name"));
    EXPECT_EQ("first_child", childElemB->GetAttribute("name")->GetAsString());
  }

  // Check that it works with const pointers
  {
    sdf::ElementConstPtr rootConst = root;
    sdf::ElementConstPtr elemA = rootConst->FindElement("elem_A");
    ASSERT_NE(nullptr, elemA);
    EXPECT_NE(nullptr, elemA->FindElement("child_elem_A"));
    EXPECT_EQ(nullptr, elemA->FindElement("non_existent_elem"));

    sdf::ElementConstPtr elemB = root->FindElement("elem_B");
    ASSERT_NE(nullptr, elemB);
    // This should find the first "child_elem_B" element, which has the name
    // attribute
    sdf::ElementConstPtr childElemB = elemB->FindElement("child_elem_B");
    ASSERT_TRUE(childElemB->HasAttribute("name"));
    EXPECT_EQ("first_child", childElemB->GetAttribute("name")->GetAsString());
  }
  {
    sdf::ElementConstPtr rootConst = root;
    sdf::ElementConstPtr elemA = rootConst->FindElement("elem_A");
    ASSERT_NE(nullptr, elemA);
    EXPECT_NE(nullptr, elemA->FindElement("child_elem_A"));
    EXPECT_EQ(nullptr, elemA->FindElement("non_existent_elem"));

    sdf::ElementConstPtr elemB = root->FindElement("elem_B");
    ASSERT_NE(nullptr, elemB);
    // This should find the first "child_elem_B" element, which has the name
    // attribute
    sdf::ElementConstPtr childElemB = elemB->FindElement("child_elem_B");
    ASSERT_TRUE(childElemB->HasAttribute("name"));
    EXPECT_EQ("first_child", childElemB->GetAttribute("name")->GetAsString());
  }
}

/////////////////////////////////////////////////
/// Main
int main(int argc, char **argv)
{
  ::testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}
