package lux.xml.tinybin;

import static org.junit.Assert.assertTrue;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;

import javax.xml.transform.stream.StreamSource;

import lux.SearchTest;
import net.sf.saxon.Configuration;
import net.sf.saxon.expr.sort.CodepointCollator;
import net.sf.saxon.expr.sort.GenericAtomicComparer;
import net.sf.saxon.functions.DeepEqual;
import net.sf.saxon.s9api.DocumentBuilder;
import net.sf.saxon.s9api.Processor;
import net.sf.saxon.s9api.SaxonApiException;
import net.sf.saxon.s9api.XdmNode;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.tree.iter.SingletonIterator;
import net.sf.saxon.tree.tiny.TinyDocumentImpl;

import org.apache.commons.io.IOUtils;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;

public class TinyBinaryTest {

    Processor processor;
    DocumentBuilder builder;
    
    @Before
    public void init () {
        processor = new Processor(false);
        builder = processor.newDocumentBuilder();
    }
    
    @Test
    public void testRoundTrip() throws SaxonApiException, XPathException, IOException {
        // try building a TinyBinary and recreating a tree from that
        assertRoundTrip("lux/reader-test.xml", null);
        assertRoundTrip("lux/reader-test.xml", "utf-8");
        // reuse the same Processor (and thus namepool)        
        assertRoundTrip("lux/reader-test.xml", null);
        assertRoundTrip("lux/reader-test.xml", "utf-8");
        // try a document that includes some namespaces
        assertRoundTrip("lux/reader-test-ns.xml", null);
        assertRoundTrip("lux/reader-test-ns.xml", "utf-8");
        // a large(r) document:
        assertRoundTrip("lux/hamlet.xml", null);
        assertRoundTrip("lux/hamlet.xml", "utf-8");
    }
    
    @Test
    public void testAttributes () throws Exception {
        assertRoundTrip ("conf/solrconfig.xml", null);
        assertRoundTrip ("conf/solrconfig.xml", "utf-8");
    }
    
    @Test
    public void testEmptyAttribute() throws SaxonApiException, XPathException, IOException {
    	// this document has an attribute with an empty value
        assertRoundTrip("lux/wikipedia-ns-test.xml", "utf-8");
    }
    
    @Test
    public void testOnce() throws SaxonApiException, XPathException, IOException {
        assertRoundTrip("lux/reader-test.xml", "utf-8");
    }
    
    @Test @Ignore
    public void testBenchmark () throws IOException, SaxonApiException {
        doBenchmark("lux/reader-test.xml", null, 1000);
        doBenchmark("lux/reader-test.xml", "utf-8", 1000);
        doBenchmark("lux/reader-test.xml", null, 1000);
        doBenchmark("lux/reader-test.xml", "utf-8", 1000);
        doBenchmark("lux/hamlet.xml", null, 1000);
        doBenchmark("lux/hamlet.xml", "utf-8", 1000);
    }    
    
    private void doBenchmark (String docpath, String charsetName, int iterations) throws IOException, SaxonApiException {
        InputStream in = SearchTest.class.getClassLoader().getResourceAsStream(docpath);
        byte[] inputBytes = IOUtils.toByteArray(in);
        in.close();
        Charset charset = charsetName == null ? null : Charset.forName(charsetName);
        XdmNode doc = builder.build(new StreamSource(new ByteArrayInputStream(inputBytes)));
        TinyBinary tinyBin = new TinyBinary(((TinyDocumentImpl) doc.getUnderlyingNode()).getTree(), charset);
        byte[] tinyInput = tinyBin.getBytes();
        System.out.println(String.format("Original size=%d bytes, 'tiny' binary size=%d bytes", inputBytes.length, tinyBin.length()));
        long start = System.nanoTime();
        for (int i = 0; i < iterations; i++) {
            builder.build(new StreamSource(new ByteArrayInputStream(inputBytes)));
        }
        long t1 = System.nanoTime();
        Configuration config = processor.getUnderlyingConfiguration();
        for (int i = 0; i < iterations; i++) {
            new TinyBinary(tinyInput, charset).getTinyDocument(config);
        }
        long t2 = System.nanoTime();
        System.out.println (String.format("DocBuilder: %dms; TinyBinary: %dms", (t1-start)/1000000, (t2-t1)/1000000));        
    }
    
    private void assertRoundTrip (String docpath, String charsetName)
            throws SaxonApiException, XPathException, IOException 
    {
    	// get a file from the class path
        InputStream in = SearchTest.class.getClassLoader().getResourceAsStream(docpath);
        // build a document from that file
        XdmNode doc = builder.build(new StreamSource(in));
        in.close();
        Charset charset = charsetName == null ? null : Charset.forName(charsetName);
        // Make a TinyBinary from the TinyTree
        TinyBinary tinyBin = new TinyBinary(((TinyDocumentImpl) doc.getUnderlyingNode()).getTree(), charset);
        byte[] b = tinyBin.getBytes();
        // Copy the TinyBinary using its byte array 
        TinyBinary copy = new TinyBinary (b, charset);
        Configuration config = processor.getUnderlyingConfiguration();
        // get the document node from the copy
        TinyDocumentImpl tinyDoc = copy.getTinyDocument(config);
        // for debugging:
        // processor.newSerializer(System.out).serializeNode(new XdmNode(tinyDoc));
        assertTrue (docpath + " was not preserved by TinyBinary roundtrip",
                DeepEqual.deepEquals(
                SingletonIterator.makeIterator(tinyDoc),
                SingletonIterator.makeIterator(doc.getUnderlyingNode()),
                new GenericAtomicComparer(CodepointCollator.getInstance(),
                        config.getConversionContext()),
                config,
                DeepEqual.INCLUDE_PREFIXES |
                DeepEqual.EXCLUDE_WHITESPACE_TEXT_NODES |
                    DeepEqual.INCLUDE_COMMENTS |
                    DeepEqual.COMPARE_STRING_VALUES |
                    DeepEqual.INCLUDE_PROCESSING_INSTRUCTIONS));
    }
    
}

@@ -1855,32 +1855,239 @@
 %0A    %0A    @Test%0A
+    public void testReadVersion0 () throws Exception %7B%0A        assertRoundTrip(%22lux/reader-test.xml%22, null, (byte) 0);%0A        assertRoundTrip(%22lux/reader-test.xml%22, %22utf-8%22, (byte) 0);%0A    %7D%0A    %0A    @Test%0A
     public void 
@@ -4503,37 +4503,42 @@
 Name
-)%0A            throws SaxonApi
+, byte formatVersion) throws XPath
 Exce
@@ -4536,37 +4536,40 @@
 XPathException, 
-XPath
+SaxonApi
 Exception, IOExc
@@ -4575,21 +4575,16 @@
 ception 
-%0A    
 %7B%0A    %09/
@@ -5058,32 +5058,47 @@
 tTree(), charset
+, formatVersion
 );%0A        byte%5B
@@ -6190,24 +6190,256 @@
 RUCTIONS));%0A
+    %09%0A    %7D%0A    %0A    private void assertRoundTrip (String docpath, String charsetName)%0A            throws SaxonApiException, XPathException, IOException %0A    %7B%0A    %09assertRoundTrip (docpath, charsetName, TinyBinary.CURRENT_FORMAT);%0A
     %7D%0A    %0A%7D

test for tinybin version 0 back compat