// Copyright (c) 2003-present, Jodd Team (http://jodd.org)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

package jodd.lagarto.dom;

import jodd.csselly.CSSelly;
import jodd.csselly.CssSelector;
import jodd.io.FileUtil;
import org.junit.Before;
import org.junit.Test;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

import static org.junit.Assert.*;

public class NodeSelectorTest {

	protected String testDataRoot;

	@Before
	public void setUp() throws Exception {
		if (testDataRoot != null) {
			return;
		}
		URL data = NodeSelectorTest.class.getResource("test");
		testDataRoot = data.getFile();
	}

	@Test
	public void testTags() throws IOException {
		NodeSelector nodeSelector = createNodeFilter();

		List<Node> nodes = nodeSelector.select("div");
		assertEquals(5, nodes.size());

		nodes = nodeSelector.select("body");
		assertEquals(1, nodes.size());
		assertEquals("body", nodes.get(0).getNodeName());

		nodes = nodeSelector.select("p");
		assertEquals(4, nodes.size());
	}

	@Test
	public void testMoreTags() throws IOException {
		NodeSelector nodeSelector = createNodeFilter();

		List<Node> nodes = nodeSelector.select("div b");
		assertEquals(1, nodes.size());
		assertEquals("b", nodes.get(0).getNodeName());
		assertEquals("p", nodes.get(0).getParentNode().getNodeName());

		nodes = nodeSelector.select("p b");
		assertEquals(4, nodes.size());

		nodes = nodeSelector.select("div div");
		assertEquals(3, nodes.size());

		nodes = nodeSelector.select("div div div");
		assertEquals(2, nodes.size());

		nodes = nodeSelector.select("* div div div");
		assertEquals(2, nodes.size());
	}

	@Test
	public void testIdClass() throws IOException {
		NodeSelector nodeSelector = createNodeFilter();

		List<Node> nodes = nodeSelector.select("div#fiona");
		assertEquals(1, nodes.size());
		assertEquals("fiona", nodes.get(0).getAttribute("id"));

		nodes = nodeSelector.select("div#fiona div#jodd");
		assertEquals(1, nodes.size());
		assertEquals("jodd", nodes.get(0).getAttribute("id"));

		nodes = nodeSelector.select("div.k1");
		assertEquals(1, nodes.size());

		nodes = nodeSelector.select("div.k2");
		assertEquals(2, nodes.size());

		nodes = nodeSelector.select("div.k1.k2");
		assertEquals(1, nodes.size());

		nodes = nodeSelector.select(".k1.k2");
		assertEquals(1, nodes.size());

		nodes = nodeSelector.select("p em");
		assertEquals(5, nodes.size());

		nodes = nodeSelector.select("p * em");
		assertEquals(2, nodes.size());
	}

	@Test
	public void testAttributes() throws IOException {
		NodeSelector nodeSelector = createNodeFilter();

		List<Node> nodes = nodeSelector.select("div[id]");
		assertEquals(2, nodes.size());

		nodes = nodeSelector.select("html body div[id] div#jodd");
		assertEquals(1, nodes.size());

		nodes = nodeSelector.select("div[id*=ion]");
		assertEquals(1, nodes.size());

		nodes = nodeSelector.select("div[id*=o]");
		assertEquals(2, nodes.size());

		nodes = nodeSelector.select("div[id$=odd]");
		assertEquals(1, nodes.size());

		nodes = nodeSelector.select("div[id$=od]");
		assertEquals(0, nodes.size());

		nodes = nodeSelector.select("div[id^=jo]");
		assertEquals(1, nodes.size());

		nodes = nodeSelector.select("div[id^=od]");
		assertEquals(0, nodes.size());

		nodes = nodeSelector.select("[lang|=en]");
		assertEquals(1, nodes.size());
		assertEquals("h1", nodes.get(0).getNodeName());

		nodes = nodeSelector.select("[class~=k1]");
		assertEquals(1, nodes.size());

		nodes = nodeSelector.select("[class~=k2]");
		assertEquals(2, nodes.size());
		nodes = nodeSelector.select("[class~=k2][class~=k1]");
		assertEquals(1, nodes.size());
	}

	@Test
	public void testCombinators() throws IOException {
		NodeSelector nodeSelector = createNodeFilter();

		List<Node> nodes = nodeSelector.select("p#text > span");
		assertEquals(1, nodes.size());
		Node spanNode = nodes.get(0);
		assertEquals("spanner", (spanNode.getChild(0)).getNodeValue());

		nodes = nodeSelector.select("p#text > em");
		assertEquals(3, nodes.size());

		nodes = nodeSelector.select("p#text > em#oleg + em");
		assertEquals(0, nodes.size());
		nodes = nodeSelector.select("p#text > em#oleg + span");
		assertEquals(1, nodes.size());
		assertEquals("spanner", (nodes.get(0).getChild(0)).getNodeValue());

		nodes = nodeSelector.select("p#text > em#oleg ~ em");
		assertEquals(1, nodes.size());
		assertEquals("lina", nodes.get(0).getAttribute(0).getValue());
	}

	@Test
	public void testPseudoClasses() throws IOException {
		NodeSelector nodeSelector = createNodeFilter();

		List<Node> nodes = nodeSelector.select("p#text > em:first-child");
		assertEquals(1, nodes.size());
		assertEquals("ema", nodes.get(0).getAttribute(0).getValue());

		nodes = nodeSelector.select("p#text  em:first-child");
		assertEquals(3, nodes.size());

		nodes = nodeSelector.select("p#text > em:last-child");
		assertEquals(1, nodes.size());

		nodes = nodeSelector.select("p#text em:last-child");
		assertEquals(2, nodes.size());

		nodes = nodeSelector.select("em:only-child");
		assertEquals(1, nodes.size());
		assertEquals("Sanja", (nodes.get(0).getFirstChild()).getNodeValue());

		nodes = nodeSelector.select("em:first-child:last-child");
		assertEquals(1, nodes.size());
		assertEquals("Sanja", (nodes.get(0).getFirstChild()).getNodeValue());

		nodes = nodeSelector.select("b:first-of-type");
		assertEquals(3, nodes.size());

		nodes = nodeSelector.select("p#text b:first-of-type");
		assertEquals(1, nodes.size());

		nodes = nodeSelector.select("p#text b:last-of-type");
		assertEquals(1, nodes.size());

		nodes = nodeSelector.select("p:root");
		assertEquals(0, nodes.size());
		nodes = nodeSelector.select("html:root");
		assertEquals(1, nodes.size());

		nodes = nodeSelector.select(":empty");
		assertEquals(2, nodes.size());

		nodes = nodeSelector.select("b span:only-of-type");
		assertEquals(1, nodes.size());
		assertEquals("framework", (nodes.get(0).getFirstChild()).getNodeValue());

	}

	@Test
	public void testPseudoFunctions() throws IOException {
		NodeSelector nodeSelector = createNodeFilter();

		List<Node> nodes = nodeSelector.select("p#text > em:nth-child(2n+1)");
		assertEquals(2, nodes.size());

		nodes = nodeSelector.select("p#text  em:nth-child(2n+1)");
		assertEquals(4, nodes.size());

		nodes = nodeSelector.select("p#text > em:nth-last-child(2n+1)");
		assertEquals(1, nodes.size());
		assertEquals("lina", (nodes.get(0)).getAttribute("id"));

		nodes = nodeSelector.select("p#text em:nth-last-child(2n+1)");
		assertEquals(2, nodes.size());

		nodes = nodeSelector.select("p#text em:nth-of-type(odd)");
		assertEquals(4, nodes.size());

		nodes = nodeSelector.select("p#text em:nth-of-type(even)");
		assertEquals(1, nodes.size());

		nodes = nodeSelector.select("p#text em:nth-last-of-type(odd)");
		assertEquals(4, nodes.size());

		nodes = nodeSelector.select("p#text em:nth-last-of-type(even)");
		assertEquals(1, nodes.size());

	}

	@Test
	public void testDuplicatesRemoval() throws IOException {
		NodeSelector nodeSelector = createNodeFilter();

		List<Node> nodes = nodeSelector.select("div div");
		assertEquals(3, nodes.size());
	}


	@Test
	public void testNodeSelector() throws IOException {
		NodeSelector nodeSelector = createNodeFilter();

		List<Node> nodes = nodeSelector.select(new NodeFilter() {
			public boolean accept(Node node) {
				if (node.getNodeType() != Node.NodeType.ELEMENT) {
					return false;
				}
				if ("ema".equals(node.getAttribute("id"))) {
					return true;
				}
				if ("lina".equals(node.getAttribute("id"))) {
					return true;
				}
				return false;
			}
		});

		assertEquals(2, nodes.size());
	}

	@Test
	public void testTwoHtml() throws IOException {
		File file = new File(testDataRoot, "two.html");
		String htmlContent = FileUtil.readString(file);

		Document document = new LagartoDOMBuilder().parse(htmlContent);

		Node html = new NodeSelector(document).select("html").get(0);
		assertNotNull(html);

		Node body = new NodeSelector(html).selectFirst("body");
		Element h1 = body.getFirstChildElement();
		assertEquals("h1", h1.getNodeName());

		Node comment1 = body.getFirstChild().getNextSibling();
		assertEquals(Node.NodeType.COMMENT, comment1.getNodeType());


		Element p = (Element) new NodeSelector(body).selectFirst("p");

		assertEquals(h1, p.getPreviousSiblingElement());
		assertEquals(h1, comment1.getNextSiblingElement());
		assertNull(comment1.getNextSiblingName());

		// check if filter works just for sub elements
		List<Node> p_ems = new NodeSelector(p).select("em");
		assertEquals(1, p_ems.size());

		Element script = (Element) new NodeSelector(html).selectFirst("script");
		assertEquals("text/javascript", script.getAttribute("type"));

		assertTrue(document.check());
	}

	@Test
	public void testGroupOfSelectors() throws IOException {
		File file = new File(testDataRoot, "one.html");
		String htmlContent = FileUtil.readString(file);

		Document document = new LagartoDOMBuilder().parse(htmlContent);

		List<Node> nodes = new NodeSelector(document).select("em, b, b");
		assertEquals(9, nodes.size());

		assertTrue(document.check());
	}
	
	@Test
	public void testClassWithTabs() throws IOException {
		File file = new File(testDataRoot, "class-tabs.html");
		String htmlContent = FileUtil.readString(file);

		Document document = new LagartoDOMBuilder().parse(htmlContent);

		List<Node> nodes = new NodeSelector(document).select(".hey");
		assertEquals(1, nodes.size());

		Node n = nodes.get(0);
		assertEquals("div", n.getNodeName());
		assertEquals("jodd", n.getAttribute("id"));
	}

	@Test
	public void testCollectionOfSelectors() throws IOException {
		NodeSelector nodeSelector = createNodeFilter();

		List<CssSelector> selectors1 = new CSSelly("body").parse();
		List<CssSelector> selectors2 = new CSSelly("p").parse();

		List<List<CssSelector>> collection = new ArrayList<>();
		collection.add(selectors1);
		collection.add(selectors2);

		List<Node> nodes = nodeSelector.select(collection);

		assertEquals(5, nodes.size());

		assertEquals("body", nodes.get(0).nodeName);
	}


	// ---------------------------------------------------------------- utils

	private NodeSelector createNodeFilter() throws IOException {
		File file = new File(testDataRoot, "one.html");
		String html = FileUtil.readString(file);
		return new NodeSelector(new LagartoDOMBuilder().parse(html));
	}
}
