#include <library/cpp/testing/unittest/registar.h>
#include <util/generic/map.h>

#include "xml-document.h"

Y_UNIT_TEST_SUITE(TestXmlDocument) {
    Y_UNIT_TEST(Iteration) {
        NXml::TDocument xml(
            "<?xml version=\"1.0\"?>\n"
            "<root>qq<a><b></b></a>ww<c></c></root>",
            NXml::TDocument::String);

        NXml::TConstNode root = xml.Root();
        UNIT_ASSERT_EQUAL(root.Name(), "root");
        NXml::TConstNode n = root.FirstChild().NextSibling();
        UNIT_ASSERT_EQUAL(n.Name(), "a");
        n = n.NextSibling().NextSibling();
        UNIT_ASSERT_EQUAL(n.Name(), "c");
    }

    Y_UNIT_TEST(ParseString) {
        NXml::TDocument xml(
            "<?xml version=\"1.0\"?>\n"
            "<root>\n"
            "<a><b len=\"15\" correct=\"1\">hello world</b></a>\n"
            "<text>Некоторый текст</text>\n"
            "</root>",
            NXml::TDocument::String);

        NXml::TConstNode root = xml.Root();
        NXml::TConstNode b = root.Node("a/b");
        UNIT_ASSERT_EQUAL(b.Attr<int>("len"), 15);
        UNIT_ASSERT_EQUAL(b.Attr<bool>("correct"), true);

        NXml::TConstNode text = root.Node("text");
        UNIT_ASSERT_EQUAL(text.Value<TString>(), "Некоторый текст");
    }
    Y_UNIT_TEST(SerializeString) {
        NXml::TDocument xml("frob", NXml::TDocument::RootName);
        xml.Root().SetAttr("xyzzy", "Frobozz");
        xml.Root().SetAttr("kulness", 0.3);
        xml.Root().SetAttr("timelimit", 3);

        NXml::TNode authors = xml.Root().AddChild("authors");
        authors.AddChild("graham").SetAttr("name", "Nelson");
        authors.AddChild("zarf").SetValue("Andrew Plotkin");
        authors.AddChild("emshort", "Emily Short");

        TString data = xml.ToString("utf-8");
        UNIT_ASSERT_EQUAL(data, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
                                "<frob xyzzy=\"Frobozz\" kulness=\"0.3\" timelimit=\"3\">\n"
                                "  <authors>\n"
                                "    <graham name=\"Nelson\"/>\n"
                                "    <zarf>Andrew Plotkin</zarf>\n"
                                "    <emshort>Emily Short</emshort>\n"
                                "  </authors>\n"
                                "</frob>\n");
        // check default utf8 output with ru
        {
            NXml::TDocument xml2("frob", NXml::TDocument::RootName);
            xml2.Root().SetAttr("xyzzy", "привет =)");
            UNIT_ASSERT_VALUES_EQUAL(xml2.ToString(), "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
                                                      "<frob xyzzy=\"привет =)\"/>\n");
        }
    }
    Y_UNIT_TEST(XPathNs) {
        using namespace NXml;
        TDocument xml(
            "<?xml version=\"1.0\"?>\n"
            "<root xmlns='http://hello.com/hello'>\n"
            "<a><b len=\"15\" correct=\"1\">hello world</b></a>\n"
            "<text>Некоторый текст</text>\n"
            "</root>",
            TDocument::String);

        TNamespacesForXPath nss;
        TNamespaceForXPath ns = {"h", "http://hello.com/hello"};
        nss.push_back(ns);

        TConstNode root = xml.Root();
        TConstNode b = root.Node("h:a/h:b", false, nss);
        UNIT_ASSERT_EQUAL(b.Attr<int>("len"), 15);
        UNIT_ASSERT_EQUAL(b.Attr<bool>("correct"), true);

        TConstNode text = root.Node("h:text", false, nss);
        UNIT_ASSERT_EQUAL(text.Value<TString>(), "Некоторый текст");

        // For performance you can create xpath context once using nss and pass it.
        TXPathContextPtr ctxt = root.CreateXPathContext(nss);
        UNIT_ASSERT(root.Node("text", true, *ctxt).IsNull());
        UNIT_ASSERT_EXCEPTION(root.Node("text", false, *ctxt), yexception);
        UNIT_ASSERT_EQUAL(root.Node("h:text", false, *ctxt).Value<TString>(), "Некоторый текст");
    }
    Y_UNIT_TEST(XmlNodes) {
        using namespace NXml;
        TDocument xml("<?xml version=\"1.0\"?>\n"
                      "<root>qq<a><b>asdfg</b></a>ww<c></c></root>",
                      NXml::TDocument::String);
        TNode root = xml.Root();
        UNIT_ASSERT_EQUAL(root.Value<TString>(), "qqasdfgww");
        TConstNode node = root.FirstChild();
        UNIT_ASSERT_EQUAL(node.IsText(), true);
        UNIT_ASSERT_EQUAL(node.Value<TString>(), "qq");
        node = node.NextSibling();
        UNIT_ASSERT_EQUAL(node.IsText(), false);
        UNIT_ASSERT_EQUAL(node.Name(), "a");
        UNIT_ASSERT_EQUAL(node.Value<TString>(), "asdfg");
        node = node.NextSibling();
        UNIT_ASSERT_EQUAL(node.IsText(), true);
        UNIT_ASSERT_EQUAL(node.Value<TString>(), "ww");
        node = node.NextSibling();
        UNIT_ASSERT_EQUAL(node.IsText(), false);
        UNIT_ASSERT_EQUAL(node.Name(), "c");
        UNIT_ASSERT_EQUAL(node.Value<TString>(), "");
        node = node.NextSibling();
        UNIT_ASSERT_EQUAL(node.IsNull(), true);
        TStringStream iterLog;
        for (const auto& node2 : root.Nodes("/root/*")) {
            iterLog << node2.Name() << ';';
        }
        UNIT_ASSERT_STRINGS_EQUAL(iterLog.Str(), "a;c;");

        // get only element nodes, ignore text nodes with empty "name" param
        node = root.FirstChild(TString());
        UNIT_ASSERT_EQUAL(node.IsText(), false);
        UNIT_ASSERT_EQUAL(node.Name(), "a");
        node = node.NextSibling(TString());
        UNIT_ASSERT_EQUAL(node.IsText(), false);
        UNIT_ASSERT_EQUAL(node.Name(), "c");

        // use exact "name" to retrieve children and siblings
        node = root.FirstChild("a");
        UNIT_ASSERT_EQUAL(node.IsNull(), false);
        UNIT_ASSERT_EQUAL(node.Name(), "a");
        node = node.NextSibling("c");
        UNIT_ASSERT_EQUAL(node.IsNull(), false);
        UNIT_ASSERT_EQUAL(node.Name(), "c");
        node = root.FirstChild("c"); // skip "a"
        UNIT_ASSERT_EQUAL(node.IsNull(), false);
        UNIT_ASSERT_EQUAL(node.Name(), "c");

        // node not found: no exceptions, null nodes are returned
        node = root.FirstChild("b"); // b is not direct child of root
        UNIT_ASSERT_EQUAL(node.IsNull(), true);
        node = root.FirstChild("nosuchnode");
        UNIT_ASSERT_EQUAL(node.IsNull(), true);
        node = root.FirstChild();
        node = root.NextSibling("unknownnode");
        UNIT_ASSERT_EQUAL(node.IsNull(), true);
        UNIT_ASSERT_EXCEPTION(node.Name(), yexception);
        UNIT_ASSERT_EXCEPTION(node.Value<TString>(), yexception);
        UNIT_ASSERT_EXCEPTION(node.IsText(), yexception);
    }
    Y_UNIT_TEST(DefVal) {
        using namespace NXml;
        TDocument xml("<?xml version=\"1.0\"?>\n"
                      "<root><a></a></root>",
                      NXml::TDocument::String);
        UNIT_ASSERT_EQUAL(xml.Root().Node("a", true).Node("b", true).Value<int>(3), 3);
    }
    Y_UNIT_TEST(NodesVsXPath) {
        using namespace NXml;
        TDocument xml("<?xml version=\"1.0\"?>\n"
                      "<root><a x=\"y\"></a></root>",
                      NXml::TDocument::String);
        UNIT_ASSERT_EXCEPTION(xml.Root().Nodes("/root/a/@x"), yexception);
        UNIT_ASSERT_VALUES_EQUAL(xml.Root().XPath("/root/a/@x").Size(), 1);
    }
    Y_UNIT_TEST(NodeIsFirst) {
        using namespace NXml;
        TDocument xml("<?xml version=\"1.0\"?>\n"
                      "<root><a x=\"y\">first</a>"
                      "<a>second</a></root>",
                      NXml::TDocument::String);
        UNIT_ASSERT_EXCEPTION(xml.Root().Node("/root/a/@x"), yexception);
        UNIT_ASSERT_STRINGS_EQUAL(xml.Root().Node("/root/a").Value<TString>(), "first");
    }
    Y_UNIT_TEST(CopyNode) {
        using namespace NXml;
        // default-construct empty node
        TNode empty;
        // put to container
        TMap<int, TNode> nmap;
        nmap[2];

        // do copy
        TDocument xml("<?xml version=\"1.0\"?>\n"
                      "<root><a></a></root>",
                      TDocument::String);

        TDocument xml2("<?xml version=\"1.0\"?>\n"
                       "<root><node><b>bold</b><i>ita</i></node></root>",
                       TDocument::String);

        TNode node = xml2.Root().Node("//node");
        TNode place = xml.Root().Node("//a");

        place.AddChild(node);

        TStringStream s;
        xml.Save(s, "", false);
        UNIT_ASSERT_VALUES_EQUAL(s.Str(),
                                 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
                                 "<root><a><node><b>bold</b><i>ita</i></node></a></root>\n");
    }

    Y_UNIT_TEST(RenderNode) {
        using namespace NXml;
        {
            // no namespaces
            TDocument xml(
                "<?xml version=\"1.0\"?>\n"
                "<root>\n"
                "<a><b len=\"15\" correct=\"1\">hello world</b></a>\n"
                "<text>Некоторый текст</text>\n"
                "</root>",
                TDocument::String);
            TNode n = xml.Root().Node("//a");
            UNIT_ASSERT_VALUES_EQUAL(n.ToString(), "<a><b len=\"15\" correct=\"1\">hello world</b></a>");
        }
        {
            // namespaces
            TDocument xml(
                "<?xml version=\"1.0\"?>\n"
                "<root xmlns='http://hello.com/hello'>\n"
                "<a><b len=\"15\" correct=\"1\">hello world</b></a>\n"
                "<text>Некоторый текст</text>\n"
                "</root>",
                TDocument::String);
            TNamespacesForXPath nss;
            TNamespaceForXPath ns = {"h", "http://hello.com/hello"};
            nss.push_back(ns);

            TNode n = xml.Root().Node("//h:a", false, nss);
            UNIT_ASSERT_VALUES_EQUAL(n.ToString(), "<a><b len=\"15\" correct=\"1\">hello world</b></a>");
        }
    }

    Y_UNIT_TEST(ReuseXPathContext) {
        using namespace NXml;

        TDocument xml(
            "<?xml version=\"1.0\"?>\n"
            "<root>\n"
            "<a><b><c>Hello, world!</c></b></a>\n"
            "<text x=\"10\">First</text>\n"
            "<text y=\"20\">Second</text>\n"
            "</root>",
            TDocument::String);

        TXPathContextPtr rootCtxt = xml.Root().CreateXPathContext();

        // Check Node()
        TConstNode b = xml.Root().Node("a/b", false, *rootCtxt);

        // We can use root node context for xpath evaluation in any node
        TConstNode c1 = b.Node("c", false, *rootCtxt);
        UNIT_ASSERT_EQUAL(c1.Value<TString>(), "Hello, world!");

        TXPathContextPtr bCtxt = b.CreateXPathContext();
        TConstNode c2 = b.Node("c", false, *bCtxt);
        UNIT_ASSERT_EQUAL(c2.Value<TString>(), "Hello, world!");

        // Mixing contexts from different documents is forbidden
        TDocument otherXml("<root></root>", TDocument::String);
        TXPathContextPtr otherCtxt = otherXml.Root().CreateXPathContext();
        UNIT_ASSERT_EXCEPTION(b.Node("c", false, *otherCtxt), yexception);

        // Check Nodes()
        TConstNodes texts = xml.Root().Nodes("text", true, *rootCtxt);
        UNIT_ASSERT_EQUAL(texts.Size(), 2);

        // Nodes() does't work for non-element nodes
        UNIT_ASSERT_EXCEPTION(xml.Root().Nodes("text/@x", true, *rootCtxt), yexception);

        // Check XPath()
        TConstNodes ys = xml.Root().XPath("text/@y", true, *rootCtxt);
        UNIT_ASSERT_EQUAL(ys.Size(), 1);
        UNIT_ASSERT_EQUAL(ys[0].Value<int>(), 20);
    }

    Y_UNIT_TEST(Html) {
        using namespace NXml;

        TDocument htmlChunk("video", TDocument::RootName);
        TNode videoNode = htmlChunk.Root();

        videoNode.SetAttr("controls");

        TStringStream ss;
        videoNode.SaveAsHtml(ss);
        UNIT_ASSERT_EQUAL(ss.Str(), "<video controls></video>");
    }

    Y_UNIT_TEST(Move) {
        using namespace NXml;

        TDocument xml1("foo", TDocument::RootName);
        xml1.Root().AddChild("bar");

        UNIT_ASSERT_VALUES_EQUAL(xml1.Root().ToString(), "<foo><bar/></foo>");

        TDocument xml2 = std::move(xml1);
        UNIT_ASSERT_EXCEPTION(xml1.Root(), yexception);
        UNIT_ASSERT_VALUES_EQUAL(xml2.Root().ToString(), "<foo><bar/></foo>");
    }

    Y_UNIT_TEST(StringConversion) {
        using namespace NXml;
        TDocument xml("foo", TDocument::RootName);
        auto root = xml.Root();
        const TStringBuf stringBuf = "bar";
        root.SetAttr("bar", stringBuf);
        const TString tString = "baz";
        root.SetAttr("baz", tString);
        root.SetAttr("quux", "literal");
        root.SetAttr("frob", 500);
    }
}