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

package com.sleepycat.je.recovery;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import junit.framework.TestCase;

import com.sleepycat.bind.tuple.IntegerBinding;
import com.sleepycat.je.Cursor;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.DbInternal;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.je.OperationStatus;
import com.sleepycat.je.config.EnvironmentParams;
import com.sleepycat.je.util.TestUtils;

public class CheckBase extends TestCase {

    private static final boolean DEBUG = false;
    private Set expected;
    private Set found;

    protected File envHome;
    Environment env;

    public CheckBase() {
        envHome = new File(System.getProperty(TestUtils.DEST_DIR));
    }

    public void setUp()
        throws IOException {

        TestUtils.removeLogFiles("Setup", envHome, false);
    }
    
    public void tearDown()
        throws Exception {

	TestUtils.removeLogFiles("TearDown", envHome, false);
    }

    protected void testOneCase(String dbName,
                               EnvironmentConfig startEnvConfig,
                               DatabaseConfig startDbConfig,
                               TestGenerator testGen,
                               EnvironmentConfig validateEnvConfig,
                               DatabaseConfig validateDbConfig)
        throws Throwable {

        try {
            /* Create an environment. */
            env = new Environment(envHome, startEnvConfig);
            Database db = env.openDatabase(null, dbName, startDbConfig);

            /* Generate test data. */
            testGen.generateData(db);

            /* Scan the database to save what values we should have. */
            loadExpectedData(db);

            /* Close w/out checkpointing. */
            db.close();
            DbInternal.envGetEnvironmentImpl(env).close(false);
            env = null;

            /* Recover and load what's in the database post-recovery. */
            recoverAndLoadData(validateEnvConfig,
            		       validateDbConfig,
			       dbName);

            /* Check the pre and post recovery data. */
            validate();
        } catch (Throwable t) {
            /* Dump stack trace before trying to tear down. */
            t.printStackTrace();
            throw t;
        }
    }

    protected void turnOffEnvDaemons(EnvironmentConfig envConfig) {
        envConfig.setConfigParam(EnvironmentParams.ENV_RUN_CLEANER.getName(),
                                 "false");
        envConfig.setConfigParam(EnvironmentParams.
                                 ENV_RUN_CHECKPOINTER.getName(),
                                 "false");
        envConfig.setConfigParam(EnvironmentParams.ENV_RUN_EVICTOR.getName(),
                                 "false");
        envConfig.setConfigParam(EnvironmentParams.
                                 ENV_RUN_INCOMPRESSOR.getName(),
                                 "false");
    }

    /**
     * Re-open the environment and load all data present, to compare to the
     * data set of expected values.
     */
    protected void recoverAndLoadData(EnvironmentConfig envConfig,
                                      DatabaseConfig dbConfig,
                                      String dbName)
        throws DatabaseException {

        env = new Environment(envHome, envConfig);
        Database db = env.openDatabase(null, dbName, dbConfig);
        found = new HashSet();

        Cursor cursor = db.openCursor(null, null);
        DatabaseEntry key = new DatabaseEntry();
        DatabaseEntry data = new DatabaseEntry();

        int i = 0;
        try {
            while (cursor.getNext(key, data, null) ==
                   OperationStatus.SUCCESS) {
                TestData t = new TestData(key, data);
                if (DEBUG) {
                    System.out.println("found k=" +
                                       IntegerBinding.entryToInt(key) +
                                       " d=" +
                                       IntegerBinding.entryToInt(data));
                }
                found.add(t);
            }
        }
        finally {
            cursor.close();
        }

        db.close();
        env.close();
    }

    /*
     * The found and expected data sets need to match exactly after recovery.
     */
    protected void validate() 
        throws DatabaseException {

        assertEquals("expected and found set sizes don't match" ,
                     expected.size(), found.size());

        Iterator foundIter = found.iterator();
        while (foundIter.hasNext()) {
            TestData t = (TestData) foundIter.next();
            assertTrue("Missing " + t + "from the expected set",
                       expected.remove(t));
        }

        assertEquals("Expected has " + expected.size() + " items remaining",
                     0, expected.size());
    }

    protected void putTestData(Database db,
                             DatabaseEntry key,
                             DatabaseEntry data)
        throws DatabaseException {

        assertEquals(OperationStatus.SUCCESS, db.put(null, key, data));
    }

    private void loadExpectedData(Database db) 
        throws DatabaseException {
        
        expected = new HashSet();

        Cursor cursor = db.openCursor(null, null);
        DatabaseEntry key = new DatabaseEntry();
        DatabaseEntry data = new DatabaseEntry();

        try {
            while (cursor.getNext(key, data, null) ==
                   OperationStatus.SUCCESS) {
                if (DEBUG) {
                    System.out.println("expect k=" +
                                       IntegerBinding.entryToInt(key) +
                                       " d=" +
                                       IntegerBinding.entryToInt(data));
                }
                TestData t = new TestData(key, data);
                expected.add(t);
            }
        }
        finally {
            cursor.close();
        }
    }

    private void dumpData(Database db) 
        throws DatabaseException {

        Cursor cursor = db.openCursor(null, null);
        DatabaseEntry key = new DatabaseEntry();
        DatabaseEntry data = new DatabaseEntry();
        int i = 0;
        try {
            while (cursor.getNext(key, data, null) ==
                   OperationStatus.SUCCESS) {
                TestData t = new TestData(key, data);
                System.out.println(t);
                i++;
            }
        }
        finally {
            cursor.close();
        }
        System.out.println("scanned=" + i);
    }

    private void dumpExpected() {
        Iterator iter = expected.iterator();
        System.out.println("expected size=" + expected.size());
        while (iter.hasNext()) {
            System.out.println((TestData)iter.next());
        }
    }

    protected class TestData {
        private DatabaseEntry key;
        private DatabaseEntry data;

        TestData(DatabaseEntry key, DatabaseEntry data) {
            this.key = new DatabaseEntry(key.getData());
            this.data = new DatabaseEntry(data.getData());
        }

        public boolean equals(Object o ) {
            if (this == o)
                return true;
            if (!(o instanceof TestData))
                return false;

            TestData other = (TestData) o;
            if (Arrays.equals(key.getData(), other.key.getData()) &&
                Arrays.equals(data.getData(), other.data.getData())) {
                return true;
            } else 
                return false;
        }

        public String toString() {
            return  " k=" + IntegerBinding.entryToInt(key) +
                    " d=" + IntegerBinding.entryToInt(data);
        }

        public int hashCode() {
            return toString().hashCode();
        }
    }

    /* 
     * Each unit test overrides the generateData method. Don't make this
     * abstract, because we may want different unit tests to call different
     * flavors of generateData(), and we want a default implementation for each
     * flavor.
     */
    protected class TestGenerator {
        void generateData(Database db) 
            throws Exception {

        }
    }
}
