package org.codelibs.minhash;

import java.io.IOException;

import org.apache.lucene.analysis.Analyzer;

import junit.framework.TestCase;

public class MinHashTest extends TestCase {

    public void test_usercase1() throws IOException {
        String text = "Fess is very powerful and easily deployable Enterprise Search Server.";

        // The number of bits for each hash value.
        int hashBit = 1;
        // A base seed for hash functions.
        int seed = 0;
        // The number of hash functions.
        int num = 128;
        // Analyzer for 1-bit 128 hash.
        Analyzer analyzer = MinHash.createAnalyzer(hashBit, seed,
                num);

        // Calculate a minhash value. The size is hashBit*num.
        byte[] minhash = MinHash.calculate(analyzer, text);

        assertEquals(
                "00101010000100110011000101100000001101011101111010100010111101101000000011100010100100001111110110011101111101001010001110010101",
                MinHash.toBinaryString(minhash));

        // Compare a similar text.
        String text1 = "Fess is very powerful and easily deployable Search Server.";
        byte[] minhash1 = MinHash.calculate(analyzer, text1);
        assertEquals(0.953125f, MinHash.compare(minhash, minhash1));

        // Compare a different text.
        String text2 = "Solr is the popular, blazing fast open source enterprise search platform";
        byte[] minhash2 = MinHash.calculate(analyzer, text2);
        assertEquals(0.453125f, MinHash.compare(minhash, minhash2));

    }

    public void test_calculate_1bit_128funcs_seed0() throws IOException {

        final int hashBit = 1;
        final int seed = 0;
        final int num = 128;
        final Analyzer minhashAnalyzer = MinHash.createAnalyzer(hashBit,
                seed, num);
        final StringBuilder[] texts = createTexts();
        final byte[][] data = createMinHashes(minhashAnalyzer, texts);

        assertEquals(1.0f, MinHash.compare(data[0], data[0]));
        assertEquals(0.890625f, MinHash.compare(data[0], data[1]));
        assertEquals(0.7890625f, MinHash.compare(data[0], data[2]));
        assertEquals(0.7421875f, MinHash.compare(data[0], data[3]));
        assertEquals(0.6953125f, MinHash.compare(data[0], data[4]));
        assertEquals(0.609375f, MinHash.compare(data[0], data[5]));
        assertEquals(0.578125f, MinHash.compare(data[0], data[6]));
        assertEquals(0.546875f, MinHash.compare(data[0], data[7]));
        assertEquals(0.546875f, MinHash.compare(data[0], data[8]));
        assertEquals(0.5625f, MinHash.compare(data[0], data[9]));
    }

    public void test_calculate_1bit_128funcs_seed100() throws IOException {

        final int hashBit = 1;
        final int seed = 100;
        final int num = 128;
        final Analyzer minhashAnalyzer = MinHash.createAnalyzer(hashBit,
                seed, num);
        final StringBuilder[] texts = createTexts();
        final byte[][] data = createMinHashes(minhashAnalyzer, texts);

        assertEquals(1.0f, MinHash.compare(data[0], data[0]));
        assertEquals(0.9296875f, MinHash.compare(data[0], data[1]));
        assertEquals(0.8515625f, MinHash.compare(data[0], data[2]));
        assertEquals(0.8046875f, MinHash.compare(data[0], data[3]));
        assertEquals(0.7265625f, MinHash.compare(data[0], data[4]));
        assertEquals(0.640625f, MinHash.compare(data[0], data[5]));
        assertEquals(0.640625f, MinHash.compare(data[0], data[6]));
        assertEquals(0.5703125f, MinHash.compare(data[0], data[7]));
        assertEquals(0.53125f, MinHash.compare(data[0], data[8]));
        assertEquals(0.484375f, MinHash.compare(data[0], data[9]));
    }

    public void test_calculate_2bit_128funcs_seed0() throws IOException {

        final int hashBit = 2;
        final int seed = 0;
        final int num = 128;
        final Analyzer minhashAnalyzer = MinHash.createAnalyzer(hashBit,
                seed, num);
        final StringBuilder[] texts = createTexts();
        final byte[][] data = createMinHashes(minhashAnalyzer, texts);

        assertEquals(1.0f, MinHash.compare(data[0], data[0]));
        assertEquals(0.89453125f, MinHash.compare(data[0], data[1]));
        assertEquals(0.80859375f, MinHash.compare(data[0], data[2]));
        assertEquals(0.7734375f, MinHash.compare(data[0], data[3]));
        assertEquals(0.7265625f, MinHash.compare(data[0], data[4]));
        assertEquals(0.66015625f, MinHash.compare(data[0], data[5]));
        assertEquals(0.625f, MinHash.compare(data[0], data[6]));
        assertEquals(0.59765625f, MinHash.compare(data[0], data[7]));
        assertEquals(0.5859375f, MinHash.compare(data[0], data[8]));
        assertEquals(0.55078125f, MinHash.compare(data[0], data[9]));
    }

    public void test_calculate_1bit_256funcs_seed0() throws IOException {

        final int hashBit = 1;
        final int seed = 0;
        final int num = 256;
        final Analyzer minhashAnalyzer = MinHash.createAnalyzer(hashBit,
                seed, num);
        final StringBuilder[] texts = createTexts();
        final byte[][] data = createMinHashes(minhashAnalyzer, texts);

        assertEquals(1.0f, MinHash.compare(data[0], data[0]));
        assertEquals(0.90625f, MinHash.compare(data[0], data[1]));
        assertEquals(0.82421875f, MinHash.compare(data[0], data[2]));
        assertEquals(0.76953125f, MinHash.compare(data[0], data[3]));
        assertEquals(0.703125f, MinHash.compare(data[0], data[4]));
        assertEquals(0.625f, MinHash.compare(data[0], data[5]));
        assertEquals(0.6015625f, MinHash.compare(data[0], data[6]));
        assertEquals(0.55078125f, MinHash.compare(data[0], data[7]));
        assertEquals(0.53125f, MinHash.compare(data[0], data[8]));
        assertEquals(0.51171875f, MinHash.compare(data[0], data[9]));
    }

    public void test_calculate_1bit_128funcs_seed0_moreSimilar()
            throws IOException {

        final int hashBit = 1;
        final int seed = 0;
        final int num = 128;
        final Analyzer minhashAnalyzer = MinHash.createAnalyzer(hashBit,
                seed, num);
        final StringBuilder[] texts = createMoreSimilarTexts();
        final byte[][] data = createMinHashes(minhashAnalyzer, texts);

        assertEquals(1.0f, MinHash.compare(data[0], data[0]));
        assertEquals(0.984375f, MinHash.compare(data[0], data[1]));
        assertEquals(0.9765625f, MinHash.compare(data[0], data[2]));
        assertEquals(0.96875f, MinHash.compare(data[0], data[3]));
        assertEquals(0.953125f, MinHash.compare(data[0], data[4]));
        assertEquals(0.9375f, MinHash.compare(data[0], data[5]));
        assertEquals(0.9296875f, MinHash.compare(data[0], data[6]));
        assertEquals(0.921875f, MinHash.compare(data[0], data[7]));
        assertEquals(0.921875f, MinHash.compare(data[0], data[8]));
        assertEquals(0.921875f, MinHash.compare(data[0], data[9]));
    }

    private byte[][] createMinHashes(final Analyzer minhashAnalyzer,
            final StringBuilder[] texts) throws IOException {
        final byte[][] data = new byte[10][];
        for (int i = 0; i < 10; i++) {
            // System.out.println("texts" + i + ": " + texts[i]);
            data[i] = MinHash.calculate(minhashAnalyzer, texts[i].toString());
        }
        return data;
    }

    private StringBuilder[] createTexts() {
        final StringBuilder[] texts = new StringBuilder[10];
        for (int i = 0; i < 10; i++) {
            texts[i] = new StringBuilder();
        }
        for (int i = 0; i < 100; i++) {
            for (int j = 0; j < 10; j++) {
                if (i - j * 10 >= 0) {
                    texts[j].append(" aaa" + i);
                } else {
                    texts[j].append(" bbb" + i);
                }
            }
        }
        return texts;
    }

    private StringBuilder[] createMoreSimilarTexts() {
        final StringBuilder[] texts = new StringBuilder[10];
        for (int i = 0; i < 10; i++) {
            texts[i] = new StringBuilder();
        }
        for (int i = 0; i < 100; i++) {
            for (int j = 0; j < 10; j++) {
                if (i - 90 - j >= 0) {
                    texts[j].append(" aaa" + i);
                } else {
                    texts[j].append(" bbb" + i);
                }
            }
        }
        return texts;
    }

    public void test_compare() {
        assertEquals(1f,
                MinHash.compare(new byte[] { 0x1 }, new byte[] { 0x1 }));
        assertEquals(1f, MinHash.compare(new byte[] { 0x1, 0x1 }, new byte[] {
                0x1, 0x1 }));

        assertEquals(0.5f,
                MinHash.compare(new byte[] { 0xf }, new byte[] { 0x0 }));
        assertEquals(0.5f, MinHash.compare(new byte[] { 0xf, 0x0 }, new byte[] {
                0x0, 0xf }));

        assertEquals(0.0f,
                MinHash.compare(new byte[] { 0xf }, new byte[] { (byte) 0xf0 }));
        assertEquals(
                0.0f,
                MinHash.compare(new byte[] { 0xf, (byte) 0xf0 }, new byte[] {
                        (byte) 0xf0, 0xf }));
    }

    public void test_countSameBits() {
        assertEquals(8,
                MinHash.countSameBits(new byte[] { 0x0 }, new byte[] { 0x0 }));
        assertEquals(7,
                MinHash.countSameBits(new byte[] { 0x1 }, new byte[] { 0x0 }));
        assertEquals(7,
                MinHash.countSameBits(new byte[] { 0x2 }, new byte[] { 0x0 }));
        assertEquals(6,
                MinHash.countSameBits(new byte[] { 0x3 }, new byte[] { 0x0 }));
        assertEquals(7,
                MinHash.countSameBits(new byte[] { 0x4 }, new byte[] { 0x0 }));
        assertEquals(6,
                MinHash.countSameBits(new byte[] { 0x5 }, new byte[] { 0x0 }));
        assertEquals(6,
                MinHash.countSameBits(new byte[] { 0x6 }, new byte[] { 0x0 }));
        assertEquals(5,
                MinHash.countSameBits(new byte[] { 0x7 }, new byte[] { 0x0 }));
        assertEquals(7,
                MinHash.countSameBits(new byte[] { 0x8 }, new byte[] { 0x0 }));
        assertEquals(6,
                MinHash.countSameBits(new byte[] { 0x9 }, new byte[] { 0x0 }));
        assertEquals(6,
                MinHash.countSameBits(new byte[] { 0xa }, new byte[] { 0x0 }));
        assertEquals(5,
                MinHash.countSameBits(new byte[] { 0xb }, new byte[] { 0x0 }));
        assertEquals(6,
                MinHash.countSameBits(new byte[] { 0xc }, new byte[] { 0x0 }));
        assertEquals(5,
                MinHash.countSameBits(new byte[] { 0xd }, new byte[] { 0x0 }));
        assertEquals(5,
                MinHash.countSameBits(new byte[] { 0xe }, new byte[] { 0x0 }));
        assertEquals(4,
                MinHash.countSameBits(new byte[] { 0xf }, new byte[] { 0x0 }));
        assertEquals(4,
                MinHash.countSameBits(new byte[] { 0x0f }, new byte[] { 0x0 }));
        assertEquals(3,
                MinHash.countSameBits(new byte[] { 0x1f }, new byte[] { 0x0 }));
        assertEquals(3,
                MinHash.countSameBits(new byte[] { 0x2f }, new byte[] { 0x0 }));
        assertEquals(2,
                MinHash.countSameBits(new byte[] { 0x3f }, new byte[] { 0x0 }));
        assertEquals(3,
                MinHash.countSameBits(new byte[] { 0x4f }, new byte[] { 0x0 }));
        assertEquals(2,
                MinHash.countSameBits(new byte[] { 0x5f }, new byte[] { 0x0 }));
        assertEquals(2,
                MinHash.countSameBits(new byte[] { 0x6f }, new byte[] { 0x0 }));
        assertEquals(1,
                MinHash.countSameBits(new byte[] { 0x7f }, new byte[] { 0x0 }));
        assertEquals(3, MinHash.countSameBits(new byte[] { (byte) 0x8f },
                new byte[] { 0x0 }));
        assertEquals(2, MinHash.countSameBits(new byte[] { (byte) 0x9f },
                new byte[] { 0x0 }));
        assertEquals(2, MinHash.countSameBits(new byte[] { (byte) 0xaf },
                new byte[] { 0x0 }));
        assertEquals(1, MinHash.countSameBits(new byte[] { (byte) 0xbf },
                new byte[] { 0x0 }));
        assertEquals(2, MinHash.countSameBits(new byte[] { (byte) 0xcf },
                new byte[] { 0x0 }));
        assertEquals(1, MinHash.countSameBits(new byte[] { (byte) 0xdf },
                new byte[] { 0x0 }));
        assertEquals(1, MinHash.countSameBits(new byte[] { (byte) 0xef },
                new byte[] { 0x0 }));
        assertEquals(0, MinHash.countSameBits(new byte[] { (byte) 0xff },
                new byte[] { 0x0 }));

        assertEquals(
                16,
                MinHash.countSameBits(new byte[] { 0x0, 0x0 }, new byte[] {
                        0x0, 0x0 }));
        assertEquals(
                15,
                MinHash.countSameBits(new byte[] { 0x0, 0x0 }, new byte[] {
                        0x0, 0x1 }));

    }

    public void test_calculate_toBinaryString() throws IOException {
        assertEquals("00000000", MinHash.toBinaryString(new byte[] { 0 }));
        assertEquals("00000001", MinHash.toBinaryString(new byte[] { 1 }));
        assertEquals("00000010", MinHash.toBinaryString(new byte[] { 2 }));
        assertEquals("00000011", MinHash.toBinaryString(new byte[] { 3 }));
        assertEquals("00000100", MinHash.toBinaryString(new byte[] { 4 }));
        assertEquals("00000101", MinHash.toBinaryString(new byte[] { 5 }));
        assertEquals("00000110", MinHash.toBinaryString(new byte[] { 6 }));
        assertEquals("00000111", MinHash.toBinaryString(new byte[] { 7 }));
        assertEquals("00001000", MinHash.toBinaryString(new byte[] { 8 }));
        assertEquals("00001001", MinHash.toBinaryString(new byte[] { 9 }));
        assertEquals("00001010", MinHash.toBinaryString(new byte[] { 10 }));
        assertEquals("00001011", MinHash.toBinaryString(new byte[] { 11 }));
        assertEquals("00001100", MinHash.toBinaryString(new byte[] { 12 }));
        assertEquals("00001101", MinHash.toBinaryString(new byte[] { 13 }));
        assertEquals("00001110", MinHash.toBinaryString(new byte[] { 14 }));
        assertEquals("00001111", MinHash.toBinaryString(new byte[] { 15 }));
        assertEquals("00010000", MinHash.toBinaryString(new byte[] { 16 }));

        assertEquals("0000000100000000",
                MinHash.toBinaryString(new byte[] { 1, 0 }));
    }

    public void test_calculate() throws IOException {

        final Analyzer minhashAnalyzer4 = MinHash.createAnalyzer(1, 0, 4);
        final Analyzer minhashAnalyzer8 = MinHash.createAnalyzer(1, 0, 8);
        final Analyzer minhashAnalyzer12 = MinHash.createAnalyzer( 1, 0,
                12);
        final Analyzer minhashAnalyzer16 = MinHash.createAnalyzer(1, 0,
                16);
        final StringBuilder[] texts = createTexts();

        assertEquals("00000011", MinHash.toBinaryString(MinHash.calculate(
                minhashAnalyzer4, texts[0].toString())));
        assertEquals("11010011", MinHash.toBinaryString(MinHash.calculate(
                minhashAnalyzer8, texts[0].toString())));
        assertEquals("1101001100000000", MinHash.toBinaryString(MinHash
                .calculate(minhashAnalyzer12, texts[0].toString())));
        assertEquals("1101001101000000", MinHash.toBinaryString(MinHash
                .calculate(minhashAnalyzer16, texts[0].toString())));
    }

    public void test_calculate_multiple_data() throws IOException {

        final Analyzer minhashAnalyzer4 = MinHash.createAnalyzer(1, 0, 4);
        final Analyzer minhashAnalyzer8 = MinHash.createAnalyzer(1, 0, 8);
        final Analyzer minhashAnalyzer12 = MinHash.createAnalyzer(1, 0,
                12);
        final Analyzer minhashAnalyzer16 = MinHash.createAnalyzer(1, 0,
                16);
        final StringBuilder[] texts = createTexts();

        assertEquals("00000011", MinHash.toBinaryString(MinHash
                .calculate(new MinHash.Data[] { MinHash.newData(
                        minhashAnalyzer4, texts[0].toString(), 4) })));
        assertEquals("00000011", MinHash.toBinaryString(MinHash
                .calculate(new MinHash.Data[] { MinHash.newData(
                        minhashAnalyzer4, texts[0].toString(), 8) })));
        assertEquals("0000001100000000", MinHash.toBinaryString(MinHash
                .calculate(new MinHash.Data[] { MinHash.newData(
                        minhashAnalyzer4, texts[0].toString(), 12) })));
        assertEquals("0000001100000000", MinHash.toBinaryString(MinHash
                .calculate(new MinHash.Data[] { MinHash.newData(
                        minhashAnalyzer4, texts[0].toString(), 16) })));

        assertEquals("00000011", MinHash.toBinaryString(MinHash
                .calculate(new MinHash.Data[] { MinHash.newData(
                        minhashAnalyzer8, texts[0].toString(), 4) })));
        assertEquals("11010011", MinHash.toBinaryString(MinHash
                .calculate(new MinHash.Data[] { MinHash.newData(
                        minhashAnalyzer8, texts[0].toString(), 8) })));
        assertEquals("1101001100000000", MinHash.toBinaryString(MinHash
                .calculate(new MinHash.Data[] { MinHash.newData(
                        minhashAnalyzer8, texts[0].toString(), 12) })));
        assertEquals("1101001100000000", MinHash.toBinaryString(MinHash
                .calculate(new MinHash.Data[] { MinHash.newData(
                        minhashAnalyzer8, texts[0].toString(), 16) })));

        assertEquals("00000011", MinHash.toBinaryString(MinHash
                .calculate(new MinHash.Data[] { MinHash.newData(
                        minhashAnalyzer12, texts[0].toString(), 4) })));
        assertEquals("11010011", MinHash.toBinaryString(MinHash
                .calculate(new MinHash.Data[] { MinHash.newData(
                        minhashAnalyzer12, texts[0].toString(), 8) })));
        assertEquals("1101001100000000", MinHash.toBinaryString(MinHash
                .calculate(new MinHash.Data[] { MinHash.newData(
                        minhashAnalyzer12, texts[0].toString(), 12) })));
        assertEquals("1101001100000000", MinHash.toBinaryString(MinHash
                .calculate(new MinHash.Data[] { MinHash.newData(
                        minhashAnalyzer12, texts[0].toString(), 16) })));

        assertEquals("00000011", MinHash.toBinaryString(MinHash
                .calculate(new MinHash.Data[] { MinHash.newData(
                        minhashAnalyzer16, texts[0].toString(), 4) })));
        assertEquals("11010011", MinHash.toBinaryString(MinHash
                .calculate(new MinHash.Data[] { MinHash.newData(
                        minhashAnalyzer16, texts[0].toString(), 8) })));
        assertEquals("1101001100000000", MinHash.toBinaryString(MinHash
                .calculate(new MinHash.Data[] { MinHash.newData(
                        minhashAnalyzer16, texts[0].toString(), 12) })));
        assertEquals("1101001101000000", MinHash.toBinaryString(MinHash
                .calculate(new MinHash.Data[] { MinHash.newData(
                        minhashAnalyzer16, texts[0].toString(), 16) })));
    }
}