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

package com.sleepycat.je.dbi;

import java.io.File;
import java.util.Iterator;
import java.util.SortedSet;

import junit.framework.TestCase;

import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.DbInternal;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.je.config.EnvironmentParams;
import com.sleepycat.je.evictor.Evictor;
import com.sleepycat.je.junit.JUnitThread;
import com.sleepycat.je.log.FileManager;
import com.sleepycat.je.tree.IN;
import com.sleepycat.je.util.TestUtils;

public class INListTest extends TestCase {
    private static String DB_NAME = "INListTestDb";
    private File envHome;
    private volatile int sequencer = 0;
    private Environment env;
    private EnvironmentImpl envImpl;
    private Database db;
    private DatabaseImpl dbImpl;
    private DatabaseConfig dbConfig;
    private INList inList1 = null;

    public INListTest() {
        envHome = new File(System.getProperty(TestUtils.DEST_DIR));
        dbConfig = new DatabaseConfig();
        dbConfig.setAllowCreate(true);

    }

    public void setUp()
        throws Exception {

        TestUtils.removeFiles("Setup", envHome, FileManager.JE_SUFFIX);
        sequencer = 0;
        EnvironmentConfig envConfig = new EnvironmentConfig();
        envConfig.setAllowCreate(true);
        envConfig.setConfigParam(EnvironmentParams.ENV_RUN_EVICTOR.getName(),
                                 "false");
        env = new Environment(envHome, envConfig);
        envImpl = DbInternal.envGetEnvironmentImpl(env);

        inList1 = new INList(envImpl);
        db = env.openDatabase(null, DB_NAME, dbConfig);
        dbImpl = DbInternal.dbGetDatabaseImpl(db);
    }

    public void tearDown()
	throws Exception {

        inList1 = null;
        db.close();
	env.close();
        TestUtils.removeFiles("TearDown", envHome, FileManager.JE_SUFFIX);
    }

    public void testMajorMinorLatching()
	throws Throwable {
        
        JUnitThread tester1 =
            new JUnitThread("testMajorMinorLatching-Thread1") {
                public void testBody() {

                    try {
                        /* Create two initial elements. */
                        for (int i = 0; i < 2; i++) {
                            IN in = new IN(dbImpl, null, 1, 1);
                            inList1.add(in);
                        }

                        /* 
                         * Acquire the major latch in preparation for an
                         * iteration.
                         */
                        inList1.latchMajor();

                        /* Wait for tester2 to try to acquire the
                           /* minor latch */
                        sequencer = 1;
                        while (sequencer <= 1) {
                            Thread.yield();
                        }

                        /* 
                         * Sequencer is now 2. There should only be
                         * two elements in the list right now even
                         * though thread 2 added a third one.
                         */
                        int count = 0;
                        Iterator iter = inList1.iterator();
                        while (iter.hasNext()) {
                            IN in = (IN) iter.next();
                            count++;
                        }

                        assertEquals(2, count);

                        /*
                         * Allow thread2 to run again.  It will
                         * add another element and throw control
                         * back to thread 1.
                         */
                        sequencer++;   // now it's 3
                        while (sequencer <= 3) {
                            Thread.yield();
                        }

                        /* 
                         * Thread2 has exited.  Release the major
                         * latch so that the addedINs can be added
                         * into the main in set.
                         */
                        inList1.releaseMajorLatch();

                        /*
                         * Check that the entry added by tester2 was really
                         * added.
                         */
                        inList1.latchMajor();
                        count = 0;
                        iter = inList1.iterator();
                        while (iter.hasNext()) {
                            IN in = (IN) iter.next();
                            count++;
                        }

                        assertEquals(4, count);
                        inList1.releaseMajorLatch();
                    } catch (Throwable T) {
                        T.printStackTrace(System.out);
                        fail("Thread 1 caught some Throwable: " + T);
                    }
                }
            };

        JUnitThread tester2 =
            new JUnitThread("testMajorMinorLatching-Thread2") {
                public void testBody() {

                    try {
                        /* Wait for tester1 to start */
                        while (sequencer < 1) {
                            Thread.yield();
                        }

                        assertEquals(1, sequencer);

                        /* 
                         * Acquire the minor latch in preparation for some
                         * concurrent additions.
                         */
                        inList1.add(new IN(dbImpl, null, 1, 1));
                        sequencer++;

                        /* Sequencer is now 2. */

                        while (sequencer < 3) {
                            Thread.yield();
                        }

                        assertEquals(3, sequencer);
                        /* Add one more element. */
                        inList1.add(new IN(dbImpl, null, 1, 1));
                        sequencer++;
                    } catch (Throwable T) {
                        T.printStackTrace(System.out);
                        fail("Thread 2 caught some Throwable: " + T);
                    }
                }
            };

        tester1.start();
        tester2.start();
        tester1.finishTest();
        tester2.finishTest();
    }

    public void xtestINSorting()
        throws DatabaseException {

        DatabaseImpl db0 = new DatabaseImpl("INListTestDB0",
					    new DatabaseId(0),
					    envImpl,
					    new DatabaseConfig());

        DatabaseImpl db1 = new DatabaseImpl("INListTestDB1",
					    new DatabaseId(1),
					    envImpl,
					    new DatabaseConfig());

        DatabaseImpl db2 = new DatabaseImpl("INListTestDB2",
					    new DatabaseId(2),
					    envImpl,
					    new DatabaseConfig());

        inList1.latchMajor();
        for (int i = 0; i < 10; i++) {
            IN in0 = new IN(db0, null, 1, i);
            IN in1 = new IN(db1, null, 1, i);
            IN in2 = new IN(db2, null, 1, i);
            inList1.add(in0);
            inList1.add(in1);
            inList1.add(in2);
        }

        /*
         * IN's must be returned with increasing level numbers and any
         * dbid.  Dbid 0 (the map DB) IN's must come last and in
         * increasing level order.
         */
        int prevLevel = Integer.MIN_VALUE;

        Iterator iter = inList1.iterator();
        int count = 0;
        while (iter.hasNext()) {
            IN in = (IN) iter.next();
            assertTrue(in.getLevel() >= prevLevel);
            prevLevel = in.getLevel();
            count++;
        }
        assertEquals(30, count);

        inList1.releaseMajorLatch();
    }

    public void testINSelection()
        throws DatabaseException {

        int[] sampleGenerations = {
            100, 200, 150, 160, 50, 70, 250, 180, 100, 40,
            220, 70, 60, 250, 330, 20, 60, 30, 10, 100
        };

        DatabaseImpl database = new DatabaseImpl("INListTestDB",
						 new DatabaseId(1),
						 envImpl,
						 new DatabaseConfig());

        /* Create the initial elements. */
        for (int i = 0; i < sampleGenerations.length; i++) {
            IN in = new IN(database, null, 1, 1);
            in.setGeneration(sampleGenerations[i]);
            inList1.add(in);
        }

        Evictor evictor =
	    new Evictor(DbInternal.envGetEnvironmentImpl(env),
                        "testEvictor", 100, 25);
        inList1.latchMajor();
        SortedSet evictList = evictor.selectINSet(inList1);
        assertEquals(5, evictList.size());
        Iterator iter = evictList.iterator();
        while (iter.hasNext()) {
            IN in = (IN) iter.next();
            if (in.getGeneration() > 50) {
                fail("wacky generation selected");
            }
        }
        inList1.releaseMajorLatch();
    }

    public void testMultipleINSelections()
        throws DatabaseException {

        int[] sampleGenerations = {
            1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
            10, 9, 8, 7, 6, 5, 4, 3, 2, 1,
            5, 4, 3, 2, 1, 6, 7, 8, 9, 10,
            4, 3, 2, 6, 5, 7, 8, 9, 1, 10,
            3, 4, 6, 2, 7, 5, 9, 8, 10, 1,
            4, 3, 2, 7, 6, 9, 5, 10, 8, 1,
            4, 2, 3, 6, 7, 5, 10, 9, 1, 8,
            1, 4, 6, 3, 5, 7, 9, 10, 8, 2,
            1, 6, 4, 9, 7, 3, 5, 8, 10, 2,
            1, 6, 4, 9, 10, 3, 5, 8, 7, 2,
        };

        DatabaseImpl database = new DatabaseImpl("INListTestDB",
						 new DatabaseId(1),
						 envImpl,
						 new DatabaseConfig());

        /* Create the initial elements. */
        for (int i = 0; i < sampleGenerations.length; i++) {
            IN in = new IN(database, null, 1, 1);
            in.setGeneration(sampleGenerations[i]);
            inList1.add(in);
        }

        Evictor evictor =
	    new Evictor(DbInternal.envGetEnvironmentImpl(env),
                        "testEvictor", 10, 50);
        inList1.latchMajor();
        /* divide by 5 so that we go through the list twice. */
        for (int i = 0; i < sampleGenerations.length / 5; i++) {
            SortedSet evictList = evictor.selectINSet(inList1);
            assertEquals(5, evictList.size());
            Iterator iter = evictList.iterator();
            int count = 0;
            while (iter.hasNext()) {
                IN in = (IN) iter.next();
                assertEquals(++count, in.getGeneration());
            }
        }
        inList1.releaseMajorLatch();
    }
}
