/*
 * Copyright (C) 2014 Indeed Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the
 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the License for the specific language governing permissions and
 * limitations under the License.
 */
 package com.indeed.flamdex.fieldcache;

import com.google.common.collect.Lists;
import com.google.common.primitives.Longs;
import com.indeed.util.io.Files;
import com.indeed.flamdex.api.IntValueLookup;
import com.indeed.flamdex.reader.MockFlamdexReader;
import org.junit.Test;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;

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

/**
 * @author jsgroth
 */
public class TestFieldCacher {
    private final Random rand = new Random();

    @Test
    public void testFieldCacher() throws IOException {
        runCacheTest(Integer.MAX_VALUE, 65535, FieldCacher.INT, 40);
        runCacheTest(65535, 255, FieldCacher.CHAR, 20);
        runCacheTest(255, 1, FieldCacher.BYTE, 10);
        runCacheTest(1, 0, FieldCacher.BITSET, 8);
    }

    private void runCacheTest(int maxVal, int lowerMaxVal, FieldCacher expectedType, long expectedMemory) throws IOException {
        for (int i = 0; i < 10; ++i) {
            MockFlamdexReader r = new MockFlamdexReader(Arrays.asList("f"), Collections.<String>emptyList(), Arrays.asList("f"), 10);
            long maxTerm = rand.nextInt(maxVal - lowerMaxVal) + lowerMaxVal + 1;
            int maxTermDoc = rand.nextInt(10);
            r.addIntTerm("f", maxTerm, maxTermDoc);
            List<Integer> docs = Lists.newArrayList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
            docs.remove(Integer.valueOf(maxTermDoc));
            Set<Integer> terms = new HashSet<Integer>();
            long[] cache = new long[10];
            cache[maxTermDoc] = maxTerm;
            if (lowerMaxVal >= docs.size()) {
                while (!docs.isEmpty()) {
                    int numDocs = docs.size() > 1 ? rand.nextInt(docs.size() - 1) + 1 : 1;
                    int term = rand.nextInt(lowerMaxVal + 1);
                    if (terms.contains(term)) continue;
                    List<Integer> termDocs = new ArrayList<Integer>();
                    for (int j = 0; j < numDocs; ++j) {
                        termDocs.add(docs.remove(rand.nextInt(docs.size())));
                    }
                    Collections.sort(termDocs);
                    r.addIntTerm("f", term, termDocs);
                    terms.add(term);
                    for (int doc : termDocs) {
                        cache[doc] = term;
                    }
                }
            } else {
                int term = rand.nextInt(lowerMaxVal + 1);
                r.addIntTerm("f", term, docs);
                for (int doc : docs) {
                    cache[doc] = term;
                }
            }

            FieldCacher fieldCacher = FieldCacher.getCacherForField("f", r);
            assertEquals(expectedType, fieldCacher);
            assertEquals(expectedMemory, fieldCacher.memoryRequired(r.getNumDocs()));
            IntValueLookup ivl = fieldCacher.newFieldCache("f", r);
            verifyCache(cache, ivl);

            String tempDir = Files.getTempDirectory("asdf", "");
            for (int x = 0; x < 5; ++x) {
                if (x > 0) {
                    assertTrue(new File(tempDir, fieldCacher.getMMapFileName("f")).exists());
                } else {
                    assertFalse(new File(tempDir, fieldCacher.getMMapFileName("f")).exists());
                }
                IntValueLookup mmivl = fieldCacher.newMMapFieldCache("f", r, tempDir);
                verifyCache(cache, mmivl);
                assertTrue(new File(tempDir, fieldCacher.getMMapFileName("f")).exists());
                mmivl.close();
                assertTrue(new File(tempDir, fieldCacher.getMMapFileName("f")).exists());
            }
            Files.delete(tempDir);
        }
    }

    private static void verifyCache(long[] cache, IntValueLookup ivl) {
        int[] docIds = new int[10];
        for (int j = 0; j < 10; ++j) docIds[j] = j;
        long[] values = new long[10];
        ivl.lookup(docIds, values, 10);
        assertEquals(Longs.asList(cache), Longs.asList(values));
    }
}
