/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.jena.tdb.index;

import static java.lang.String.format ;
import static org.apache.jena.atlas.lib.ListUtils.asList ;
import static org.apache.jena.atlas.lib.ListUtils.unique ;
import static org.apache.jena.atlas.lib.RandomLib.random ;
import static org.apache.jena.atlas.test.Gen.permute ;
import static org.apache.jena.atlas.test.Gen.rand ;
import static org.apache.jena.atlas.test.Gen.strings ;
import static org.apache.jena.tdb.base.record.RecordLib.intToRecord ;
import static org.apache.jena.tdb.base.record.RecordLib.r ;
import static org.apache.jena.tdb.base.record.RecordLib.toIntList ;
import static org.junit.Assert.assertEquals ;
import static org.junit.Assert.assertNotNull ;
import static org.junit.Assert.fail ;

import java.util.ArrayList ;
import java.util.List ;
import java.util.SortedSet ;
import java.util.TreeSet ;

import org.apache.jena.atlas.junit.BaseTest ;
import org.apache.jena.tdb.base.record.Record ;
import org.apache.jena.tdb.base.record.RecordLib ;
import org.apache.jena.tdb.index.Index ;
import org.apache.jena.tdb.index.RangeIndex ;

public class IndexTestLib
{

    // ---------- Utilities
    
//    public static RangeIndex buildRangeIndex(RangeIndexMaker maker, int[] keys)
//    {
//        RangeIndex index = maker.make() ;
//        IndexTestLib.add(index, keys) ;
//        return index ;
//    }

    public static Index buildIndex(IndexMaker maker, int[] keys)
    {
        Index index = maker.makeIndex() ;
        IndexTestLib.add(index, keys) ;
        return index ;
    }

    
    public static void testIteration(RangeIndex index, int[] keys, int numIterations)
    {
        // Shared across test-lets
        SortedSet<Integer> x = new TreeSet<>() ;
        for ( int v : keys )
            x.add(v) ;
        
        for ( int i = 0 ; i < numIterations ; i++ )
        {
            int lo = random.nextInt(keys.length) ;
            int hi = random.nextInt(keys.length) ;
            if ( lo > hi )
            {
                int t = lo ;
                lo = hi ;
                hi = t ;
            }
            // Does not consider nulls - assumed to be part of functional testing.
            // Tweak lo and hi
            if ( lo != 0 && random.nextFloat() < 0.5 )
                lo-- ;  // Negatives confuse the int/record code.
            if ( random.nextFloat() < 0.5 )
                hi++ ;
            
            List<Integer> slice = r(index.iterator(r(lo), r(hi))) ;
            List<Integer> expected = new ArrayList<>(keys.length) ;
            for ( Integer ii : x.subSet(lo, hi) )
                expected.add(ii) ;
            assertEquals(format("(%d,%d)",lo, hi), expected, slice) ;
        }
    }

    /* One random test : print the keys if there was a problem */ 
    
    public static void randTest(Index index, int maxValue, int numKeys)
    {
        if ( numKeys >= 5000 )
            System.err.printf("Warning: too many keys\n") ;
            
        int[] keys1 = rand(numKeys, 0, maxValue) ;
        int[] keys2 = permute(keys1) ;
        try {
            testInsert(index, keys1);
            if ( true )
            {
                // Checking tests.
                testIndexContents(index, keys2);
                // Test iteration - quite expensive.
                if ( index instanceof RangeIndex )
                    testIteration((RangeIndex)index, keys1, 10) ;
            }
            testDelete(index, keys2) ;
            index.close() ;
        } catch (RuntimeException ex)
        {
            System.err.printf("Index : %s\n", index.getClass().getName()) ;
            System.err.printf("int[] keys1 = {%s} ;\n", strings(keys1)) ;
            System.err.printf("int[] keys2 = {%s}; \n", strings(keys2)) ;
            throw ex ;
        }
    }

    // ---- Test utils
    
    public static void testInsert(Index index, int[] keys)
    {
        IndexTestLib.add(index, keys) ;
        testIndexContents(index, keys);
    }

    public static Index testInsert(IndexMaker maker, int[] keys)
    {
        Index index = maker.makeIndex() ;
        testInsert(index, keys);
        return index ; 
    }

    public static void testInsertDelete(Index index, int[] buildKeys, int[] deleteKeys)
    {
        testInsert(index, buildKeys) ;
        testDelete(index, deleteKeys) ;
    }

    public static void testDelete(Index index, int[] vals)
    {
        long size1 = index.size() ;
        
        int count = 0 ;
        count = delete(index, vals) ;
    
        List<Record> x =  intToRecord(vals, RecordLib.TestRecordLength) ;
        for ( Record r : x )
        {
            boolean b = index.delete(r) ;
            if ( b )
                count ++ ;
        }
        
        for ( Record r : x )
            BaseTest.assertFalse(index.contains(r)) ;
        long size2 = index.size() ;

        assertEquals(size1-count, size2) ;
    }

    public static int delete(Index index, int[] vals)
    {
        int count = 0 ;
        for ( int v : vals )
        {
            boolean b = index.delete(r(v)) ;
            if ( b )
                count ++ ;
        }
        return count ;
    }

    public static void add(Index index, int[] vals)
    {
        //System.out.println("Add: "+Arrays.toString(vals)) ;
        List<Record> x = intToRecord(vals, RecordLib.TestRecordLength) ;
        for ( Record r : x )
        {
            //System.out.println("  Add: "+r) ;
            index.add(r) ;
        }
    }

    public static void testIndexContents(Index index, int[] records)
    {
        List<Integer> x = toIntList(index.iterator());
        
        // Make a unique list of expected records.  Remove duplicates
        List<Integer> y = unique(asList(records)) ;
        
        assertEquals("Expected records size and tree size different", y.size(), index.size()) ;
        assertEquals("Expected records size and iteration over all keys are of different sizes", y.size(), x.size()) ;
        
        if ( index instanceof RangeIndex )
        {
            // Check sorted order
            for ( int i = 0 ; i < x.size()-2 ; i++ )
            {
                if ( x.get(i) > x.get(i+1) )
                {
                    fail("check failed: "+strings(records)) ;
                    return ;
                }
            }
        }
        
        // Check each expected record is in the tree
        for ( int k : y)
        {
            Record rec = intToRecord(k) ;
            Record r2 = index.find(rec) ;
            assertNotNull("Finding "+rec, r2) ;
        }
    }
}
