package org.exist.storage;

import org.exist.collections.Collection;
import org.exist.collections.IndexInfo;
import org.exist.dom.DefaultDocumentSet;
import org.exist.dom.DocumentImpl;
import org.exist.dom.DocumentSet;
import org.exist.security.xacml.AccessContext;
import org.exist.storage.lock.Lock;
import org.exist.storage.serializers.Serializer;
import org.exist.storage.txn.TransactionManager;
import org.exist.storage.txn.Txn;
import org.exist.test.TestConstants;
import org.exist.util.Configuration;
import org.exist.xmldb.XmldbURI;
import org.exist.xquery.XQuery;
import org.exist.xquery.value.Item;
import org.exist.xquery.value.Sequence;
import org.exist.xquery.value.SequenceIterator;
import org.junit.After;
import static org.junit.Assert.*;
import org.junit.Test;
import org.xml.sax.InputSource;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.Random;

/**
 * Test indexing and recovery of large string sequences.
 */
public class LargeValuesTest {

    private String CONFIG_QNAME =
    	"<collection xmlns=\"http://exist-db.org/collection-config/1.0\">" +
    	"	<index xmlns:x=\"http://www.foo.com\" xmlns:xx=\"http://test.com\">" +
    	"		<fulltext default=\"none\">" +
    	"		</fulltext>" +
        "       <create qname=\"@id\" type=\"xs:string\"/>" +
        "	</index>" +
    	"</collection>";

    private static final int KEY_COUNT = 5000;

    private static final int KEY_LENGTH = 5000;

    @Test
    public void storeAndRecover() {
        for (int i = 0; i < 1; i++) {
            System.out.println("-----------------------------------------------------");
            System.out.println("Run: " + i);
            storeDocuments();
            restart();
            remove();
        }
    }

    private File createDocument() throws IOException {
        File file = File.createTempFile("eXistTest", ".xml");
        OutputStream os = new FileOutputStream(file);
        Writer writer = new BufferedWriter(new OutputStreamWriter(os, "UTF-8"));

        Random r = new Random();
        writer.write("<test>");
        for (int i = 0; i < KEY_COUNT; i++) {
            writer.write("<key id=\"");
            int keySize = r.nextInt(KEY_LENGTH);
            if (keySize == 0)
                keySize = 1;
            for (int j = 0; j < keySize; j++) {
                char ch;
                do {
                    ch = (char) r.nextInt(0x5A);
                } while (ch < 0x41);
                writer.write(ch);
            }
            writer.write("\"/>");
        }
        writer.write("</test>");
        writer.close();
        return file;
    }

    /**
     * Store some documents, reindex the collection and crash without commit.
     */
    private void storeDocuments() {
        BrokerPool pool = null;
        DBBroker broker = null;
        try {
        	pool = startDB();
        	assertNotNull(pool);
            broker = pool.get(org.exist.security.SecurityManager.SYSTEM_USER);
            assertNotNull(broker);
            TransactionManager transact = pool.getTransactionManager();
            assertNotNull(transact);
            Txn transaction = transact.beginTransaction();
            assertNotNull(transaction);
            System.out.println("Transaction started ...");

            Collection root = broker.getOrCreateCollection(transaction, TestConstants.TEST_COLLECTION_URI);
            assertNotNull(root);
            broker.saveCollection(transaction, root);

            pool.getConfigurationManager().addConfiguration(transaction, broker, root, CONFIG_QNAME);

            transact.commit(transaction);
            transact.getJournal().flushToLog(true);
            
            BrokerPool.FORCE_CORRUPTION = true;
            transaction = transact.beginTransaction();
            File file = createDocument();
            IndexInfo info = root.validateXMLResource(transaction, broker, XmldbURI.create("test.xml"),
                    new InputSource(file.toURI().toASCIIString()));
            assertNotNull(info);
            root.store(transaction, broker, info, new InputSource(file.toURI().toASCIIString()), false);
            broker.saveCollection(transaction, root);

            transact.commit(transaction);

            transact.getJournal().flushToLog(true);
            System.out.println("Transaction interrupted ...");
            file.delete();
        } catch (Exception e) {
            e.printStackTrace();
            fail(e.getMessage());
        } finally {
            if (pool != null)
                pool.release(broker);
        }
    }

    /**
     * Just recover.
     */
    private void restart() {
        BrokerPool.FORCE_CORRUPTION = false;
        BrokerPool pool = null;
        DBBroker broker = null;
        try {
        	System.out.println("restart() ...\n");
        	pool = startDB();
        	assertNotNull(pool);
            broker = pool.get(org.exist.security.SecurityManager.SYSTEM_USER);
            assertNotNull(broker);

            Collection root = broker.openCollection(TestConstants.TEST_COLLECTION_URI, Lock.READ_LOCK);
            assertNotNull(root);

            DocumentImpl doc = root.getDocument(broker, XmldbURI.create("test.xml"));
            assertNotNull(doc);
            Serializer serializer = broker.getSerializer();
            serializer.reset();
            File tempFile = File.createTempFile("eXist", ".xml");
            Writer writer = new OutputStreamWriter(new FileOutputStream(tempFile), "UTF-8");
            serializer.serialize(doc, writer);

            XQuery xquery = broker.getXQueryService();
            DocumentSet docs = broker.getAllXMLResources(new DefaultDocumentSet());
            Sequence result = xquery.execute("//key/@id/string()", docs.docsToNodeSet(), AccessContext.TEST);
            assertEquals(KEY_COUNT, result.getItemCount());
            for (SequenceIterator i = result.iterate(); i.hasNext();) {
                Item item = i.nextItem();
                String s = item.getStringValue();
                assertTrue(s.length() > 0);
                if (s.length() == 0)
                    break;
                System.out.println(s);
            }
        } catch (Exception e) {
            e.printStackTrace();
	        fail(e.getMessage());
        } finally {
            if (pool != null)
                pool.release(broker);
        }
    }

    private void remove() {
        BrokerPool pool = null;
        DBBroker broker = null;
        try {
        	System.out.println("remove() ...\n");
        	pool = startDB();
        	assertNotNull(pool);
            broker = pool.get(org.exist.security.SecurityManager.SYSTEM_USER);
            assertNotNull(broker);
            TransactionManager transact = pool.getTransactionManager();
            assertNotNull(transact);
            Txn transaction = transact.beginTransaction();
            assertNotNull(transaction);

            Collection root = broker.openCollection(TestConstants.TEST_COLLECTION_URI, Lock.READ_LOCK);
            assertNotNull(root);
            broker.removeCollection(transaction, root);

            transact.commit(transaction);
        } catch (Exception e) {
            e.printStackTrace();
	        fail(e.getMessage());
        } finally {
            if (pool != null)
                pool.release(broker);
        }
    }

    @After
    public void closeDB() {
        BrokerPool.stopAll(false);
    }

    protected BrokerPool startDB() {
        try {
            Configuration config = new Configuration();
            BrokerPool.configure(1, 5, config);
            return BrokerPool.getInstance();
        } catch (Exception e) {
            e.printStackTrace();
            fail(e.getMessage());
        }
        return null;
    }
}

@@ -1523,17 +1523,17 @@
 COUNT = 
-5
+1
 000;%0A%0A  
@@ -6259,47 +6259,8 @@
 ak;%0A
-                System.out.println(s);%0A
     

remove console output produced by LargeValuesTest. Reduce the size of the generated document.