/*-
 * See the file LICENSE for redistribution information.
 *
 * Copyright (c) 2002-2003
 *	Sleepycat Software.  All rights reserved.
 *
 * $Id: KeyRangeTest.java,v 1.9 2003/10/18 19:51:27 mhayes Exp $
 */

package com.sleepycat.bdb.test;

import com.sleepycat.bdb.bind.ByteArrayBinding;
import com.sleepycat.bdb.bind.ByteArrayFormat;
import com.sleepycat.db.Db;
import com.sleepycat.db.DbEnv;
import com.sleepycat.db.DbException;
import com.sleepycat.bdb.DataCursor;
import com.sleepycat.bdb.DataStore;
import com.sleepycat.bdb.DataView;
import java.io.File;
import java.io.IOException;
import java.math.BigInteger;
import java.util.Arrays;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;

/**
 * @author Mark Hayes
 */
public class KeyRangeTest extends TestCase {

    private static boolean VERBOSE = false;

    private DbEnv env;
    private DataStore store;
    private DataView view;
    private DataCursor cursor;

    public static void main(String[] args)
        throws Exception {

        junit.textui.TestRunner.run(suite());
    }

    public static Test suite() {

        TestSuite suite = new TestSuite();
        suite.addTest(new KeyRangeTest());
        return suite;
    }

    public KeyRangeTest() {

        super("all-tests");
    }

    public void setUp()
        throws Exception {

        System.out.println(DbTestUtil.qualifiedTestName(this));
        File dir = DbTestUtil.getNewDir();
        ByteArrayFormat dataFormat = new ByteArrayFormat();
        ByteArrayBinding dataBinding = new ByteArrayBinding(dataFormat);

        int envFlags = Db.DB_INIT_MPOOL | Db.DB_CREATE;
        env = new DbEnv(0);
        env.open(dir.getAbsolutePath(), envFlags, 0);
        Db db = new Db(env, 0);
        db.open(null, "test.db", null, Db.DB_BTREE, Db.DB_CREATE, 0);
        store = new DataStore(db, dataFormat, dataFormat, null);
        view = new DataView(store, null, dataBinding, dataBinding, null, true);
    }

    public void tearDown()
        throws Exception {

        store.close();
        env.close(0);
    }

    public void runTest() throws Exception {

        final byte FF = (byte) 0xFF;

        byte[][] keyBytes = {
            /* 0 */ {1},
            /* 1 */ {FF},
            /* 2 */ {FF, 0},
            /* 3 */ {FF, 0x7F},
            /* 4 */ {FF, FF},
            /* 5 */ {FF, FF, 0},
            /* 6 */ {FF, FF, 0x7F},
            /* 7 */ {FF, FF, FF},
        };
        byte[][] keys = new byte[keyBytes.length][];
        final int end = keyBytes.length - 1;
        for (int i = 0; i <= end; i++) {
            keys[i] = keyBytes[i];
            view.put(keys[i], keyBytes[i], 0, null);
        }
        byte[][] extremeKeyBytes = {
            /* 0 */ {0},
            /* 1 */ {FF, FF, FF, FF},
        };
        byte[][] extremeKeys = new byte[extremeKeyBytes.length][];
        for (int i = 0; i < extremeKeys.length; i++) {
            extremeKeys[i] = extremeKeyBytes[i];
        }

        // with empty range

        cursor = new DataCursor(view, false);
        expectRange(keyBytes, 0, end);
        cursor.close();

        // begin key only, inclusive

        for (int i = 0; i <= end; i++) {
            cursor = new DataCursor(view, false, keys[i], true, null, false);
            expectRange(keyBytes, i, end);
            cursor.close();
        }

        // begin key only, exclusive

        for (int i = 0; i <= end; i++) {
            cursor = new DataCursor(view, false, keys[i], false, null, false);
            expectRange(keyBytes, i + 1, end);
            cursor.close();
        }

        // end key only, inclusive

        for (int i = 0; i <= end; i++) {
            cursor = new DataCursor(view, false, null, false, keys[i], true);
            expectRange(keyBytes, 0, i);
            cursor.close();
        }

        // end key only, exclusive

        for (int i = 0; i <= end; i++) {
            cursor = new DataCursor(view, false, null, false, keys[i], false);
            expectRange(keyBytes, 0, i - 1);
            cursor.close();
        }

        // begin and end keys, inclusive and exclusive

        for (int i = 0; i <= end; i++) {
            for (int j = i; j <= end; j++) {
                // begin inclusive, end inclusive

                cursor = new DataCursor(view, false, keys[i], true, keys[j],
                                        true);
                expectRange(keyBytes, i, j);
                cursor.close();

                // begin inclusive, end exclusive

                cursor = new DataCursor(view, false, keys[i], true, keys[j],
                                        false);
                expectRange(keyBytes, i, j - 1);
                cursor.close();

                // begin exclusive, end inclusive

                cursor = new DataCursor(view, false, keys[i], false, keys[j],
                                        true);
                expectRange(keyBytes, i + 1, j);
                cursor.close();

                // begin exclusive, end exclusive

                cursor = new DataCursor(view, false, keys[i], false, keys[j],
                                        false);
                expectRange(keyBytes, i + 1, j - 1);
                cursor.close();
            }
        }

        // single key range

        for (int i = 0; i <= end; i++) {
            cursor = new DataCursor(view, false, keys[i]);
            expectRange(keyBytes, i, i);
            cursor.close();
        }

        // start with lower extreme (before any existing key)

        cursor = new DataCursor(view, false, extremeKeys[0], true, null,                                        false);
        expectRange(keyBytes, 0, end);
        cursor.close();

        // start with higher extreme (after any existing key)

        cursor = new DataCursor(view, false, null, false, extremeKeys[1],                                       true);
        expectRange(keyBytes, 0, end);
        cursor.close();
    }

    private void expectRange(byte[][] bytes, int first, int last)
        throws DbException, IOException {

        for (int pos = Db.DB_FIRST, i = first;; i++, pos = Db.DB_NEXT) {
            if (checkRange(bytes, first, last, i <= last, true, pos, i))
                break;
        }

        for (int pos = Db.DB_LAST, i = last;; i--, pos = Db.DB_PREV) {
            if (checkRange(bytes, first, last, i >= first, false, pos, i))
                break;
        }
    }

    private boolean checkRange(byte[][] bytes, int first, int last,
                               boolean inRange, boolean forward, int pos,
                               int i)
        throws DbException, IOException {

        int err = cursor.get(null, null, pos, false);

        // check that moving past ends doesn't move the cursor
        if (err == 0 && i == first) {
            int e2 = cursor.get(null, null, Db.DB_PREV, false);
            assertEquals(Db.DB_NOTFOUND, e2);
        }
        if (err == 0 && i == last) {
            int e2 = cursor.get(null, null, Db.DB_NEXT, false);
            assertEquals(Db.DB_NOTFOUND, e2);
        }

        byte[] val = (err == 0) ? ((byte[]) cursor.getCurrentValue()) : null;

        String msg = " " + (forward ? "fwd" : "rev") + " i=" + i +
                     " first=" + first + " last=" + last;

        if (inRange) {
            assertNotNull("RangeNotFound" + msg, val);

            if (!Arrays.equals(val, bytes[i])){
                printBytes(val);
                printBytes(bytes[i]);
                fail("RangeKeyNotEqual" + msg);
            }
            if (VERBOSE) System.out.println("GotRange" + msg);
            return false;
        } else {
            assertEquals("RangeExceeded" + msg, Db.DB_NOTFOUND, err);
            return true;
        }
    }

    private void printBytes(byte[] bytes) {

        System.out.println(new BigInteger(bytes).toString());
    }
}
