/*-
* See the file LICENSE for redistribution information.
*
* Copyright (c) 2002-2005
*      Sleepycat Software.  All rights reserved.
*
* $Id: LoggableTest.java,v 1.68 2004/12/22 14:11:45 linda Exp $
*/

package com.sleepycat.je.log;

import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import junit.framework.TestCase;

import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.je.cleaner.FileSummary;
import com.sleepycat.je.config.EnvironmentParams;
import com.sleepycat.je.dbi.DatabaseId;
import com.sleepycat.je.dbi.DatabaseImpl;
import com.sleepycat.je.dbi.DbTree;
import com.sleepycat.je.dbi.EnvironmentImpl;
import com.sleepycat.je.recovery.CheckpointEnd;
import com.sleepycat.je.recovery.CheckpointStart;
import com.sleepycat.je.tree.BIN;
import com.sleepycat.je.tree.ChildReference;
import com.sleepycat.je.tree.DBIN;
import com.sleepycat.je.tree.DIN;
import com.sleepycat.je.tree.FileSummaryLN;
import com.sleepycat.je.tree.IN;
import com.sleepycat.je.tree.INDeleteInfo;
import com.sleepycat.je.tree.Key;
import com.sleepycat.je.tree.LN;
import com.sleepycat.je.tree.MapLN;
import com.sleepycat.je.tree.Node;
import com.sleepycat.je.txn.TxnAbort;
import com.sleepycat.je.txn.TxnCommit;
import com.sleepycat.je.util.TestUtils;
import com.sleepycat.je.utilint.DbLsn;
import com.sleepycat.je.utilint.Tracer;

/**
 * Check that every loggable object can be read in and out of a buffer
 */
public class LoggableTest extends TestCase {

    static private final boolean DEBUG = false;

    private DocumentBuilder builder;
    private EnvironmentImpl env;
    private File envHome;
    private DatabaseImpl database;

    public LoggableTest()
        throws Exception {

        envHome = new File(System.getProperty(TestUtils.DEST_DIR));

        /*
         * Attempt to validate that the db print log output is valid
         * xml -- isn't quite working
         */
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        builder = factory.newDocumentBuilder();

    }

    public void setUp()
        throws IOException, DatabaseException {

        TestUtils.removeFiles("Setup", envHome, FileManager.JE_SUFFIX);
	
	EnvironmentConfig envConfig = new EnvironmentConfig();
	envConfig.setConfigParam(EnvironmentParams.NODE_MAX.getName(), "6");
        envConfig.setAllowCreate(true);
        env = new EnvironmentImpl(envHome, envConfig);
    }
    
    public void tearDown()
        throws IOException, DatabaseException {

        TestUtils.removeFiles("TearDown", envHome, FileManager.JE_SUFFIX);
        env.close();
    }

    public void testEntryData()
        throws Throwable {

        try {
            ByteBuffer buffer = ByteBuffer.allocate(1000);
            database = new DatabaseImpl("foo", new DatabaseId(1),
                                        env, new DatabaseConfig());

            /*
             * For each loggable object, can we write the entry data out?
             */

            /*
             * Tracer records.
             */
            Tracer dMsg = new Tracer("Hello there");
            writeAndRead(buffer, dMsg, new Tracer());

            /*
             * LNs
             */
            String data = "abcdef";
            LN ln = new LN(data.getBytes());
            LN lnFromLog = new LN();
            writeAndRead(buffer, ln, lnFromLog);
            lnFromLog.verify(null);
            assertTrue(ln.marshallOutsideWriteLatch());

            FileSummaryLN fsLN = new FileSummaryLN(new FileSummary());
            FileSummaryLN fsLNFromLog = new FileSummaryLN();
            writeAndRead(buffer, fsLN, fsLNFromLog);
            assertFalse(fsLN.marshallOutsideWriteLatch());

            /*
             * Keys
             */
            data = "abcdef";
            Key key = new Key(data.getBytes());
            writeAndRead(buffer, key, new Key());

            /*
             * INs
             */
            IN in = new IN(database,
                           new Key(new byte[]{1,0,1,0}),
                           7, 5);
            in.insertEntry(new ChildReference(null,
                                              new Key(new byte[] {1,0,1,0}),
                                              DbLsn.makeLsn(12, 200)));
            in.insertEntry(new ChildReference(null,
                                              new Key(new byte[] {1,1,1,0}),
                                              DbLsn.makeLsn(29, 300)));
            in.insertEntry(new ChildReference(null,
                                              new Key(new byte[] {0,0,1,0}),
                                              DbLsn.makeLsn(35, 400)));

            // write it
            IN inFromLog = new IN();
            writeAndRead(buffer, in, inFromLog);

            /*
             * BINs
             */
            BIN bin = new BIN(database,
                              new Key(new byte[]{3,2,1}),
                              8, 5);
            bin.latch();
            bin.insertEntry(new ChildReference(null,
                                               new Key(new byte[] {1,0,1,0}),
                                               DbLsn.makeLsn(212, 200)));
            bin.insertEntry(new ChildReference(null,
                                               new Key(new byte[] {1,1,1,0}),
                                               DbLsn.makeLsn(229, 300)));
            bin.insertEntry(new ChildReference(null,
                                               new Key(new byte[] {0,0,1,0}),
                                               DbLsn.makeLsn(235, 400)));
            BIN binFromLog = new BIN();
            writeAndRead(buffer, bin, binFromLog);
            binFromLog.verify(null);
            bin.releaseLatch();

            /*
             * DINs
             */
            DIN din = new DIN(database,
                              new Key(new byte[]{1,0,0,1}),
                              7,
                              new Key(new byte[]{0,1,1,0}),
                              new ChildReference(null,
                                                 new Key(new byte[]{1,0,0,1}),
                                                 DbLsn.makeLsn(10, 100)),
                              5);
            din.insertEntry(new ChildReference(null,
                                               new Key(new byte[] {1,0,1,0}),
                                               DbLsn.makeLsn(12, 200)));
            din.insertEntry(new ChildReference(null,
                                               new Key(new byte[] {1,1,1,0}),
                                               DbLsn.makeLsn(29, 300)));
            din.insertEntry(new ChildReference(null,
                                               new Key(new byte[] {0,0,1,0}),
                                               DbLsn.makeLsn(35, 400)));

            // write it
            DIN dinFromLog = new DIN();
            writeAndRead(buffer, din, dinFromLog);

            /*
             * DBINs
             */
            DBIN dbin = new DBIN(database,
                                 new Key(new byte[]{3,2,1}),
                                 8,
                                 new Key(new byte[]{1,2,3}),
                                 5);
            dbin.latch();
            dbin.insertEntry(new ChildReference(null,
                                                new Key(new byte[] {1,0,1,0}),
                                                DbLsn.makeLsn(212, 200)));
            dbin.insertEntry(new ChildReference(null,
                                                new Key(new byte[] {1,1,1,0}),
                                                DbLsn.makeLsn(229, 300)));
            dbin.insertEntry(new ChildReference(null,
                                                new Key(new byte[] {0,0,1,0}),
                                                DbLsn.makeLsn(235, 400)));
            DBIN dbinFromLog = new DBIN();
            writeAndRead(buffer, dbin, dbinFromLog);
            dbinFromLog.verify(null);
            dbin.releaseLatch();

            /*
             * Root
             */
            DbTree dbTree = new DbTree(env);
            DbTree dbTreeFromLog = new DbTree();
            writeAndRead(buffer, dbTree, dbTreeFromLog);

            /*
             * MapLN
             */
            MapLN mapLn = new MapLN(database);
            MapLN mapLnFromLog = new MapLN();
            writeAndRead(buffer, mapLn, mapLnFromLog);

            /*
             * UserTxn
             */

            /* Disabled for now because these txns don't compare equal,
             * because one has a name of "main" and the other has a name of
             * null because it was read from the log.

            Txn txn = new Txn(env, new TransactionConfig());
            Txn txnFromLog = new Txn();
            writeAndRead(buffer, txn, txnFromLog);
            txn.commit();
            */


            /*
             * Locker Commit
             */
            TxnCommit commit = new TxnCommit(111, DbLsn.makeLsn(10, 10));
            TxnCommit commitFromLog = new TxnCommit();
            writeAndRead(buffer, commit, commitFromLog);

            /*
             * Locker Abort
             */
            TxnAbort abort = new TxnAbort(111, DbLsn.makeLsn(11, 11));
            TxnAbort abortFromLog = new TxnAbort();
            writeAndRead(buffer, abort, abortFromLog);

            /*
             * IN delete info
             */
            INDeleteInfo info = new INDeleteInfo(77, new Key(new byte[1]),
                                                 new DatabaseId(100));
            INDeleteInfo infoFromLog = new INDeleteInfo();
            writeAndRead(buffer, info, infoFromLog);

            /*
             * Checkpoint start
             */
            CheckpointStart start = new CheckpointStart(177, "test");
            CheckpointStart startFromLog = new CheckpointStart();
            writeAndRead(buffer, start, startFromLog);

            /*
             * Checkpoint end
             */
            CheckpointEnd end =
                new CheckpointEnd("test",
                                  DbLsn.makeLsn(20, 55),
                                  env.getRootLsn(),
                                  env.getTxnManager().getFirstActiveLsn(),
                                  Node.getLastId(),
                                  env.getDbMapTree().getLastDbId(),
                                  env.getTxnManager().getLastTxnId(),
                                  177);
            CheckpointEnd endFromLog = new CheckpointEnd();
            writeAndRead(buffer, end, endFromLog);
        } catch (Throwable t) {
            t.printStackTrace();
            throw t;
        }
    }

    /**
     * Helper which takes a dbLoggable, writes it, reads it back and
     * checks for equality and size
     */
    private void writeAndRead(ByteBuffer buffer, LogWritable orig,
                              LogReadable fromLog)
        throws Exception  {
        // Write it.

        buffer.clear();
        orig.writeToLog(buffer);

        // Check the log size.
        buffer.flip();
        assertEquals(buffer.limit(), orig.getLogSize());

        // Read it and compare sizes. Note that we assume we're testing 
        // objects that are readable and writable to the log.
        fromLog.readFromLog(buffer);
        assertEquals(orig.getLogSize(), ((LogWritable)fromLog).getLogSize());

        assertEquals("We should have read the whole buffer for " +
                     fromLog.getClass().getName(), 
                     buffer.limit(), buffer.position());

        // Compare contents.
        StringBuffer sb1 = new StringBuffer();
        StringBuffer sb2 = new StringBuffer();
        ((LogReadable) orig).dumpLog(sb1, true);
        fromLog.dumpLog(sb2, true);

        if (DEBUG) {
            System.out.println("sb1 = " + sb1.toString());
            System.out.println("sb2 = " + sb2.toString());
        }
        assertEquals("Not equals for " +
                     fromLog.getClass().getName(),
                     sb1.toString(), sb2.toString());

        // Validate that the dump string is valid XML
        //        builder.parse("<?xml version=\"1.0\" ?>");
        //                      sb1.toString()+
    }
}
