local gumbo = require "gumbo"
local Document = require "gumbo.dom.Document"
local Text = require "gumbo.dom.Text"
local Comment = require "gumbo.dom.Comment"
local assert, pcall = assert, pcall
local _ENV = nil

local input = [[
<div id="main" class="foo bar baz etc">
    <h1 id="heading">Title <!--comment --></h1>
</div>
<section id="section">
<h2 id="heading2">Heading 2</h2>
<p id="p1">p1</p><p id="p2">p2</p><p id="p3">p3</p><p id="p4">p4</p>
</section>
]]

local document = assert(gumbo.parse(input))
local html = assert(document.documentElement)
local head = assert(document.head)
local body = assert(document.body)

local main = assert(document:getElementById("main"))
local heading = assert(document:getElementById("heading"))
local text = assert(heading.childNodes[1])
local comment = assert(heading.childNodes[2])

local section = assert(document:getElementById("section"))
local heading2 = assert(document:getElementById("heading2"))
local p1 = assert(document:getElementById("p1"))
local p2 = assert(document:getElementById("p2"))
local p3 = assert(document:getElementById("p3"))
local p4 = assert(document:getElementById("p4"))

assert(document.nodeName == "#document")
assert(#document.childNodes == 1)
assert(#document.children == 1)
assert(document.children[1] == html)
assert(document.childNodes[1] == html)
assert(document.firstChild == body.parentNode)
assert(document.lastChild == body.parentNode)
assert(document.contentType == "text/html")
assert(document.characterSet == "UTF-8")
assert(document.URL == "about:blank")
assert(document.documentURI == document.URL)
assert(document.compatMode == "BackCompat")
assert(document.parentNode == nil)
assert(document.parentElement == nil)
assert(document.ownerDocument == nil)
document.nodeName = "this-is-readonly"
assert(document.nodeName == "#document")

assert(document:createElement("p").localName == "p")
assert(pcall(document.createElement, document, "Inv@lidName") == false)
assert(document:createTextNode("xyz..").data == "xyz..")
assert(document:createComment(" etc ").data == " etc ")
assert(document:createComment("comment "):isEqualNode(comment) == true)
assert(document:createComment("........"):isEqualNode(comment) == false)
assert(document:createTextNode("Title "):isEqualNode(text) == true)
assert(document:createTextNode("......"):isEqualNode(text) == false)

-- Should all throw InvalidCharacterError
assert(not pcall(document.createElement, document, "xy\tz"))
assert(not pcall(document.createElement, document, "xy\0z"))
assert(not pcall(document.createElement, document, ".xyz"))
assert(not pcall(document.createElement, document, "-xyz"))
assert(not pcall(document.createElement, document, "123"))
assert(not pcall(document.createElement, document, "\205\190"))

assert(Document().nodeName == "#document")
assert(Text("xyz..").data == "xyz..")
assert(Comment(" etc ").data == " etc ")
assert(Comment("comment "):isEqualNode(comment) == true)
assert(Comment("........"):isEqualNode(comment) == false)
assert(Text("Title "):isEqualNode(text) == true)
assert(Text("......"):isEqualNode(text) == false)
assert(Text().data == "")
assert(Comment().data == "")
assert(not pcall(Text, false))
assert(not pcall(Comment, false))
assert(not pcall(Text, {}))
assert(not pcall(Comment, {}))
assert(not pcall(Text, 100))
assert(not pcall(Comment, 100))

local newelem = assert(document:createElement("div"))
assert(newelem.localName == "div")
assert(newelem.namespaceURI == "http://www.w3.org/1999/xhtml")
assert(newelem.attributes.length == 0)
newelem:setAttribute("test", "...")
assert(newelem.attributes.length == 1)
newelem:setAttribute("test", "---")
assert(newelem.attributes.length == 1)
newelem:setAttribute("xyz", "+++")
assert(newelem.attributes.length == 2)
assert(newelem:getAttribute("test") == "---")
assert(newelem:getAttribute("xyz") == "+++")
assert(newelem:getAttribute("xyz") == newelem.attributes[2].value)
assert(newelem.ownerDocument == document)
assert(newelem.parentNode == nil)

local newdoc = assert(gumbo.parse(""))
newdoc.body:appendChild(newelem)
assert(newelem.ownerDocument == newdoc)
assert(newelem.parentNode == newdoc.body)

assert(p1.nextSibling == p2)
assert(p2.nextSibling == p3)
assert(p3.nextSibling == p4)
assert(p4.nextSibling.data == "\n")
assert(p4.nextSibling.nextSibling == nil)
assert(p4.previousSibling == p3)
assert(p3.previousSibling == p2)
assert(p2.previousSibling == p1)
assert(p1.previousSibling.previousSibling == heading2)
assert(heading2.previousSibling.data == "\n")
assert(heading2.previousSibling.previousSibling == nil)
assert(document.nextSibling == nil)
assert(document.previousSibling == nil)
assert(html.previousSibling == nil)
assert(body.previousSibling == head)
assert(body.nextSibling == nil)
assert(head.previousSibling == nil)
assert(head.nextSibling == body)
assert(text.previousSibling == nil)
assert(text.nextSibling == comment)
assert(comment.previousSibling == text)

assert(html.localName == "html")
assert(html.nodeType == document.ELEMENT_NODE)
assert(html.parentNode == document)
assert(html.parentElement == nil)
assert(html.ownerDocument == document)
assert(html.firstElementChild == head)
assert(html.lastElementChild == body)
assert(html.childElementCount == 2)
assert(html.innerHTML == "<head></head><body>"..input.."</body>")
assert(html.outerHTML == "<html><head></head><body>"..input.."</body></html>")

assert(document:contains(html) == true)
assert(document:contains(comment) == true)
assert(html:contains(html) == true)
assert(html:contains(head) == true)
assert(html:contains(body) == true)
assert(html:contains(main) == true)
assert(html:contains(heading) == true)
assert(html:contains(text) == true)
assert(text:contains(text) == true)
assert(comment:contains(comment) == true)
assert(text:contains(heading) == false)
assert(comment:contains(main) == false)
assert(html:contains(document) == false)
assert(html:contains(nil) == false)
assert(heading:contains(main) == false)
assert(body:contains(html) == false)
assert(head:contains(body) == false)

assert(head.childNodes.length == 0)
assert(head.ownerDocument == document)
assert(head.innerHTML == "")
assert(head.outerHTML == "<head></head>")

assert(body:getElementsByTagName("*").length == 8)
assert(body.childElementCount == 2)
assert(body == html.childNodes[2])
assert(body.nodeName == "BODY")
assert(body.nodeType == document.ELEMENT_NODE)
assert(body.localName == "body")
assert(body.parentElement == html)
assert(body.parentNode == html)
assert(body.parentNode.localName == "html")
assert(body.ownerDocument == document)
assert(body.firstElementChild == main)
assert(body.lastElementChild == section)
assert(body.innerHTML == input)
assert(body.outerHTML == "<body>" .. input .. "</body>")

assert(main.childElementCount == 1)
assert(main:getElementsByTagName("*").length == 1)
assert(main == body.childNodes[1])
assert(main:getElementsByTagName("div").length == 0)
assert(main.nodeName == "DIV")
main.nodeName = "newname"
assert(main.nodeName == "DIV")
assert(main.tagName == "DIV")
main.tagName = "newname"
assert(main.tagName == "DIV")
assert(main:hasAttributes() == true)
assert(main.attributes.length == 2)
assert(main:hasAttribute("class") == true)
assert(main:getAttribute("class") == "foo bar baz etc")
assert(main.id == "main")
assert(main.id == main.attributes.id.value)
assert(main.id == main.attributes.id.textContent)
assert(main.className == "foo bar baz etc")
assert(main.className == main.attributes.class.value)
assert(main.className == main.attributes.class.textContent)
assert(main.classList[1] == "foo")
assert(main.classList[2] == "bar")
assert(main.classList[3] == "baz")
assert(main.classList[4] == "etc")
assert(main.classList.length == 4)
assert(main:hasChildNodes() == true)

local mainclone = assert(main:cloneNode())
assert(mainclone.nodeName == "DIV")
assert(mainclone:getAttribute("class") == "foo bar baz etc")
assert(mainclone.attributes.id.value == "main")
assert(mainclone.attributes[1].value == "main")
assert(mainclone.attributes[1].textContent == "main")
assert(mainclone.attributes.length == 2)
assert(mainclone.classList.length == 4)
assert(mainclone:hasChildNodes() == false)
assert(mainclone:isEqualNode(main) == false)
assert(mainclone:isEqualNode(body) == false)

local deepclone = assert(main:cloneNode(true))
assert(deepclone:hasChildNodes() == true)
assert(deepclone:isEqualNode(main) == true)
assert(deepclone:isEqualNode(mainclone) == false)
assert(deepclone.childNodes[2].localName == "h1")
assert(deepclone.childNodes[2].childNodes.length == 2)
assert(deepclone.childNodes[2].childNodes[1].data == "Title ")
assert(deepclone.childNodes[2].childNodes[2].data == "comment ")
deepclone.childNodes[2].childNodes[1]:remove()
assert(deepclone:isEqualNode(main) == false)
assert(deepclone.childNodes[2].childNodes.length == 1)

assert(section.childElementCount == 5)
assert(section.children.length == 5)
assert(section:getElementsByTagName("*").length == 5)
assert(section.childNodes.length == 8)
assert(section.localName == "section")
assert(section.nodeName == "SECTION")
assert(section.parentNode == body)
assert(section.ownerDocument == document)
assert(section.firstElementChild == heading2)
assert(section.lastElementChild == p4)
assert(section.firstChild.nodeName == "#text")
assert(section.firstChild.data == "\n")
assert(section.lastChild.nodeName == "#text")
assert(section.lastChild.data == "\n")
assert(section:hasAttributes() == true)
assert(section.attributes.length == 1)

assert(text.nodeName == "#text")
assert(text.nodeType == document.TEXT_NODE)
assert(text.data == "Title ")
assert(text.length == #text.data)
assert(text.parentNode == heading)
assert(text.ownerDocument == document)

local textclone = assert(text:cloneNode())
assert(textclone.data == text.data)
assert(textclone.nodeName == "#text")
assert(textclone.parentNode == nil)
assert(textclone:isEqualNode(text) == true)
assert(textclone:isEqualNode(comment) == false)
assert(textclone:isEqualNode(heading) == false)

assert(comment.nodeName == "#comment")
assert(comment.nodeType == document.COMMENT_NODE)
assert(comment.data == "comment ")
assert(comment.length == #comment.data)
assert(comment.parentNode == heading)
assert(comment.parentElement == heading)
assert(comment.ownerDocument == document)

local commentclone = assert(comment:cloneNode())
assert(commentclone.data == comment.data)
assert(commentclone.nodeName == "#comment")
assert(commentclone.parentNode == nil)
assert(commentclone:isEqualNode(comment) == true)
assert(commentclone:isEqualNode(text) == false)
assert(commentclone:isEqualNode(document) == false)

assert(heading.parentNode == main)
assert(heading.ownerDocument == document)
assert(heading.childElementCount == 0)

assert(heading.attributes[1].name == "id")
assert(heading.attributes[1].localName == "id")

assert(heading.attributes.length == 1)
assert(heading.attributes[1].value == "heading")
assert(heading.attributes.id.value == "heading")
heading.id = "new-id"
assert(heading.attributes.length == 1)
assert(heading.attributes[1].value == "new-id")
assert(heading.attributes.id.value == "new-id")

heading:setAttribute("id", "test...")
assert(heading.attributes.length == 1)
assert(heading.attributes[1].value == "test...")
assert(heading.attributes.id.value == "test...")
heading:setAttribute("new_attr", "new_value")
assert(heading.attributes.length == 2)
assert(heading.attributes[2].value == "new_value")
assert(heading.attributes.new_attr.value == "new_value")

heading.className = "x y z"
assert(heading.attributes.length == 3)
assert(heading.className == "x y z")
assert(heading.attributes[3].value == "x y z")
assert(heading.attributes.class.value == "x y z")

heading:removeAttribute("id")
assert(heading.attributes.length == 2)
assert(not heading.id)
assert(heading.attributes[2].value == "x y z")
heading:removeAttribute("new_attr")
assert(heading.attributes.length == 1)
assert(heading.attributes[1].value == "x y z")
heading:removeAttribute("class")
assert(heading.attributes.length == 0)
assert(not heading.className)
assert(not heading.attributes[1])
assert(not heading.attributes.class)
heading:setAttribute("id", "heading")
assert(heading.attributes.length == 1)
assert(heading:getAttribute("id") == "heading")
heading:setAttribute("foo", "bar")
assert(heading.attributes.length == 2)
assert(heading:getAttribute("foo") == "bar")

assert(heading:hasChildNodes() == true)
assert(heading.childNodes.length == 2)
assert(heading.children.length == 0)
assert(heading.firstChild == heading.childNodes[1])
assert(heading.lastChild == heading.childNodes[2])

heading.firstChild = false
heading.lastChild = "bla"
assert(heading.firstChild == heading.childNodes[1])
assert(heading.lastChild == heading.childNodes[2])

assert(comment == heading.childNodes[2])
assert(comment.parentNode == heading)
comment:remove()
assert(comment.parentNode == nil)
assert(heading:hasChildNodes() == true)
assert(heading.childNodes.length == 1)
assert(heading.firstChild == heading.childNodes[1])
assert(heading.lastChild == heading.childNodes[1])

heading.childNodes[1]:remove()
assert(heading:hasChildNodes() == false)
assert(heading.childNodes.length == 0)
assert(heading.firstChild == nil)
assert(heading.lastChild == nil)

assert(head.parentNode == html)
assert(html:removeChild(head) == head)
assert(head.parentNode == nil)
local status, value = pcall(html.removeChild, html, head)
assert(status == false)
assert(value:find("NotFoundError", 1, true))
assert(html.parentNode == document)
assert(document:removeChild(html) == html)
assert(html.parentNode == nil)
