/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.
 */
package org.apache.wicket.markup.parser;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.io.UnsupportedEncodingException;
import java.text.ParseException;

import org.apache.wicket.markup.parser.IXmlPullParser.HttpTagType;
import org.apache.wicket.util.resource.StringResourceStream;
import org.junit.jupiter.api.Test;

/**
 * Quite some tests are already with MarkupParser.
 *
 * @author Juergen Donnerstag
 */
class XmlPullParserTest
{
	/**
	 *
	 * @throws Exception
	 */
	@Test
	final void basics() throws Exception
	{
		final XmlPullParser parser = new XmlPullParser();
		parser.parse("This is a text");
		XmlTag elem = parser.nextTag();
		assertNull(elem);

		parser.parse("<tag/>");
		XmlTag tag = parser.nextTag();
		assertFalse(tag.isOpen());
		// assertFalse(tag.isOpen("tag"));
		// assertFalse(tag.isOpen("xxx"));
		assertFalse(tag.isClose());
		assertTrue(tag.isOpenClose());
		// assertTrue(tag.isOpenClose("tag"));
		assertEquals("tag", tag.getName());
		assertNull(tag.getNamespace());
		assertEquals(0, tag.getAttributes().size());

		// extra spaces
		parser.parse("<tag ></tag >");
		tag = parser.nextTag();
		assertTrue(tag.isOpen());
		// assertTrue(tag.isOpen("tag"));
		// assertFalse(tag.isOpen("xxx"));
		assertFalse(tag.isClose());
		assertFalse(tag.isOpenClose());
		// assertFalse(tag.isOpenClose("tag"));
		assertEquals("tag", tag.getName());
		assertNull(tag.getNamespace());
		assertEquals(0, tag.getAttributes().size());

		tag = parser.nextTag();
		assertFalse(tag.isOpen());
		// assertFalse(tag.isOpen("tag"));
		// assertFalse(tag.isOpen("xxx"));
		assertTrue(tag.isClose());
		assertFalse(tag.isOpenClose());
		// assertFalse(tag.isOpenClose("tag"));
		assertEquals("tag", tag.getName());
		assertNull(tag.getNamespace());
		assertEquals(0, tag.getAttributes().size());

		parser.parse("<tag>  </tag>");
		tag = parser.nextTag();
		// assertTrue(tag.isOpen("tag"));
		tag = parser.nextTag();
		assertTrue(tag.isClose());

		parser.parse("xx <tag> yy </tag> zz");
		tag = parser.nextTag();
		// assertTrue(tag.isOpen("tag"));
		tag = parser.nextTag();
		assertTrue(tag.isClose());

		// XmlPullParser does NOT check that tags get properly closed
		parser.parse("<tag>");
		tag = parser.nextTag();
		tag = parser.nextTag();
		assertNull(elem);

		parser.parse("<tag> <tag> <tag>");
		tag = parser.nextTag();
		// assertTrue(tag.isOpen("tag"));
		tag = parser.nextTag();
// assertTrue(tag.isOpen("tag"));
		tag = parser.nextTag();
// assertTrue(tag.isOpen("tag"));

		parser.parse("<ns:tag/>");
		tag = parser.nextTag();
// assertTrue(tag.isOpenClose("tag"));
		assertEquals("ns", tag.getNamespace());
		assertEquals("tag", tag.getName());

		parser.parse("<ns:tag></ns:tag>");
		tag = parser.nextTag();
// assertTrue(tag.isOpen("tag"));
		assertEquals("ns", tag.getNamespace());
		assertEquals("tag", tag.getName());

		XmlTag closeTag = parser.nextTag();
		assertTrue(closeTag.isClose());
		assertEquals("ns", closeTag.getNamespace());
		assertEquals("tag", closeTag.getName());
	}

	/**
	 *
	 * @throws Exception
	 */
	@Test
	final void encoding() throws Exception
	{
		final XmlPullParser parser = new XmlPullParser();
		parser.parse(
			new StringResourceStream("<?xml version=\"1.0\" encoding=\"iso-8859-1\" ?>").getInputStream(),
			null);
		assertEquals("iso-8859-1", parser.getEncoding());
		XmlTag tag = parser.nextTag();
		assertNull(tag);

		parser.parse(new StringResourceStream(
			"<?xml version=\"1.0\" encoding='iso-8859-1' ?> test test").getInputStream(), null);
		assertEquals("iso-8859-1", parser.getEncoding());
		tag = parser.nextTag();
		assertNull(tag);

		// re-order and move close (remove whitespaces
		parser.parse(new StringResourceStream(
			"   <?xml encoding='iso-8859-1'version=\"1.0\"?> test test").getInputStream(), null);
		assertEquals("iso-8859-1", parser.getEncoding());
		tag = parser.nextTag();
		assertNull(tag);

		// attribute value must be enclosed by ""
		parser.parse(
			new StringResourceStream("<?xml encoding=iso-8859-1 ?> test test").getInputStream(),
			null);
		assertEquals("iso-8859-1", parser.getEncoding());

		// Invaluid encoding
		Exception ex = null;
		try
		{
			parser.parse(new StringResourceStream("<?xml encoding='XXX' ?>").getInputStream(), null);
		}
		catch (UnsupportedEncodingException e)
		{
			ex = e;
		}
		assertNotNull(ex);

		// no extra characters allowed before <?xml>
		// TODO General: I'd certainly prefer an exception
		parser.parse(
			new StringResourceStream("xxxx <?xml encoding='iso-8859-1' ?>").getInputStream(), null);
		assertNull(parser.getEncoding());
		tag = parser.nextTag();
		assertNull(tag);

		// no extra characters allowed before <?xml>
		// Are comments allowed preceding the encoding string?
		parser.parse(
			new StringResourceStream("<!-- Comment --> <?xml encoding='iso-8859-1' ?>").getInputStream(),
			null);
		assertNull(parser.getEncoding());
		tag = parser.nextTag();
		assertNull(tag);

		// 'test' is not a valid attribut. But we currently don't test it.
		parser.parse(new StringResourceStream("<?xml test='123' >").getInputStream(), null);
		assertNull(parser.getEncoding());
		tag = parser.nextTag();
		assertNull(tag);
	}

	/**
	 * WICKET-5398 parsing from String
	 */
	@Test
	void encodingOfString() throws Exception
	{
		// use an encoding that is not the system's file encoding
		final String encoding;
		if ("UTF-8".equals(System.getProperty("file.encoding")))
		{
			encoding = "ISO-8859-1";
		}
		else
		{
			encoding = "UTF-8";
		}

		final XmlPullParser parser = new XmlPullParser();
		parser.parse(String.format("<?xml encoding='%s' ?><span id='umlaut-äöü'></span>", encoding));
		assertEquals("umlaut-äöü", parser.nextTag().getAttribute("id"));
	}

	/**
	 *
	 * @throws Exception
	 */
	@Test
	final void attributes() throws Exception
	{
		final XmlPullParser parser = new XmlPullParser();
		parser.parse("<tag>");
		XmlTag tag = parser.nextTag();
		assertEquals(0, tag.getAttributes().size());
		// assertTrue(tag.isOpen("tag"));
		assertFalse(tag.getAttributes().containsKey("attr"));
		assertNull(tag.getAttributes().getString("attr"));

		parser.parse("<tag attr='1234'>");
		tag = parser.nextTag();
		assertEquals(1, tag.getAttributes().size());
		assertTrue(tag.getAttributes().containsKey("attr"));
		assertEquals("1234", tag.getAttributes().getString("attr"));

		parser.parse("<tag attr=1234>");
		tag = parser.nextTag();
		assertEquals(1, tag.getAttributes().size());
		assertTrue(tag.getAttributes().containsKey("attr"));
		assertEquals("1234", tag.getAttributes().getString("attr"));

		parser.parse("<tag attr=1234 >");
		tag = parser.nextTag();
		assertEquals(1, tag.getAttributes().size());
		assertTrue(tag.getAttributes().containsKey("attr"));
		assertEquals("1234", tag.getAttributes().getString("attr"));

		parser.parse("<tag attr-withHypen=1234 >");
		tag = parser.nextTag();
		assertEquals(1, tag.getAttributes().size());
		assertTrue(tag.getAttributes().containsKey("attr-withHypen"));
		assertEquals("1234", tag.getAttributes().getString("attr-withHypen"));

		parser.parse("<tag attr=\"1234\">");
		tag = parser.nextTag();
		assertEquals(1, tag.getAttributes().size());
		assertTrue(tag.getAttributes().containsKey("attr"));
		assertEquals("1234", tag.getAttributes().getString("attr"));

		parser.parse("<tag attr='1234' test='23'>");
		tag = parser.nextTag();
		assertEquals(2, tag.getAttributes().size());
		assertTrue(tag.getAttributes().containsKey("attr"));
		assertEquals("1234", tag.getAttributes().getString("attr"));
		assertTrue(tag.getAttributes().containsKey("test"));
		assertEquals("23", tag.getAttributes().getString("test"));

		parser.parse("<tag attr='1234' attr='23'>");
		Exception ex = null;
		try
		{
			tag = parser.nextTag();
		}
		catch (ParseException e)
		{
			ex = e;
		}
		assertNotNull(ex);
	}

	/**
	 *
	 * @throws Exception
	 */
	@Test
	final void comments() throws Exception
	{
		final XmlPullParser parser = new XmlPullParser();
		parser.parse("<!-- test --><tag>");
		XmlTag tag = parser.nextTag();
// assertTrue(tag.isOpen("tag"));

		parser.parse("<!-- test --><tag> aaa <!-- test 1 --> bbb <tag> <!-- test --> </tag>");
		tag = parser.nextTag();
// assertTrue(tag.isOpen("tag"));
		tag = parser.nextTag();
// assertTrue(tag.isOpen("tag"));
		tag = parser.nextTag();
		assertTrue(tag.isClose());
		tag = parser.nextTag();
		assertNull(tag);

		// As you can see, XmlPullParser is really a shallow parser only
		parser.parse("<!-- test --><tag> aaa <?tag test 1 ?> bbb <tag> <!DOCTYPE test > </tag>");
		tag = parser.nextTag();
// assertTrue(tag.isOpen("tag"));
		tag = parser.nextTag();
// assertTrue(tag.isOpen("tag"));
		tag = parser.nextTag();
		assertTrue(tag.isClose());
		tag = parser.nextTag();
		assertNull(tag);
	}

	/**
	 *
	 * @throws Exception
	 */
	@Test
	final void compressWhitespace() throws Exception
	{
		final XmlPullParser parser = new XmlPullParser();
		parser.parse("<?xml version=\"1.0\" encoding=\"iso-8859-1\" ?>");
	}

	/**
	 *
	 * @throws Exception
	 */
	@Test
	final void script() throws Exception
	{
		final XmlPullParser parser = new XmlPullParser();
		parser.parse("<html><script language=\"JavaScript\">... <x a> ...</script></html>");
		XmlTag tag = parser.nextTag();
		assertTrue(tag.isOpen());
		assertEquals("html", tag.getName());
		tag = parser.nextTag();
		assertTrue(tag.isOpen());
		assertEquals("script", tag.getName());
		tag = parser.nextTag();
		assertTrue(tag.isClose());
		assertEquals("script", tag.getName());
		tag = parser.nextTag();
		assertTrue(tag.isClose());
		assertEquals("html", tag.getName());
	}

	@Test
    public void skipScriptTag() throws Exception
	{
	    final XmlPullParser parser = new XmlPullParser();
	    //ensure that script tag content is skiped if we have 'module' as type value
        parser.parse("<html><script type=\"module\">all I need is a < char to break parser </script><body></body></html>");
        XmlTag tag = parser.nextTag();
        assertTrue(tag.isOpen());
        assertEquals("html", tag.getName());
        tag = parser.nextTag();
        assertTrue(tag.isOpen());
        assertEquals("script", tag.getName());
        tag = parser.nextTag();
        assertTrue(tag.isClose());
        assertEquals("script", tag.getName());
        tag = parser.nextTag();
        assertTrue(tag.isOpen());
        assertEquals("body", tag.getName());
        tag = parser.nextTag();
        assertTrue(tag.isClose());
        assertEquals("body", tag.getName());
        tag = parser.nextTag();
        assertTrue(tag.isClose());
        assertEquals("html", tag.getName());

    }

	/**
	 *
	 * @throws Exception
	 */
	@Test
	final void conditionalComments() throws Exception
	{
		final XmlPullParser parser = new XmlPullParser();
		parser.parse("<!--[if IE]><a href='test.html'>my link</a><![endif]-->");
		XmlTag tag = parser.nextTag();
		assertTrue(tag.isOpen());
		assertEquals("a", tag.getName());
		tag = parser.nextTag();
		assertTrue(tag.isClose());
		assertEquals("a", tag.getName());
		tag = parser.nextTag();
		assertNull(tag);
	}

	/**
	 *
	 * @throws Exception
	 */
	@Test
	final void conditionalComments2() throws Exception
	{
		final XmlPullParser parser = new XmlPullParser();
		parser.parse("<!--[if IE]><a href='test.html'>my link</a><![endif]-->");
		HttpTagType type = parser.next();
		assertEquals(type, HttpTagType.CONDITIONAL_COMMENT);
		type = parser.next();
		assertEquals(type, HttpTagType.TAG);
		assertTrue((parser.getElement()).isOpen());
		type = parser.next();
		assertEquals(type, HttpTagType.BODY);
		type = parser.next();
		assertEquals(type, HttpTagType.TAG);
		assertEquals("a", (parser.getElement()).getName());
		assertTrue((parser.getElement()).isClose());
		type = parser.next();
		assertEquals(type, HttpTagType.CONDITIONAL_COMMENT_ENDIF);
		type = parser.next();
		assertEquals(type, HttpTagType.NOT_INITIALIZED);
	}

	/**
	 *
	 * @throws Exception
	 */
	@Test
	final void names() throws Exception
	{
		final XmlPullParser parser = new XmlPullParser();
		parser.parse("<filter-mapping>");
		XmlTag tag = parser.nextTag();
		assertTrue(tag.isOpen());
		assertEquals("filter-mapping", tag.getName());

		parser.parse("<filter.mapping>");
		tag = parser.nextTag();
		assertTrue(tag.isOpen());
		assertEquals("filter.mapping", tag.getName());

		parser.parse("<filter_mapping>");
		tag = parser.nextTag();
		assertTrue(tag.isOpen());
		assertEquals("filter_mapping", tag.getName());
	}

	/**
	 *
	 * @throws Exception
	 */
	@Test
	final void doctype() throws Exception
	{
		final XmlPullParser parser = new XmlPullParser();
		parser.parse("<!DOCTYPE html>");
		HttpTagType type = parser.next();
		assertEquals(HttpTagType.DOCTYPE, type);
		assertEquals("!DOCTYPE html", parser.getDoctype());
	}

	/**
	 * @throws Exception
	 */
	@Test
	final void downlevelRevealedConditionalComments() throws Exception
	{
		final XmlPullParser parser = new XmlPullParser();
		parser.parse("<!--[if (gt IE 9)|!(IE)]><!--><html lang=\"en\" class=\"no-js\"><!--<![endif]--> <span>test</span>");
		HttpTagType type = parser.next();
		assertEquals(HttpTagType.CONDITIONAL_COMMENT, type);

		type = parser.next();
		assertEquals(HttpTagType.COMMENT, type);

		type = parser.next();
		assertEquals(HttpTagType.TAG, type);
		XmlTag componentTag = parser.getElement();
		assertEquals("html", componentTag.getName());

		type = parser.next();
		assertEquals(HttpTagType.CONDITIONAL_COMMENT_ENDIF, type);
	}
}