package voldemort.store.readonly;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;

import voldemort.VoldemortException;
import voldemort.utils.ByteUtils;

/**
 * A base test for testing search strategies for the read-only store
 * 
 * 
 */
@RunWith(Parameterized.class)
public class SearchStrategyTest {

    private SearchStrategy strategy;
    private int keyHashSize;

    @Parameters
    public static Collection<Object[]> configs() {
        return Arrays.asList(new Object[][] { { new BinarySearchStrategy(), 4 },
                { new InterpolationSearchStrategy(), 4 }, { new BinarySearchStrategy(), 16 },
                { new InterpolationSearchStrategy(), 16 } });
    }

    public SearchStrategyTest(SearchStrategy strategy, int keyHashSize) {
        this.strategy = strategy;
        this.keyHashSize = keyHashSize;
    }

    @Test
    public void findNothingInEmptyIndex() {
        ByteBuffer index = makeIndex(new byte[][] {}, new int[] {});
        assertKeysNotFound(index, key(-1, 2, -1, 2), key(9, 9, 9, 9));
    }

    @Test
    public void testSingleton() {
        byte[] theKey = key(1, 2, 1, 2);
        int theVal = 42;
        ByteBuffer index = makeIndex(new byte[][] { theKey }, new int[] { theVal });
        assertKeyFound(index, theKey, theVal);
        assertKeysNotFound(index, key(-1, 2, -1, 2), key(9, 9, 9, 9));
    }

    @Test
    public void keyOutOfRange() {
        byte[] startKey = key(0, 1, 0, 1);
        byte[] endKey = key(Integer.MIN_VALUE, 0, 0, 0);
        ByteBuffer index = makeIndex(new byte[][] { startKey, endKey }, new int[] { 1, 2 });
        assertKeyFound(index, startKey, 1);
        assertKeyFound(index, endKey, 2);
        assertKeysNotFound(index, key(-2, 0, 0, 0), key(-1, 1, 0, 0));
    }

    @Test
    public void locationIsEndPoint() {
        byte[] startKey = key(0, 0, 0, 0);
        byte[] middleKey = key(5, 5, 5, 5);
        byte[] endKey = key(Integer.MIN_VALUE,
                            Integer.MIN_VALUE,
                            Integer.MIN_VALUE,
                            Integer.MIN_VALUE);
        ByteBuffer index = makeIndex(new byte[][] { startKey, middleKey, endKey }, new int[] { 1,
                2, 3 });
        assertKeyFound(index, startKey, 1);
        assertKeyFound(index, middleKey, 2);
        assertKeyFound(index, endKey, 3);
        assertKeysNotFound(index, key(-1, 2, -1, 2), key(9, 9, 9, 9));
    }

    @Test
    public void testRandomValues() {
        Random rand = new Random(48534543);
        int size = 100;
        for(int iter = 0; iter < 10; iter++) {
            byte[][] keys = new byte[size][];
            int[] values = new int[size];
            for(int i = 0; i < size; i++) {
                byte[] key = new byte[keyHashSize];
                rand.nextBytes(key);
                values[i] = rand.nextInt(1000000);
                keys[i] = key;
            }
            ByteBuffer index = makeIndex(keys, values);
            // test finding keys
            for(int i = 0; i < size; i++)
                assertKeyFound(index, keys[i], values[i]);
            for(int i = 0; i < 10; i++) {
                byte[] key = new byte[keyHashSize];
                assertKeysNotFound(index, key);
            }

        }
    }

    public void print(byte[][] keys, int[] positions) {
        for(int i = 0; i < keys.length; i++) {
            System.out.println(ByteUtils.toHexString(keys[i]) + "\t" + positions[i]);
        }
    }

    public void print(ByteBuffer buffer, int size) {
        buffer.position(0);
        for(int i = 0; i < size; i++) {
            byte[] key = new byte[16];
            buffer.get(key);
            int position = buffer.getInt();
            System.out.println(ByteUtils.toHexString(key) + "\t" + position);
        }
    }

    public ByteBuffer makeIndex(byte[][] keys, int[] positions) {
        assertEquals(keys.length, positions.length);
        Map<byte[], Integer> m = new HashMap<byte[], Integer>();
        for(int i = 0; i < keys.length; i++)
            m.put(keys[i], positions[i]);
        byte[][] copy = keys.clone();
        Arrays.sort(copy, new Comparator<byte[]>() {

            public int compare(byte[] b1, byte[] b2) {
                return ByteUtils.compare(b1, b2);
            }
        });
        ByteBuffer buffer = ByteBuffer.allocate((keyHashSize + ByteUtils.SIZE_OF_INT) * copy.length);
        for(int i = 0; i < copy.length; i++) {
            buffer.put(copy[i]);
            buffer.putInt(m.get(copy[i]));
        }
        return buffer;
    }

    public byte[] key(int v1, int v2, int v3, int v4) {
        byte[] bytes = new byte[keyHashSize];
        switch(keyHashSize) {
            case 16:
                ByteUtils.writeInt(bytes, v1, 0);
                ByteUtils.writeInt(bytes, v2, 4);
                ByteUtils.writeInt(bytes, v3, 8);
                ByteUtils.writeInt(bytes, v4, 12);
                break;
            case 4:
                ByteUtils.writeInt(bytes, v1, 0);
                break;
            default:
                throw new VoldemortException("Key hash size " + keyHashSize + " not supported");

        }

        return bytes;
    }

    public void assertKeyFound(ByteBuffer buffer, byte[] key, int expected) {
        int found = strategy.indexOf(buffer, key, buffer.limit());
        assertTrue("Failed to find correct key " + key, found != -1);
        assertEquals(expected, found);
    }

    public void assertKeysNotFound(ByteBuffer buffer, byte[]... keys) {
        for(byte[] key: keys) {
            assertTrue("Failed to find key " + key,
                       strategy.indexOf(buffer, key, buffer.limit()) == -1);
        }
    }

}
