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

import static org.apache.jena.atlas.lib.Alg.decodeIndex ;

import java.nio.ByteBuffer ;

import org.apache.jena.atlas.lib.NotImplemented ;
import org.apache.jena.tdb.base.StorageException ;
import org.apache.jena.tdb.base.block.Block ;
import org.apache.jena.tdb.base.record.Record ;
import org.apache.jena.tdb.base.record.RecordFactory ;
import org.apache.jena.tdb.base.recordbuffer.RecordBufferPageBase ;

/** A HashBucket is a record buffer, with space store it's hash value and the bit length */  

public final class HashBucket extends RecordBufferPageBase
{
    // Offsets in the block.
    // Count is 0.
    final public static int TRIE        = COUNT+4 ;
    final public static int BITLEN      = TRIE+4 ;
    
    final public static int FIELD_LENGTH      = BITLEN ;      // Length of the space needed here (not count)
    
    // Trie/Hash of this bucket.
    private int trie ;
    // How many bits are used for storing in this bucket.
    private int bucketBitLen ;

    public static HashBucket format(Block block, RecordFactory factory)
    {
        ByteBuffer byteBuffer = block.getByteBuffer() ;
        int count = byteBuffer.getInt(COUNT) ;
        int hash = byteBuffer.getInt(TRIE) ;
        int hashLen = byteBuffer.getInt(BITLEN) ;
        HashBucket bucket = new HashBucket(NO_ID, hash, hashLen, block, factory, count) ;
        return bucket ;
    }
    
    public static HashBucket createBlank(Block block, RecordFactory factory)
    {
        ByteBuffer byteBuffer = block.getByteBuffer() ;
        int count = 0 ; 
        int hash = -1 ;
        int hashLen = -1 ;
        HashBucket bucket = new HashBucket(NO_ID, hash, hashLen, block, factory, count) ;
        return bucket ;
    }


    
    /** Create a bucket */
    public HashBucket(int id, int hashValue, int bucketBitLen,
                      Block block,
                      RecordFactory factory, 
                      int count)
    {
        super(block, FIELD_LENGTH, factory, count) ;
        this.bucketBitLen = bucketBitLen ;
        this.trie = hashValue ;
    }

    // Find the index of the key, return insertion a point if not found as -(i+1)
    public final int findIndex(Record key)
    { 
        int i = getRecordBuffer().find(key) ;
        return i ;
    }
    
    public final Record find(Record key)
    { 
        int i = getRecordBuffer().find(key) ;
        if ( i < 0 )
            return null ;
        return getRecordBuffer().get(i) ;
    }
    
    // Return true is added a new value 
    public final boolean put(Record record)
    {
        int i = findIndex(record) ;
        if ( i < 0 )
            i = decodeIndex(i) ;
        else
        {
            Record recordOrig = getRecordBuffer().get(i) ;
            if ( record.equals(recordOrig) )
                return false ;
            // Same key, different values. Replace.
            getRecordBuffer().set(i, record) ;
            return true ;
        }
        
        if ( getRecordBuffer().isFull() )
            throw new StorageException("Bucket overflow") ; 
        
        getRecordBuffer().add(i, record) ;
        return true ;
    }          
    
    public void set(int x, Record record)
    {
        getRecordBuffer().set(x, record) ;
    }

    public final boolean removeByKey(Record key)
    {
        int i = findIndex(key) ;
        if ( i < 0 )
            return false ;
        
        getRecordBuffer().remove(i) ;
        return true ;         
    }
    
    @Override
    public void _reset(Block block)
    { throw new NotImplemented("reset") ; }

    public final boolean isFull()
    {
        return getRecordBuffer().isFull() ;
    }
    
    public final boolean isEmpty()
    {
        return getRecordBuffer().isEmpty() ;
    }
    
    // Return the item in slot idx
    public final Record get(int idx)
    { 
        if ( idx >= getRecordBuffer().size() )
            return null ;
        return getRecordBuffer().get(idx) ;
    }
    
    public final int getTrieValue()         { return trie ; }
    
    final void setTrieValue(int newHash)    {  trie = newHash; }

    public final int getTrieBitLen()        { return bucketBitLen ; }
    
    public void setTrieLength(int trieBitLen) { bucketBitLen = trieBitLen ; }
//
//    public void setPageMgr(HashBucketMgr pageMgr)
//    {
//        this.pageMgr = pageMgr;
//    }
//
//    public HashBucketMgr getPageMgr()
//    {
//        return pageMgr;
//    }

    final void incTrieBitLen()              { bucketBitLen++ ; }

    @Override
    public String toString()
    {
        return String.format("HashBucket [id=%d, trie=0x%04X, bitlen=%d]: %s",getId(), getTrieValue(), getTrieBitLen(), getRecordBuffer().toString());
    }

}
