package wstxtest.stream;

import java.io.*;
import java.util.Random;
import javax.xml.stream.*;

import org.codehaus.stax2.*;
import com.ctc.wstx.api.ReaderConfig;
import com.ctc.wstx.stax.WstxInputFactory;

/**
 * Unit tests for testing Woodstox-specific features of location
 * tracking.
 */
public class TestLocation
    extends BaseStreamTest
{
    public void testSimpleLocation()
        throws XMLStreamException
    {
        final String XML = "\r\n  <root>\r\n </root>";

        XMLInputFactory f = getWstxInputFactory();
        XMLStreamReader2 sr = (XMLStreamReader2)f.createXMLStreamReader(new StringReader(XML));

        int type = sr.next();
        if (type == XMLStreamConstants.SPACE) {
            type = sr.next();
        }
        assertTokenType(START_ELEMENT, type);

        Location loc = sr.getLocationInfo().getStartLocation();
        assertEquals(2, loc.getLineNumber());
        assertEquals(3, loc.getColumnNumber());
        assertEquals(4, loc.getCharacterOffset());

        assertTokenType(CHARACTERS, sr.next());
        assertTokenType(END_ELEMENT, sr.next());
        loc = sr.getLocationInfo().getStartLocation();
        assertEquals(3, loc.getLineNumber());
        assertEquals(2, loc.getColumnNumber());
        assertEquals(13, loc.getCharacterOffset());
    }

    public void testLineNumbers()
        throws XMLStreamException
    {
        final int SEED = 129;
        final int ROWS = 1000;

        // First, let's create xml doc:
        StringBuffer sb = new StringBuffer();
        sb.append("<a>");
        Random r = new Random(SEED);
        for (int i = 0; i < ROWS; ++i) {
            switch (r.nextInt() % 3) {
            case 0:
                sb.append("\r");
                break;
            case 1:
                sb.append("\r\n");
                break;
            default:
                sb.append("\n");
            }
            int ind = r.nextInt() % 7;
            while (--ind >= 0) {
                sb.append(' ');
            }
            sb.append("<b/>");
        }
        sb.append("</a>");

        // And then we'll parse to ensure line numbers and offsets are ok
        
        WstxInputFactory f = getWstxInputFactory();
        // Need to shrink it to get faster convergence
        f.getConfig().setInputBufferLength(23);
        XMLStreamReader2 sr = (XMLStreamReader2) f.createXMLStreamReader(new StringReader(sb.toString()));

        assertTokenType(START_ELEMENT, sr.next());

        int linenr = 1;
        int col = 4;
        int chars = 3;

        r = new Random(SEED);
        while (true) {
            // END_ELEM signals end...
            int type = sr.next();
            if (type == END_ELEMENT) {
                assertEquals("a", sr.getLocalName());
                break;
            }
            assertTokenType(type, type);

            Location loc = sr.getLocationInfo().getStartLocation();
            assertEquals(linenr, loc.getLineNumber());
            assertEquals(col, loc.getColumnNumber());
            assertEquals(chars, loc.getCharacterOffset());

            sb = new StringBuffer();
            boolean offByOne = false;
            switch (r.nextInt() % 3) {
            case 1:
                offByOne = true; // Since \r\n gets truncated to \n
            }
            sb.append("\n");
            int ind = r.nextInt() % 7;
            while (--ind >= 0) {
                sb.append(' ');
            }
            String ws = sb.toString();
            if (!ws.equals(sr.getText())) {
                fail("Expected "+quotedPrintable(ws)+", got "+quotedPrintable(sr.getText()));
            }

            /* Char offset refers to input chars, and thus includes original
             * linefeed (which is longer than result, for \r\n)
             */
            chars += ws.length();
            if (offByOne) {
                ++chars;
            }
            ++linenr;
            // Column won't, but since it's one-based, it'll still equal ws len
            col = sb.length();

            assertTokenType(START_ELEMENT, sr.next());
            assertEquals("b", sr.getLocalName());
            loc = sr.getLocationInfo().getStartLocation();
            assertEquals("Line number wrong", linenr, loc.getLineNumber());
            assertEquals("Column number wrong (line "+linenr+")", col, loc.getColumnNumber());
            assertEquals("Character offset wrong (line "+linenr+")", chars, loc.getCharacterOffset());
            assertTokenType(END_ELEMENT, sr.next());
            assertEquals("b", sr.getLocalName());

            chars += 4;
            col += 4;
        }
    }

    /**
     * This test was added due to bug [WSTX-97]: although it is hard to
     * verify exact offset calculation, it is quite straight-forward
     * to verify that it's monotonically increasing, at least.
     */
    public void testOffsetIncrementing()
        throws XMLStreamException
    {
        doTestOffset(false, false); // non-coalesce
        doTestOffset(false, true); // non-coalesce

        doTestOffset(true, false); // coalesce
        doTestOffset(true, true); // coalesce
    }

    /*
    /////////////////////////////////////////////////////////
    // Helper methods:
    /////////////////////////////////////////////////////////
     */
     
    public void doTestOffset(boolean coal, boolean readAll)
        throws XMLStreamException
    {
        // First, let's create some input...
        StringBuffer inputBuf = new StringBuffer();
        StringBuffer expOut = new StringBuffer();
        generateData(new Random(123), inputBuf, expOut, true); 
        String inputStr = inputBuf.toString();

        WstxInputFactory f = getWstxInputFactory();
        // Should shrink it to get faster convergence
        f.getConfig().setInputBufferLength(17);
        f.getConfig().doCoalesceText(coal);
        XMLStreamReader2 sr = (XMLStreamReader2) f.createXMLStreamReader(new StringReader(inputStr));

        int lastLine = 0;
        int lastOffset = 0;

        while (sr.next() != XMLStreamConstants.END_DOCUMENT) {
            Location loc = sr.getLocation();
            int line = loc.getLineNumber();
            int offset = loc.getCharacterOffset();

            if (line < lastLine) {
                fail("Location.getLineNumber() should increase steadily, old value: "+lastLine+", new: "+line);
            }
            if (offset < lastOffset) {
                fail("Location.getCharacterOffset() should increase steadily, old value: "+lastOffset+", new: "+offset);
            }
            lastLine = line;
            lastOffset = offset;

            if (readAll) { // read it, or just skip?
                if (sr.hasText()) {
                    String text = sr.getText();
                }
            }
        }
    }
}
