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

import static java.lang.String.format ;
import static org.apache.jena.tdb.sys.SystemTDB.FillByte ;
import static org.apache.jena.tdb.sys.SystemTDB.NetworkOrder ;
import static org.apache.jena.tdb.sys.SystemTDB.NullOut ;

import java.nio.ByteBuffer ;

import org.apache.jena.atlas.lib.ByteBufferLib ;
import org.apache.jena.tdb.base.record.RecordException ;


/** ByteBuffer specialization - Common operations for RecordBuffer and PtrBuffer */  
abstract class BufferBase
{
    public static boolean CheckBuffer = false ;
    
    protected ByteBuffer bb ;
    protected int slotLen ;
    protected int numSlot ;                 // Number of records in use
    protected int maxSlot ;                 // Maximum number of records

    protected BufferBase(ByteBuffer bb, int slotLen, int num)
    {
        if ( CheckBuffer )
        {
            if ( ! bb.order().equals(NetworkOrder) || !bb.hasArray() )
                throw new RecordException("Duff buffer (byte order is not network order)") ;
            if ( bb.limit() == 0 )
                throw new RecordException("Duff buffer (zero length byte buffer)") ;
            int size = bb.limit() ;
            int slots = size/slotLen  ;
            if ( size%slotLen != 0 )
                throw new RecordException(format("Misalinged buffer: size=%d, keyLen=%d",size, slotLen)) ; 
            if ( slots < num )
                throw new RecordException(format("Wrong size: slots=%d, len=%d", slots, num)) ;
        }
        this.bb = bb ; 
        this.slotLen = slotLen ;
        this.numSlot = num ;
        this.maxSlot = bb.limit()/slotLen ;
        if ( NullOut )
            clear(numSlot, maxSlot-numSlot) ;
    }
    
    //private BufferBase() {}

    // Why so many final methods?  This code is performace critical and "final" methods
    // can be inlined by the JIT - or just not have object dispatch done each time
    // because the destination is fixed.
    // (and here - there are quite a few assumed relationships of the ints). 
    
    final
    public void copy(int srcIdx, BufferBase dst, int dstIdx, int len)
    {
        if ( len == 0 )
            return ;
        
        // Check end index is inside the buffer.
        checkBounds(srcIdx+len-1, maxSlot) ;
     
        BufferBase src = this ;   // Clarity

        if ( dst.numSlot < dstIdx-1 )
            // Allow copy to be just off the end of dst.
            throw new IllegalArgumentException(format("copy: Out of bounds: dstIdx=%d, dst size=%d", dstIdx, dst.numSlot)) ;
        if ( src.slotLen != dst.slotLen )
            throw new RecordException(format("copy: records of differnt sizes: %d, %d",src.slotLen, dst.slotLen)) ;
        
        // How do we set the numRec in dst? max(dstIdx+len, old count)  
        ByteBufferLib.bbcopy(src.bb, srcIdx, dst.bb, dstIdx, len, slotLen) ;
        dst.numSlot = Math.max(dstIdx+len, dst.numSlot) ; 
    }
    
    final
    public void copyToTop(BufferBase dst)
    { 
        copy(0, dst, dst.numSlot, numSlot) ; } 
    
    /** Remove top element */
    final public void removeTop()
    {
        if ( numSlot == 0 )
            throw new IndexOutOfBoundsException("removeTop: empty buffer") ; 
        clear(numSlot-1) ;
        numSlot-- ;
    }
    
    final public void remove(int idx) { shiftDown(idx) ; }
    
    /** Does not reset the size */
    final
    public void clear(int idx, int len)
    {
        if ( NullOut )
            ByteBufferLib.bbfill(bb, idx, (idx+len), FillByte, slotLen) ;
    }

    /** Does not reset the size */
    final public void clear()           { clear(0, maxSlot) ; numSlot = 0 ; }
    
    /** Does not reset the size */
    final public void clear(int idx)    { clear(idx, 1) ; }
    
    /** Is the record at idx set clear or not?
        This is done without regard to buffer size.
        Requires NullOut to be accurate.
        Testing.
     */
    final public boolean isClear(int idx)
    {
        checkBounds(idx, maxSlot) ;
        int x = idx*slotLen ;
        int y = (idx+1)*slotLen ;
        for ( int i = x ; i < y ; i++ )
            if ( bb.get(i) != FillByte )
            {
//                byte b = bb.get(i) ;
//                lib.ByteBufferLib.print(bb) ;
                return false ;
            }
        return true ;
    }

    final public boolean isFull()
    {
        return numSlot >= maxSlot ; 
    }

    final public boolean isEmpty()
    {
        return numSlot == 0 ; 
    }
    
    final public void incSize() { incSize(1) ; }
    final public void incSize(int n)
    { 
        if ( numSlot+n > maxSlot )
            throw new IllegalArgumentException(format("inc(%d): out of range: max=%d", n, maxSlot)) ;
        numSlot += n ;
    }

    final public void decSize() { decSize(1) ; }
    final public void decSize(int n)
    { 
        if ( numSlot-n < 0 )
            throw new IllegalArgumentException(format("dec(%d): out of range: max=%d", n, maxSlot)) ;
        numSlot -= n ;
    }
    
    final public int slotLen()      { return slotLen ; }
    
    final public int getSize()      { return numSlot ; } 
    
    final public void setSize(int n)
    {
        if ( n < 0 || n > maxSlot )
            throw new IllegalArgumentException(format("size(%d): out of range: max=%d", n, maxSlot)) ;
        numSlot = n ;
    }
    
    final public int size()       { return numSlot ; }
    final public int maxSize()    { return maxSlot ; }
    
    final public void shiftUp(int idx) { shiftUpN(idx, 1) ; }
    final public void shiftUpN(int idx, int num)
    {
        checkBounds(idx, numSlot) ;
        if ( numSlot + num > maxSlot )
            throw new IllegalArgumentException(format("Shift up(%d): out of range: len=%d max=%d", num, num, maxSlot)) ;
        
        ByteBufferLib.bbcopy(bb, idx, idx+num, (numSlot-idx), slotLen) ;       // src, dst 
        if ( NullOut )
            clear(idx, num) ;

        numSlot += num ;
    }
    
    final public void shiftDown(int idx) { shiftDownN(idx, 1) ; }
    final public void shiftDownN(int idx, int num)
    {
        checkBounds(idx, numSlot) ;
        if ( idx+num > numSlot )
            throw new IllegalArgumentException(format("Shift down(%d,%d): out of range: len=%d", idx, num, num)) ;

        ByteBufferLib.bbcopy(bb, idx+num, idx, (numSlot-num-idx), slotLen) ;       // src, dst

        if ( NullOut )
            clear(numSlot-num, num) ;
        numSlot -= num ;
    }
    
//    @Override
//    final public String toString()
//    {
//        StringBuilder str = new StringBuilder() ;
//        str.append(format("Len=%d Count=%d ", bb.limit()/recLen, num)) ;
//        
//        for ( int i = 0 ; i < max*recLen ; i++ )
//        {
//            if ( i != 0 && i%recLen == 0 )
//                str.append(" ") ;
//            byte b = bb.get(i) ;
//            str.append(format("%02x", b)) ;
//        }
//        return str.toString() ;
//    }

    /** Move the element from the high end of this to the low end of other */
    public void shiftRight(BufferBase other)
    {
        if ( other.numSlot >= other.maxSlot )
            throw new BufferException("No space in destination buffer") ;
        if ( numSlot <= 0 )
            throw new BufferException("Empty buffer") ;

        if ( other.numSlot > 0 )
            other.shiftUp(0) ;
        else
            other.numSlot++ ;
        // Copy high to low slot.
        ByteBufferLib.bbcopy(bb, (numSlot-1), other.bb, 0, 1, slotLen) ;
        removeTop() ;
    }

    /** Move the element from the low end of other to the high end of this */
    public void shiftLeft(BufferBase other)
    {
        if ( numSlot >= maxSlot )
            throw new BufferException("No space in destination buffer") ;
        if ( other.numSlot <= 0 )
            throw new BufferException("Empty buffer") ;

        // Copy low to above high slot.
        ByteBufferLib.bbcopy(other.bb, 0, bb, numSlot, 1, slotLen) ;
        // Correct length.
        numSlot ++ ;
        other.shiftDown(0) ;
    }

    final private static void checkBounds(int idx, int len)
    {
        if ( idx < 0 || idx >= len )
            throw new BufferException(format("Out of bounds: idx=%d, size=%d", idx, len)) ;
    }
}
