/*
 * 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.bplustree;

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

import org.apache.jena.tdb.base.block.BlockMgr ;
import org.apache.jena.tdb.base.block.BlockMgrFactory ;
import org.apache.jena.tdb.base.buffer.RecordBuffer ;
import org.apache.jena.tdb.base.record.Record ;
import org.apache.jena.tdb.base.record.RecordFactory ;
import org.apache.jena.tdb.base.record.RecordLib ;
import org.apache.jena.tdb.base.recordbuffer.RecordBufferPage ;
import org.apache.jena.tdb.base.recordbuffer.RecordBufferPageMgr ;
import org.apache.jena.tdb.sys.SystemTDB ;
import org.junit.After ;
import org.junit.AfterClass;
import org.junit.Before ;
import org.junit.BeforeClass;
import org.junit.Test;

public class TestBPTreeRecords
{
    static private boolean oldNullOut ;
    static private boolean oldCheckingNode ;
    static private boolean oldCheckingBTree ;
    
    static private int blockSize ;
    static private RecordFactory recordFactory ;
    
    static private int bufSizeRecord ;
    static private BlockMgr blkMgrRecords  ;
    static private RecordBufferPageMgr recordBufferPageMgr  ;
    static private BPlusTree bPlusTree ;
    
    @BeforeClass public static void beforeClass()
    {
        oldNullOut = SystemTDB.NullOut ;
        SystemTDB.NullOut = true ;
        
        oldCheckingNode = BPlusTreeParams.CheckingNode ;
        BPlusTreeParams.CheckingNode = true ;
        
        oldCheckingBTree = BPlusTreeParams.CheckingTree ;
        BPlusTreeParams.CheckingTree = true ;
        
        blockSize =  4*8 ;  // Which is 6 int records
        recordFactory = new RecordFactory(4, 0) ;
        
        bufSizeRecord = RecordBufferPage.calcRecordSize(recordFactory, blockSize) ;
        blkMgrRecords = BlockMgrFactory.createMem("BPTreeRecords", blockSize) ;
        recordBufferPageMgr = new RecordBufferPageMgr(recordFactory, blkMgrRecords) ;
        
        // B+Tree order does not matter.
        bPlusTree = BPlusTree.attach(new BPlusTreeParams(3, recordFactory), null, blkMgrRecords) ;
    }
    
    @AfterClass public static void afterClass()
    {
        SystemTDB.NullOut = oldNullOut ;
        BPlusTreeParams.CheckingTree = oldCheckingNode ;
        BPlusTreeParams.CheckingTree = oldCheckingBTree ;
    }

    @Test public void bpt_records_1()
    {
        BPTreeRecords bpr = make() ;
        fill(bpr) ;
        check(bpr) ;
        bpr.release() ;
    }
    
    @Before public void before() { blkMgrRecords.beginUpdate() ; }
    @After public void after() { blkMgrRecords.endUpdate() ; }
    

    @Test public void bpt_records_2()
    {
        BPTreeRecords bpr = make() ;
        fill(bpr) ;
        int s = bpr.getCount();
        assertTrue(bpr.isFull()) ;
        BPTreePage z = bpr.split() ;
        assertTrue(z instanceof BPTreeRecords) ;
        assertEquals(s, z.getCount()+bpr.getCount()) ;
        check(bpr) ;
        check((BPTreeRecords)z) ;
        bpr.release() ;
        z.release() ;
    }

    @Test public void bpt_records_3()
    {
        BPTreeRecords bpr = make() ;
        for ( int i = 0 ; bpr.getCount() < bpr.getMaxSize() ; i++ )
            insert(bpr, (i+0x20)) ;
        check(bpr) ;
        bpr.release() ;
    }
    
    @Test public void bpt_records_4()
    {
        BPTreeRecords bpr = make() ;
        for ( int i = bpr.getMaxSize()-1 ; i >= 0 ; i-- )
            insert(bpr, i+0x20) ;
        check(bpr) ;
        bpr.release() ;
    }
    
    @Test public void bpt_records_5()
    {
        BPTreeRecords bpr = make() ;
        int N =  bpr.getMaxSize() ;
        
        for ( int i = bpr.getMaxSize()-1 ; i >= 0 ; i-- )
            insert(bpr, (i+0x20)) ;
        
        delete(bpr, (1+0x20)) ;
        assertEquals(N-1, bpr.getCount()) ;
        check(bpr) ;

        delete(bpr, (2+0x20)) ;
        assertEquals(N-2, bpr.getCount()) ;
        check(bpr) ;
        
        delete(bpr, bpr.getLowRecord()) ;
        assertEquals(N-3, bpr.getCount()) ;
        check(bpr) ;

        bpr.internalDelete(bpr.getHighRecord()) ;
        assertEquals(N-4, bpr.getCount()) ;
        check(bpr) ;
        
        bpr.release() ;
    }
    
    @Test public void bpt_records_6()
    {
        BPTreeRecords bpr = make() ;
        fill(bpr) ;
        
        // No match.
        assertNull(bpr.internalSearch(RecordLib.intToRecord(0x20))) ;

        Record r = RecordLib.intToRecord(0x32) ;
        Record r2 = search(bpr, r) ;
        assertTrue(Record.keyEQ(r, r2)) ;

        r = bpr.getLowRecord() ;
        r2 = search(bpr, r) ;
        assertTrue(Record.keyEQ(r, r2)) ;
        
        r = bpr.getHighRecord() ;
        r2 = search(bpr, r) ;
        assertTrue(Record.keyEQ(r, r2)) ;
        
        bpr.release() ;
    }
    
    @Test public void bpt_shift_1()
    {
        BPTreeRecords bpr1 = make() ;
        BPTreeRecords bpr2 = make() ;

        insert(bpr1, 10) ;
        Record r = bpr1.shiftRight(bpr2, null) ;
        assertNull(r) ;
        //assertTrue(Record.keyEQ(r, RecordTestLib.intToRecord(10))) ;
        contains(bpr1) ;
        contains(bpr2, 10) ;
        
        bpr1.release() ;
        bpr2.release() ;

    }
    
    @Test public void bpt_shift_2()
    {
        BPTreeRecords bpr1 = make() ;
        BPTreeRecords bpr2 = make() ;
        
        insert(bpr1, 10) ;
        Record r = bpr2.shiftLeft(bpr1, null) ;
        
        assertTrue(Record.keyEQ(r, RecordLib.intToRecord(10))) ;
        contains(bpr1) ;
        contains(bpr2, 10) ;
        bpr1.release() ;
        bpr2.release() ;
    }

    @Test public void bpt_shift_3()
    {
        BPTreeRecords bpr1 = make() ;
        BPTreeRecords bpr2 = make() ;
        
        insert(bpr1, 10, 20) ;
        insert(bpr2, 99) ;
        
        Record r = bpr1.shiftRight(bpr2, null) ;

        assertTrue(r+" != "+RecordLib.intToRecord(10), Record.keyEQ(r, RecordLib.intToRecord(10))) ;
        contains(bpr1, 10) ;
        contains(bpr2, 20, 99) ;
        bpr1.release() ;
        bpr2.release() ;
    }

    @Test public void bpt_shift_4()
    {
        BPTreeRecords bpr1 = make() ;
        BPTreeRecords bpr2 = make() ;
        
        insert(bpr1, 10, 20) ;
        insert(bpr2, 5) ;
        
        Record r = bpr2.shiftLeft(bpr1, null) ;
        assertTrue(Record.keyEQ(r, RecordLib.intToRecord(10))) ;
        
        contains(bpr1, 20) ;
        contains(bpr2, 5, 10) ;
        bpr1.release() ;
        bpr2.release() ;
    }
    
    @Test public void bpt_merge_1()
    {
        BPTreeRecords bpr1 = make() ;
        BPTreeRecords bpr2 = make() ;
        
        insert(bpr1, 10, 20) ;
        insert(bpr2, 99) ;
        
        BPTreeRecords bpr3 = (BPTreeRecords)bpr1.merge(bpr2, null) ;
        contains(bpr1, 10, 20, 99) ;
        contains(bpr2) ;
        assertSame(bpr1, bpr3) ;
        bpr1.release() ;
        bpr2.release() ;
    }

    @Test public void bpt_merge_2()
    {
        BPTreeRecords bpr1 = make() ;
        BPTreeRecords bpr2 = make() ;
        
        insert(bpr1, 10, 20) ;
        insert(bpr2, 5) ;
        
        BPTreeRecords bpr3 = (BPTreeRecords)bpr2.merge(bpr1, null) ;
        contains(bpr1) ;
        contains(bpr2, 5, 10, 20) ;
        assertSame(bpr2, bpr3) ;
        bpr1.release() ;
        bpr2.release() ;
    }
    
    private static void check(BPTreeRecords bpr)
    {
        assertTrue(bpr.getCount() >= 0 ) ;
        assertTrue(bpr.getCount() <= bpr.getMaxSize() ) ;
        
        assertEquals(bpr.getRecordBuffer().getLow(), bpr.getLowRecord()) ;
        assertEquals(bpr.getRecordBuffer().getHigh(), bpr.getHighRecord()) ;
        
        for ( int i = 1 ; i < bpr.getCount() ; i++ )
        {
            Record r1 = bpr.getRecordBuffer().get(i-1) ;
            Record r2 = bpr.getRecordBuffer().get(i) ;
            assertTrue(Record.keyLE(r1, r2)) ;
        }
    }

    private static Record search(BPTreeRecords bpr, int x)
    {
        return search(bpr, RecordLib.intToRecord(x)) ;
    }

    
    private static Record search(BPTreeRecords bpr, Record r)
    {
        return bpr.internalSearch(r) ;
    }

    private static void insert(BPTreeRecords bpr, int ... values)
    {
        for ( int value : values )
        {
            bpr.internalInsert( RecordLib.intToRecord( value ) );
        }
    }
    
    private static void insert(BPTreeRecords bpr, Record r)
    {
        bpr.internalInsert(r) ;
    }
    

    private static void delete(BPTreeRecords bpr, int ... values)
    {
        for ( int value : values )
        {
            delete( bpr, RecordLib.intToRecord( value ) );
        }
    }
    
    private static void delete(BPTreeRecords bpr, Record r)
    {
        bpr.internalDelete(r) ;
    }
    

    private static void contains(BPTreeRecords bpr, int ... values)
    {
        assertEquals(values.length, bpr.getCount() ) ;
        for ( int i = 1 ; i < values.length ; i++ )
            assertTrue(Record.compareByKeyValue(RecordLib.intToRecord(values[i]),bpr.getRecordBuffer().get(i)) == 0 ) ;
    }

    private static BPTreeRecords make()
    {
        RecordBufferPage page = recordBufferPageMgr.create() ;
        return new BPTreeRecords(bPlusTree, page) ;
    }
    
    private static void fill(BPTreeRecords bpr)
    {
        RecordBuffer rb = bpr.getRecordBuffer() ;
        for ( int i = 0 ; rb.size() < rb.maxSize() ; i++ )
            insert(bpr, (i+0x30)) ;
    }
}
