package be.iminds.ilabt.jfed.rspec.parser;

import org.testng.annotations.Test;

import javax.xml.namespace.QName;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.XMLEvent;
import javax.xml.transform.stream.StreamSource;
import java.io.StringReader;

import static be.iminds.ilabt.jfed.rspec.parser.StaxHelper.xmlEventToString;
import static org.testng.Assert.*;

/**
 * StaxHelperTest
 */
public class StaxHelperTest {
    private static String testXml = "<a><b><c>test</c></b></a>";
    private static String testXmlNested = "<a><b><c>" +
            "<b>" +
            "<b/>" +
            "<b>test2</b>" +
            "<d><b>test</b></d>" +
            "</b>" +
            "</c></b></a>";

    private XMLEventReader getTestXmlXMLEventReader(String xmlString) throws XMLStreamException {
        XMLInputFactory xif = XMLInputFactory.newFactory();
        StreamSource xml = new StreamSource(new StringReader(xmlString));
        return xif.createXMLEventReader(xml);
    }

    @Test
    public void testMakeXmlEventReader() throws XMLStreamException {
        XMLEventReader eventReader = getTestXmlXMLEventReader(testXml);
        XMLEvent e = eventReader.nextEvent();
        assertTrue(e.isStartDocument());

        XMLEvent startA = eventReader.nextEvent();
        assertTrue(startA.isStartElement());
        assertEquals(startA.asStartElement().getName(), new QName("a"));
        XMLEvent startB = eventReader.nextEvent();
        assertTrue(startB.isStartElement());
        assertEquals(startB.asStartElement().getName(), new QName("b"));

        XMLEventReader subEventReader = StaxHelper.getEventsUntilEndTag(startB, eventReader);

        XMLEvent endA = eventReader.nextEvent();
        assertTrue(endA.isEndElement());
        assertEquals(endA.asEndElement().getName(), new QName("a"));
        assertEndDoc(eventReader);
        eventReader.close();

        assertTrue(subEventReader.hasNext());

        XMLEvent startB2peek = subEventReader.peek();
        XMLEvent startB2 = subEventReader.nextEvent();
        assertTrue(startB2.isStartElement());
        assertEquals(startB2.asStartElement().getName(), new QName("b"));
        assertEquals(startB2, startB2peek);

        assertTrue(subEventReader.hasNext());
        XMLEvent startCpeek = subEventReader.peek();
        XMLEvent startC = subEventReader.nextEvent();
        assertTrue(startC.isStartElement());
        assertEquals(startC.asStartElement().getName(), new QName("c"));
        assertEquals(startC, startCpeek);

        assertTrue(subEventReader.hasNext());
        XMLEvent textpeek = subEventReader.peek();
        XMLEvent text = subEventReader.nextEvent();
        assertTrue(text.isCharacters());
        assertEquals(text.asCharacters().getData(), "test");
        assertEquals(text, textpeek);

        assertTrue(subEventReader.hasNext());
        XMLEvent endCpeek = subEventReader.peek();
        XMLEvent endC = subEventReader.nextEvent();
        assertTrue(endC.isEndElement());
        assertEquals(endC.asEndElement().getName(), new QName("c"));
        assertEquals(endC, endCpeek);

        assertTrue(subEventReader.hasNext());
        XMLEvent endBpeek = subEventReader.peek();
        XMLEvent endB = subEventReader.nextEvent();
        assertTrue(endB.isEndElement());
        assertEquals(endB.asEndElement().getName(), new QName("b"));
        assertEquals(endB, endB);
        assertFalse(subEventReader.hasNext());
        subEventReader.close();
    }

    @Test
    public void testGetElementText() throws XMLStreamException {
        XMLEventReader eventReader = getTestXmlXMLEventReader(testXml);
        XMLEvent e = eventReader.nextEvent();
        assertTrue(e.isStartDocument());

        assertStart("a", eventReader);
        assertStart("b", eventReader);

        XMLEvent startCpeek = eventReader.peek();
        assertTrue(startCpeek.isStartElement());
        XMLEvent startC = eventReader.nextEvent();

        String text = eventReader.getElementText();
        assertEquals(text, "test");

        assertEnd("b", eventReader);
        assertEnd("a", eventReader);
        assertEndDoc(eventReader);
        eventReader.close();
    }

    @Test
    public void testReadAllExpectingNothingMore() throws XMLStreamException {
        XMLEventReader eventReader = getTestXmlXMLEventReader(testXml);
        XMLEvent e = eventReader.nextEvent();
        assertTrue(e.isStartDocument());

        assertStart("a", eventReader);
        XMLEvent b = assertStart("b", eventReader);
        StaxHelper.readAllExpectingNothingMore(b, eventReader);
        assertEnd("a", eventReader);
        assertEndDoc(eventReader);
        eventReader.close();
    }
    @Test
    public void testReadAllExpectingNothingMore2() throws XMLStreamException {
        XMLEventReader eventReader = getTestXmlXMLEventReader(testXmlNested);
        XMLEvent e = eventReader.nextEvent();
        assertTrue(e.isStartDocument());

        assertStart("a", eventReader);
        XMLEvent b = assertStart("b", eventReader);
        StaxHelper.readAllExpectingNothingMore(b, eventReader);
        assertEnd("a", eventReader);
        assertEndDoc(eventReader);
        eventReader.close();
    }
    @Test
    public void testReadAllExpectingNothingMore3() throws XMLStreamException {
        XMLEventReader eventReader = getTestXmlXMLEventReader(testXmlNested);
        XMLEvent e = eventReader.nextEvent();
        assertTrue(e.isStartDocument());

        assertStart("a", eventReader);
        assertStart("b", eventReader);
        XMLEvent c = assertStart("c", eventReader);
        StaxHelper.readAllExpectingNothingMore(c, eventReader);
        assertEnd("b", eventReader);
        assertEnd("a", eventReader);
        assertEndDoc(eventReader);
        eventReader.close();
    }
    @Test
    public void testReadAllExpectingNothingMore4() throws XMLStreamException {
        XMLEventReader eventReader = getTestXmlXMLEventReader(testXmlNested);
        XMLEvent e = eventReader.nextEvent();
        assertTrue(e.isStartDocument());

        assertStart("a", eventReader);
        assertStart("b", eventReader);
        assertStart("c", eventReader);
        XMLEvent b = assertStart("b", eventReader);
        StaxHelper.readAllExpectingNothingMore(b, eventReader);
        assertEnd("c", eventReader);
        assertEnd("b", eventReader);
        assertEnd("a", eventReader);
        assertEndDoc(eventReader);
        eventReader.close();
    }

    @Test
    public void testReadAllExpectingNothingMore5() throws XMLStreamException {
        XMLEventReader eventReader = getTestXmlXMLEventReader(testXmlNested);
        XMLEvent e = eventReader.nextEvent();
        assertTrue(e.isStartDocument());

        assertStart("a", eventReader);
        assertStart("b", eventReader);
        assertStart("c", eventReader);
        assertStart("b", eventReader);
        StaxHelper.readAllExpectingNothingMore(assertStart("b", eventReader), eventReader);
        StaxHelper.readAllExpectingNothingMore(assertStart("b", eventReader), eventReader);
        StaxHelper.readAllExpectingNothingMore(assertStart("d", eventReader), eventReader);
        assertEnd("b", eventReader);
        assertEnd("c", eventReader);
        assertEnd("b", eventReader);
        assertEnd("a", eventReader);
        assertEndDoc(eventReader);
        eventReader.close();
    }
    @Test
    public void testMakeXmlEventReaderGetElementText() throws XMLStreamException {
        XMLEventReader eventReader = getTestXmlXMLEventReader(testXml);
        XMLEvent e = eventReader.nextEvent();
        assertTrue(e.isStartDocument());

        XMLEvent startA = eventReader.nextEvent();
        assertTrue(startA.isStartElement());
        assertEquals(startA.asStartElement().getName(), new QName("a"));
        XMLEvent startB = eventReader.nextEvent();
        assertTrue(startB.isStartElement());
        assertEquals(startB.asStartElement().getName(), new QName("b"));

        XMLEventReader subEventReader = StaxHelper.getEventsUntilEndTag(startB, eventReader);
        eventReader.close();

        assertTrue(subEventReader.hasNext());
        XMLEvent startB2 = subEventReader.nextEvent();

        assertTrue(subEventReader.hasNext());

        XMLEvent startCpeek = subEventReader.peek();
        assertTrue(startCpeek.isStartElement());
        XMLEvent startC = subEventReader.nextEvent();

        String text = subEventReader.getElementText();
        assertEquals(text, "test");

        assertTrue(subEventReader.hasNext());
        XMLEvent endBpeek = subEventReader.peek();
        XMLEvent endB = subEventReader.nextEvent();
        assertTrue(endB.isEndElement());
        assertEquals(endB.asEndElement().getName(), new QName("b"));
        assertEquals(endB, endB);
        assertFalse(subEventReader.hasNext());
        subEventReader.close();
    }

    private XMLEvent assertStart(String name, XMLEventReader eventReader) throws XMLStreamException {
        assertTrue(eventReader.hasNext());
        XMLEvent peek = eventReader.peek();
        XMLEvent start = eventReader.nextEvent();
        assertTrue(start.isStartElement(), "unexpected element: "+xmlEventToString(start));
        assertEquals(start.asStartElement().getName(), new QName(name));
        assertEquals(start, peek);
        return start;
    }
    private XMLEvent assertEnd(String name, XMLEventReader eventReader) throws XMLStreamException {
        assertTrue(eventReader.hasNext());
        XMLEvent peek = eventReader.peek();
        XMLEvent end = eventReader.nextEvent();
        assertTrue(end.isEndElement(), "unexpected element: "+xmlEventToString(end));
        assertEquals(end.asEndElement().getName(), new QName(name));
        assertEquals(end, peek);
        return end;
    }
    private XMLEvent assertText(String text, XMLEventReader eventReader) throws XMLStreamException {
        assertTrue(eventReader.hasNext());
        XMLEvent peek = eventReader.peek();
        XMLEvent t = eventReader.nextEvent();
        assertTrue(t.isCharacters(), "unexpected element: "+xmlEventToString(t));
        assertEquals(t.asCharacters().getData(), text);
        assertEquals(t, peek);
        return t;
    }
    private XMLEvent assertEndDoc(XMLEventReader eventReader) throws XMLStreamException {
        assertTrue(eventReader.hasNext());
        XMLEvent peek = eventReader.peek();
        XMLEvent e = eventReader.nextEvent();
        assertTrue(e.isEndDocument(), "unexpected element: "+xmlEventToString(e));
        assertEquals(e, peek);
        assertFalse(eventReader.hasNext());
        return e;
    }


    @Test
    public void testNestedXmlEventReader() throws XMLStreamException {
        XMLEventReader eventReader = getTestXmlXMLEventReader(testXmlNested);
        XMLEvent e = eventReader.nextEvent();
        assertTrue(e.isStartDocument());

        assertStart("a", eventReader);
        XMLEvent startB = assertStart("b", eventReader);

        XMLEventReader subEventReader = StaxHelper.getEventsUntilEndTag(startB, eventReader);

        assertEnd("a", eventReader);
        assertEndDoc(eventReader);
        assertFalse(eventReader.hasNext());
        eventReader.close();

        assertStart("b", subEventReader);
        assertStart("c", subEventReader);
        assertStart("b", subEventReader);
        assertStart("b", subEventReader);
        assertEnd("b", subEventReader);
        assertStart("b", subEventReader);
        assertText("test2", subEventReader);
        assertEnd("b", subEventReader);
        assertStart("d", subEventReader);
        assertStart("b", subEventReader);
        assertText("test", subEventReader);
        assertEnd("b", subEventReader);
        assertEnd("d", subEventReader);
        assertEnd("b", subEventReader);
        assertEnd("c", subEventReader);
        assertEnd("b", subEventReader);

        assertFalse(subEventReader.hasNext());
        subEventReader.close();
    }
}
