package org.codehaus.stax.test.wstream;

import java.io.*;

import javax.xml.stream.*;

/**
 * Set of unit tests for verifying operation of {@link XMLStreamWriter}
 * in "repairing" mode.
 *
 * @author Tatu Saloranta
 */
public class TestRepairingWriter
    extends BaseWriterTest
{
    /**
     * Test similar to the one in {@link TestSimpleWriter}.
     */
    public void testElements()
        throws IOException, XMLStreamException
    {
        StringWriter strw = new StringWriter();
        XMLStreamWriter w = getRepairingWriter(strw);
        final String URL_P1 = "http://p1.org";
        final String URL_P2 = "http://ns.p2.net/yeehaw.html";
        final String URL_DEF = "urn:default";

        final String TEXT = "  some text\n";

        w.writeStartDocument();

        // Calling setPrefix() should be optional; but if we call it,
        // expectation is that it does properly cause URL to be bound.
        w.setPrefix("p1", URL_P1);
        w.writeStartElement(URL_P1, "test");

        w.writeStartElement("p2", "branch", URL_P2);

        // And then a dynamically created prefix...
        w.writeStartElement(URL_DEF, "leaf");

        w.writeCharacters(TEXT);

        w.writeEndElement(); // first leaf

        w.writeEmptyElement(URL_P1, "leaf"); // second leaf

        w.writeStartElement("", "third"); // may need dynamic NS too
        w.writeEndElement();

        w.writeEndElement(); // branch
        w.writeEndElement(); // root elem
        w.writeEndDocument();
        w.close();

        // And then let's parse and verify it all:
        XMLStreamReader sr = constructNsStreamReader(strw.toString());
        assertTokenType(START_DOCUMENT, sr.getEventType(), sr);

        // root element
        assertTokenType(START_ELEMENT, sr.next(), sr);
        assertEquals("test", sr.getLocalName());
        // ??? is writer obligated to honor the prefix suggestion
        assertEquals(URL_P1, sr.getNamespaceURI());
        /* note: can not really verify number of namespace bindings, since
         * writer should be in charge... and it may output extra bindings,
         * too (and use default ns or explicit ones etc)
         */
        
        // first branch:
        assertTokenType(START_ELEMENT, sr.next(), sr);
        assertEquals("branch", sr.getLocalName());
        assertEquals(URL_P2, sr.getNamespaceURI());

        // first leaf
        assertTokenType(START_ELEMENT, sr.next(), sr);
        assertEquals("leaf", sr.getLocalName());
        assertEquals(URL_DEF, sr.getNamespaceURI());

        assertTokenType(CHARACTERS, sr.next(), sr);
        assertEquals(TEXT, getAllText(sr));
        // not: getAllText ^^^ moves cursor!

        assertTokenType(END_ELEMENT, sr.getEventType(), sr);
        assertEquals("leaf", sr.getLocalName());
        assertEquals(URL_DEF, sr.getNamespaceURI());

        // another leaf:
        assertTokenType(START_ELEMENT, sr.next(), sr);
        assertEquals("leaf", sr.getLocalName());
        assertEquals(URL_P1, sr.getNamespaceURI());

        assertTokenType(END_ELEMENT, sr.next(), sr);
        assertEquals("leaf", sr.getLocalName());
        assertEquals(URL_P1, sr.getNamespaceURI());

        // "third"
        /* Adding explicit catching to print more diagnostics, as one
         * of the tested impls did fail here:
         */
        try {
            assertTokenType(START_ELEMENT, sr.next(), sr);
        } catch (XMLStreamException e) {
            fail("Unexpected problems when parsing document [\""+strw.toString()+"\"], expecting element 'third': "+e.getMessage());
            throw e;
        }
        assertEquals("third", sr.getLocalName());
        assertNoNsURI(sr);
        assertTokenType(END_ELEMENT, sr.next(), sr);
        assertEquals("third", sr.getLocalName());
        assertNoNsURI(sr);

        // (close) branch
        assertTokenType(END_ELEMENT, sr.next(), sr);
        assertEquals("branch", sr.getLocalName());
        assertEquals(URL_P2, sr.getNamespaceURI());

        // closing root element
        assertTokenType(END_ELEMENT, sr.next(), sr);
        assertEquals("test", sr.getLocalName());
        assertEquals(URL_P1, sr.getNamespaceURI());

        assertTokenType(END_DOCUMENT, sr.next(), sr);
    }

    public void testAttributeSimple()
        throws IOException, XMLStreamException
    {
        StringWriter strw = new StringWriter();
        XMLStreamWriter w = getRepairingWriter(strw);
        final String URL_P1 = "http://p1.org";
        final String ATTR_VALUE = "'value'&\"another\"";

        w.writeStartDocument();
        w.writeStartElement("", "test");
        w.writeAttribute(URL_P1, "attr", ATTR_VALUE);
        w.writeEndElement();
        w.writeEndDocument();
        w.close();

//System.err.println("testAttributeSimple: doc = '"+strw+"'");

        // And then let's parse and verify it all:
        XMLStreamReader sr = constructNsStreamReader(strw.toString());
        assertTokenType(START_DOCUMENT, sr.getEventType(), sr);

        // root element
        assertTokenType(START_ELEMENT, sr.next(), sr);
        assertEquals("test", sr.getLocalName());
        assertNoNsURI(sr);

        assertEquals(1, sr.getAttributeCount());
        assertEquals("attr", sr.getAttributeLocalName(0));
        assertEquals(URL_P1, sr.getAttributeNamespace(0));
        assertEquals(ATTR_VALUE, sr.getAttributeValue(0));

        assertTokenType(END_ELEMENT, sr.next(), sr);
        assertEquals("test", sr.getLocalName());
        assertNoNsURI(sr);

        assertTokenType(END_DOCUMENT, sr.next(), sr);
    }

    public void testAttributes()
        throws IOException, XMLStreamException
    {
        StringWriter strw = new StringWriter();
        XMLStreamWriter w = getRepairingWriter(strw);
//        final String URL_P1 = "http://p1.org";
        final String URL_DEF = "urn:default";
        final String ATTR_VALUE = "'value\"";
        final String ATTR_VALUE2 = "<tag>";

        w.writeStartDocument();

        /* Calling this method should be optional; but if we call it,
         * exceptation is that it does properly bind the prefix and URL
         * as the 'preferred' combination. In this case we'll just try
         * to make URL bound as the default namespace
         */
        w.setDefaultNamespace(URL_DEF);
        w.writeStartElement(URL_DEF, "test");

        /* And let's further make element and attribute(s) belong to that
         * same namespace
         */
        w.writeStartElement("", "leaf", URL_DEF);
        w.writeAttribute(URL_DEF, "attr", ATTR_VALUE);
        w.writeEndElement();

        w.writeEmptyElement("", "leaf"); // in empty/no namespace!

        w.writeStartElement(URL_DEF, "leaf");
        w.writeAttribute("", "attr2", ATTR_VALUE2); // in empty/no namespace
        w.writeEndElement();

        w.writeEndElement(); // root elem
        w.writeEndDocument();
        w.close();

        // And then let's parse and verify it all:
        //System.err.println("testAttributes: doc = '"+strw+"'");

        XMLStreamReader sr = constructNsStreamReader(strw.toString());
        assertTokenType(START_DOCUMENT, sr.getEventType(), sr);

        // root element
        assertTokenType(START_ELEMENT, sr.next(), sr);
        assertEquals("test", sr.getLocalName());
        assertEquals(URL_DEF, sr.getNamespaceURI());
        
        // first leaf:
        assertTokenType(START_ELEMENT, sr.next(), sr);
        assertEquals("leaf", sr.getLocalName());
        assertEquals(URL_DEF, sr.getNamespaceURI());
        assertEquals(1, sr.getAttributeCount());
        assertEquals("attr", sr.getAttributeLocalName(0));

        String uri = sr.getAttributeNamespace(0);
        if (!URL_DEF.equals(uri)) {
            fail("Expected attribute 'attr' to have NS '"+URL_DEF+"', was "+valueDesc(uri)+"; input = '"+strw+"'");
        }
        assertEquals(ATTR_VALUE, sr.getAttributeValue(0));
        assertTokenType(END_ELEMENT, sr.next(), sr);
        assertEquals("leaf", sr.getLocalName());
        assertEquals(URL_DEF, sr.getNamespaceURI());

        // empty leaf
        assertTokenType(START_ELEMENT, sr.next(), sr);
        assertEquals("leaf", sr.getLocalName());
        assertNoNsURI(sr);
        assertTokenType(END_ELEMENT, sr.next(), sr);
        assertEquals("leaf", sr.getLocalName());
        assertNoNsURI(sr);
        
        // third leaf
        assertTokenType(START_ELEMENT, sr.next(), sr);
        assertEquals("leaf", sr.getLocalName());
        assertEquals(URL_DEF, sr.getNamespaceURI());

        assertEquals(1, sr.getAttributeCount());
        assertEquals("attr2", sr.getAttributeLocalName(0));
        assertNoAttrNamespace(sr.getAttributeNamespace(0));
        assertEquals(ATTR_VALUE2, sr.getAttributeValue(0));

        assertTokenType(END_ELEMENT, sr.next(), sr);
        assertEquals("leaf", sr.getLocalName());
        assertEquals(URL_DEF, sr.getNamespaceURI());

        // closing root element
        assertTokenType(END_ELEMENT, sr.next(), sr);
        assertEquals("test", sr.getLocalName());
        assertEquals(URL_DEF, sr.getNamespaceURI());

        assertTokenType(END_DOCUMENT, sr.next(), sr);
    }

    /**
     * This test specifically checks that namespace bindings for
     * sub-trees do not "leak" into following sibling elements or
     * trees.
     */
    public void testSiblingNsBinding()
        throws IOException, XMLStreamException
    {
        StringWriter strw = new StringWriter();
        XMLStreamWriter w = getRepairingWriter(strw);
        final String URL_P1 = "http://p1.org";

        w.writeStartDocument();
        w.writeStartElement("root");

        // First leaf:
        w.writeStartElement("leaf1");
        w.writeAttribute(URL_P1, "attr1", "1");
        w.writeEndElement();

        // Second leaf:
        w.writeStartElement("leaf2");
        w.writeAttribute(URL_P1, "attr2", "2");
        w.writeEndElement();

        w.writeEndDocument();
        w.close();

//System.err.println("doc = '"+strw+"'");

        XMLStreamReader sr = constructNsStreamReader(strw.toString());
        // root element
        assertTokenType(START_ELEMENT, sr.next(), sr);
        assertEquals("root", sr.getLocalName());

        // First leaf:
        assertTokenType(START_ELEMENT, sr.next(), sr);
        assertEquals("leaf1", sr.getLocalName());
        assertEquals(1, sr.getAttributeCount());
        assertEquals("attr1", sr.getAttributeLocalName(0));
        assertEquals("1", sr.getAttributeValue(0));
        assertEquals(URL_P1, sr.getAttributeNamespace(0));
        assertEquals(1, sr.getNamespaceCount());
        assertTokenType(END_ELEMENT, sr.next(), sr);
        assertEquals(URL_P1, sr.getNamespaceURI(0));

        // Second leaf:
        assertTokenType(START_ELEMENT, sr.next(), sr);
        assertEquals("leaf2", sr.getLocalName());
        assertEquals(1, sr.getAttributeCount());
        assertEquals("attr2", sr.getAttributeLocalName(0));
        assertEquals("2", sr.getAttributeValue(0));
        assertEquals(URL_P1, sr.getAttributeNamespace(0));
        assertEquals(1, sr.getNamespaceCount());
        assertTokenType(END_ELEMENT, sr.next(), sr);
        assertEquals(URL_P1, sr.getNamespaceURI(0));

        assertTokenType(END_ELEMENT, sr.next(), sr);
        assertTokenType(END_DOCUMENT, sr.next(), sr);
    }

    public void testSiblingNs2()
        throws IOException, XMLStreamException
    {
        StringWriter strw = new StringWriter();
        XMLStreamWriter w = getRepairingWriter(strw);

        String ns1 = "urn://namespace1";
        String ns2 = "urn://namespace2";
        w.writeStartDocument();
        w.writeStartElement(ns1, "root");
        w.writeStartElement(ns2, "first");
        w.writeEndElement();
        w.writeStartElement(ns2, "second");
        w.writeEndElement();
        w.writeEndElement();
        w.writeEndDocument();
        w.close();

        // And then let's parse and verify it all:
        XMLStreamReader sr = constructNsStreamReader(strw.toString());
        assertTokenType(START_DOCUMENT, sr.getEventType(), sr);

        assertTokenType(START_ELEMENT, sr.next(), sr);
        assertEquals("root", sr.getLocalName());
        // Can't assume anything about prefix assigned (if any), just ns uri
        assertEquals(ns1, sr.getNamespaceURI());
        assertEquals(0, sr.getAttributeCount());

        assertTokenType(START_ELEMENT, sr.next(), sr);
        assertEquals("first", sr.getLocalName());
        assertEquals(ns2, sr.getNamespaceURI());
        assertEquals(0, sr.getAttributeCount());
        assertTokenType(END_ELEMENT, sr.next(), sr);
        assertEquals("first", sr.getLocalName());

        assertTokenType(START_ELEMENT, sr.next(), sr);
        assertEquals("second", sr.getLocalName());
        assertEquals(ns2, sr.getNamespaceURI());
        assertEquals(0, sr.getAttributeCount());
        assertTokenType(END_ELEMENT, sr.next(), sr);
        assertEquals("second", sr.getLocalName());
        assertTokenType(END_ELEMENT, sr.next(), sr);
        assertEquals("root", sr.getLocalName());
    }

    /**
     * Although repairing writers are allowed to output any number of
     * namespace declarations they want to, let's still check that
     * unnecessary ones are not output in simple cases. While doing
     * that is not strictly an error, it seems reasonable fail the
     * test, to let implementors know about sub-optimal behavior.
     */
    public void testOptimalDefaultNsDecls()
        throws IOException, XMLStreamException
    {
        StringWriter strw = new StringWriter();
        XMLStreamWriter w = getRepairingWriter(strw);
        final String URL_P1 = "http://p1.org";

        w.writeStartDocument();
        // Let's try to enforce using of the default ns by passing empty prefix
        // (writer is not required to honor that request though)
        w.writeStartElement("", "test", URL_P1);
        w.writeStartElement("", "leaf", URL_P1);
        w.writeEndElement();
        w.writeEndElement();
        w.writeEndDocument();
        w.close();

//System.err.println("DEBUG: doc = '"+strw+"'");
        // And then let's parse and verify it all:
        XMLStreamReader sr = constructNsStreamReader(strw.toString());
        assertTokenType(START_DOCUMENT, sr.getEventType(), sr);

        // root element
        assertTokenType(START_ELEMENT, sr.next(), sr);
        assertEquals("test", sr.getLocalName());
        assertEquals(URL_P1, sr.getNamespaceURI());
        assertEquals(1, sr.getNamespaceCount());
        assertEquals(URL_P1, sr.getNamespaceURI(0));

        // leaf: should be able to use parent's namespace decl
        assertTokenType(START_ELEMENT, sr.next(), sr);
        assertEquals("leaf", sr.getLocalName());
        assertEquals(URL_P1, sr.getNamespaceURI());
        assertEquals(0, sr.getNamespaceCount());

        sr.close();
    }

    public void testOptimalNonDefaultNsDecls()
        throws IOException, XMLStreamException
    {
        StringWriter strw = new StringWriter();
        XMLStreamWriter w = getRepairingWriter(strw);
        final String URL_P1 = "http://p1.org";

        w.writeStartDocument();
        w.writeStartElement(URL_P1, "test");
        w.writeStartElement(URL_P1, "leaf");
        w.writeEndElement();
        w.writeEndElement();
        w.writeEndDocument();
        w.close();

        // And then let's parse and verify it all:
        XMLStreamReader sr = constructNsStreamReader(strw.toString());
        assertTokenType(START_DOCUMENT, sr.getEventType(), sr);

        // root element
        assertTokenType(START_ELEMENT, sr.next(), sr);
        assertEquals("test", sr.getLocalName());
        assertEquals(URL_P1, sr.getNamespaceURI());
        assertEquals(1, sr.getNamespaceCount());
        assertEquals(URL_P1, sr.getNamespaceURI(0));

        // leaf: should be able to use parent's namespace decl
        assertTokenType(START_ELEMENT, sr.next(), sr);
        assertEquals("leaf", sr.getLocalName());
        assertEquals(URL_P1, sr.getNamespaceURI());
        assertEquals(0, sr.getNamespaceCount());

        sr.close();
    }

    public void testPreBoundXmlNamespaceAsAttribute()
        throws IOException, XMLStreamException
    {
        StringWriter strw = new StringWriter();
        XMLStreamWriter w = getRepairingWriter(strw);
        w.writeStartDocument();
        w.writeStartElement("test");
        w.writeAttribute(javax.xml.XMLConstants.XML_NS_URI, "lang", "en-US");
        w.writeEndElement();
        w.writeEndDocument();
        w.close();
        final String XML = strw.toString().trim();
        assertEquals("<?xml version='1.0' encoding='UTF-8'?><test xml:lang=\"en-US\"/>", XML);
    }

    public void testPreBoundXmlNamespaceAsElement()
        throws IOException, XMLStreamException
    {
        StringWriter strw = new StringWriter();
        XMLStreamWriter w = getRepairingWriter(strw);
        w.writeStartDocument();
        w.writeStartElement(javax.xml.XMLConstants.XML_NS_URI, "test");
        w.writeEndElement();
        w.writeEndDocument();
        w.close();

        final String XML = strw.toString().trim();
        assertEquals("<?xml version='1.0' encoding='UTF-8'?><xml:test/>", XML);
    }
}
