package org.maachang.jni.io ;

/**
 * AbstractNativeBuffer.
 * 
 * @version 2010/06/04
 * @author  masahito suzuki
 * @since   SeabassNativeIO-1.0.0
 */
public abstract class AbstractNativeBuffer implements NativeBuffer {
    protected static final boolean BIG_ENDIAN = Unsafe.BIG_ENDIAN ;
    protected static final boolean UnsafeMode = Unsafe.UNSAFE_MODE ;
    //protected static final boolean UnsafeMode = false ;
    protected static final sun.misc.Unsafe unsafe = Unsafe.unsafe ;
    protected long address = 0L ;
    protected int length = 0 ;
    
    /**
     * メモリ先頭アドレスを取得.
     * @return long メモリの先頭アドレスが返されます.
     */
    public long getAddress() {
        //check(false) ;
        return address ;
    }
    
    /**
     * メモリサイズを取得.
     * @return int メモリサイズが返されます.
     */
    public int getLength() {
        //check(false) ;
        return length ;
    }
    
    /**
     * メモリクリアチェック.
     * @return boolean [true]の場合、メモリはクリアされています.
     */
    public boolean isClear() {
        return address == 0L ;
    }
    
    /**
     * put/get系メソッド呼び出し時のチェック処理.
     * @param mode [true]の場合、書き込み処理です.
     */
    protected void check( final boolean mode ) {
        // 上位で継承してチェック処理を行う.
    }
    
    /**
     * byte情報を設定.
     * @param index 対象のインデックス位置を設定します.
     * @param value 設定対象の情報を設定します.
     */
    public void putByte( final int index,final byte value ) {
        if( this.address == 0L ) {
            throw new IllegalStateException( "既にクリアされています" ) ;
        }
        if( index < 0 || index >= length ) {
            throw new IllegalArgumentException( "範囲外のインデックス値が設定されています(index:" +
                index + " memoryLength:" + length + ")" ) ;
        }
        check(true) ;
        if( UnsafeMode ) {
            unsafe.putByte( address+index,value ) ;
        }
        else {
            NativeIO.putByte( address+index,value ) ;
        }
    }
    
    /**
     * byte情報を取得.
     * @param index 対象のインデックス位置を設定します.
     * @return byte 情報が返されます.
     */
    public byte getByte( final int index ) {
        if( this.address == 0L ) {
            throw new IllegalStateException( "既にクリアされています" ) ;
        }
        if( index < 0 || index >= length ) {
            throw new IllegalArgumentException( "範囲外のインデックス値が設定されています(index:" +
                index + " memoryLength:" + length + ")" ) ;
        }
        check(false) ;
        if( UnsafeMode ) {
            return unsafe.getByte( address+index ) ;
        }
        return NativeIO.getByte( address+index ) ;
    }
    
    /** binaryコピーしきい値. **/
    //private static final int BINARY_COPY_TO_PUT_BYTE = DirectMemoryIO.BINARY_COPY_TO_PUT_BYTE ;
    
    /**
     * binary情報を設定.
     * @param index 対象のインデックス位置を設定します.
     * @param value 設定対象の情報を設定します.
     * @param offset 対象のオフセット値を設定します.
     * @param length 対象のデータ長を設定します.
     * @return int 設定された長さが返されます.
     */
    public int putBinary( final int index,final byte[] value,final int offset,final int length ) {
        if( this.address == 0L ) {
            throw new IllegalStateException( "既にクリアされています" ) ;
        }
        else if (value == null) {
            throw new NullPointerException();
        }
        else if( index < 0 || index + length > this.length ) {
            throw new IllegalArgumentException( "範囲外のインデックス値が設定されています(index:" +
                index + " memoryLength:" + this.length + ")" ) ;
        }
        else if ((offset < 0) || (offset > value.length) || (length < 0) ||
            ((offset + length) > value.length) || ((offset + length) < 0)) {
            throw new IndexOutOfBoundsException( "範囲外の値が設定されています(index:" +
                index + " memoryLength:" + this.length + " offset:" + offset + " length:" + length + ")" ) ;
        }
        else if (length <= 0) {
            return 0 ;
        }
        check(true) ;
        /*
        if( length <= BINARY_COPY_TO_PUT_BYTE ) {
            if( UnsafeMode ) {
                long j = index+address ;
                for( int i = 0 ; i < length ; i ++,j ++ ) {
                    unsafe.putByte( j,value[ i+offset ] ) ;
                }
            }
            else {
                long j = index+address ;
                for( int i = 0 ; i < length ; i ++,j ++ ) {
                    NativeIO.putByte( j,value[ i+offset ] ) ;
                }
            }
            return length ;
        }
        */
        NativeIO.putBinary( address+index,value,offset,length ) ;
        return length ;
    }
    
    /**
     * binary情報を取得.
     * @param index 対象のインデックス位置を設定します.
     * @param value 取得対象の情報を設定します.
     * @param offset 対象のオフセット値を設定します.
     * @param length 対象のデータ長を設定します.
     * @return int 設定された長さが返されます.
     */
    public int getBinary( final int index,final byte[] value,final int offset,final int length ) {
        if( this.address == 0L ) {
            throw new IllegalStateException( "既にクリアされています" ) ;
        }
        else if (value == null) {
            throw new NullPointerException();
        }
        else if( index < 0 || index + length > this.length ) {
            throw new IllegalArgumentException( "範囲外のインデックス値が設定されています(index:" +
                index + " memoryLength:" + this.length + ")" ) ;
        }
        else if ((offset < 0) || (offset > value.length) || (length < 0) ||
            ((offset + length) > value.length) || ((offset + length) < 0)) {
            throw new IndexOutOfBoundsException( "範囲外の値が設定されています(index:" +
                index + " memoryLength:" + this.length + " offset:" + offset + " length:" + length + ")" ) ;
        }
        else if (length <= 0) {
            return 0 ;
        }
        check(false) ;
        /*
        // binaryコピーしきい値範囲内の場合は、byte単位で処理.
        if( length <= BINARY_COPY_TO_PUT_BYTE ) {
            if( UnsafeMode ) {
                long j = index + address ;
                for( int i = 0 ; i < length ; i ++,j ++ ) {
                    value[ i+offset ] = unsafe.getByte( j ) ;
                }
            }
            else {
                long j = index + address ;
                for( int i = 0 ; i < length ; i ++,j ++ ) {
                    value[ i+offset ] = NativeIO.getByte( j ) ;
                }
            }
            return length ;
        }
        */
        NativeIO.getBinary( address+index,value,offset,length ) ;
        return length ;
    }
    
    /**
     * 指定NativeBufferをコピー.
     * <p>対象Bufferの0番目からデータ長の全ての長さをコピーします.</p>
     * @param index 対象のインデックス位置を設定します.
     * @param buf 対象のNativeBufferを設定します.
     */
    public void putNariveBuffer( int index,NativeBuffer buf ) {
        if( this.address == 0L ) {
            throw new IllegalStateException( "既にクリアされています" ) ;
        }
        else if (buf == null) {
            throw new NullPointerException();
        }
        if( buf.getLength() > length - index ) {
            throw new IllegalArgumentException( "範囲外のインデックス値が設定されています(index:" +
                index + " memoryLength:" + this.length + ")" ) ;
        }
        check(true) ;
        DirectMemoryIO.memcpy( address+index,buf.getAddress(),buf.getLength() ) ;
    }
    
    /**
     * 指定NativeBufferをコピー.
     * <p>対象Bufferの指定offsetから、lengthまでの長さをコピーします.</p>
     * @param index 対象のインデックス位置を設定します.
     * @param buf 対象のNativeBufferを設定します.
     * @param offset 対象のオフセット値を設定します.
     * @param length 対象のデータ長を設定します.
     */
    public void putNariveBuffer( int index,NativeBuffer buf,int offset,int length ) {
        if( this.address == 0L ) {
            throw new IllegalStateException( "既にクリアされています" ) ;
        }
        else if (buf == null) {
            throw new NullPointerException();
        }
        else if( index < 0 || index + length > this.length ) {
            throw new IllegalArgumentException( "範囲外のインデックス値が設定されています(index:" +
                index + " memoryLength:" + this.length + ")" ) ;
        }
        else if ((offset < 0) || (offset > buf.getLength()) || (length < 0) ||
            ((offset + length) > buf.getLength()) || ((offset + length) < 0)) {
            throw new IndexOutOfBoundsException( "範囲外の値が設定されています(index:" +
                index + " memoryLength:" + this.length + " offset:" + offset + " length:" + length + ")" ) ;
        }
        else if (length <= 0) {
            return ;
        }
        check(true) ;
        DirectMemoryIO.memcpy( address+index,buf.getAddress()+offset,length ) ;
    }
    
    /**
     * boolean設定.
     * @param index 対象のインデックス位置を設定します.
     * @param value 設定対象の情報を設定します.
     */
    public void putBoolean( final int index,final boolean value ) {
        if( this.address == 0L ) {
            throw new IllegalStateException( "既にクリアされています" ) ;
        }
        if( index < 0 || index >= length ) {
            throw new IllegalArgumentException( "範囲外のインデックス値が設定されています(index:" +
                index + " memoryLength:" + length + ")" ) ;
        }
        check(true) ;
        if( UnsafeMode ) {
            unsafe.putByte( address+index,((value)?(byte)1:(byte)0) ) ;
        }
        else {
            NativeIO.putByte( address+index,((value)?(byte)1:(byte)0) ) ;
        }
    }
    
    /**
     * boolean取得.
     * @param index 対象のインデックス位置を設定します.
     * @return boolean 情報が返されます.
     */
    public boolean getBoolean( final int index ) {
        if( this.address == 0L ) {
            throw new IllegalStateException( "既にクリアされています" ) ;
        }
        if( index < 0 || index >= length ) {
            throw new IllegalArgumentException( "範囲外のインデックス値が設定されています(index:" +
                index + " memoryLength:" + length + ")" ) ;
        }
        check(false) ;
        if( UnsafeMode ) {
            return ( unsafe.getByte( address+index ) == 0 ) ? false : true ;
        }
        return ( NativeIO.getByte( address+index ) == 0 ) ? false : true ;
    }
    
    /**
     * char設定.
     * @param index 対象のインデックス位置を設定します.
     * @param value 設定対象の情報を設定します.
     */
    public void putChar( final int index,char value ) {
        if( this.address == 0L ) {
            throw new IllegalStateException( "既にクリアされています" ) ;
        }
        if( index < 0 || index+1 >= length ) {
            throw new IllegalArgumentException( "範囲外のインデックス値が設定されています(index:" +
                index + " memoryLength:" + length + ")" ) ;
        }
        check(true) ;
        if( !BIG_ENDIAN ) {
            value = Unsafe.swap( value ) ;
        }
        if( UnsafeMode ) {
            unsafe.putChar( address+index,value ) ;
        }
        else {
            NativeIO.putChar( address+index,value ) ;
        }
    }
    
    /**
     * char取得.
     * @param index 対象のインデックス位置を設定します.
     * @return char 情報が返されます.
     */
    public char getChar( final int index ) {
        if( this.address == 0L ) {
            throw new IllegalStateException( "既にクリアされています" ) ;
        }
        if( index < 0 || index+1 >= length ) {
            throw new IllegalArgumentException( "範囲外のインデックス値が設定されています(index:" +
                index + " memoryLength:" + length + ")" ) ;
        }
        check(false) ;
        if( UnsafeMode ) {
            if( BIG_ENDIAN ) {
                return unsafe.getChar( address+index ) ;
            }
            return Unsafe.swap( unsafe.getChar( address+index ) ) ;
        }
        if( BIG_ENDIAN ) {
            return NativeIO.getChar( address+index ) ;
        }
        return Unsafe.swap( NativeIO.getChar( address+index ) ) ;
    }
    
    /**
     * short設定.
     * @param index 対象のインデックス位置を設定します.
     * @param value 設定対象の情報を設定します.
     */
    public void putShort( final int index,short value ) {
        if( this.address == 0L ) {
            throw new IllegalStateException( "既にクリアされています" ) ;
        }
        if( index < 0 || index+1 >= length ) {
            throw new IllegalArgumentException( "範囲外のインデックス値が設定されています(index:" +
                index + " memoryLength:" + length + ")" ) ;
        }
        check(true) ;
        if( !BIG_ENDIAN ) {
            value = Unsafe.swap( value ) ;
        }
        if( UnsafeMode ) {
            unsafe.putShort( address+index,value ) ;
        }
        else {
            NativeIO.putShort( address+index,value ) ;
        }
    }
    
    /**
     * short取得.
     * @param index 対象のインデックス位置を設定します.
     * @return short 情報が返されます.
     */
    public short getShort( final int index ) {
        if( this.address == 0L ) {
            throw new IllegalStateException( "既にクリアされています" ) ;
        }
        if( index < 0 || index+1 >= length ) {
            throw new IllegalArgumentException( "範囲外のインデックス値が設定されています(index:" +
                index + " memoryLength:" + length + ")" ) ;
        }
        check(false) ;
        if( UnsafeMode ) {
            if( BIG_ENDIAN ) {
                return unsafe.getShort( address+index ) ;
            }
            return Unsafe.swap( unsafe.getShort( address+index ) ) ;
        }
        if( BIG_ENDIAN ) {
            return NativeIO.getShort( address+index ) ;
        }
        return Unsafe.swap( NativeIO.getShort( address+index ) ) ;
    }
    
    /**
     * int設定.
     * @param index 対象のインデックス位置を設定します.
     * @param value 設定対象の情報を設定します.
     */
    public void putInt( final int index,int value ) {
        if( this.address == 0L ) {
            throw new IllegalStateException( "既にクリアされています" ) ;
        }
        if( index < 0 || index+3 >= length ) {
            throw new IllegalArgumentException( "範囲外のインデックス値が設定されています(index:" +
                index + " memoryLength:" + length + ")" ) ;
        }
        check(true) ;
        if( !BIG_ENDIAN ) {
            value = Unsafe.swap( value ) ;
        }
        if( UnsafeMode ) {
            unsafe.putInt( address+index,value ) ;
        }
        else {
            NativeIO.putInt( address+index,value ) ;
        }
    }
    
    /**
     * int取得.
     * @param index 対象のインデックス位置を設定します.
     * @return int 情報が返されます.
     */
    public int getInt( final int index ) {
        if( this.address == 0L ) {
            throw new IllegalStateException( "既にクリアされています" ) ;
        }
        if( index < 0 || index+3 >= length ) {
            throw new IllegalArgumentException( "範囲外のインデックス値が設定されています(index:" +
                index + " memoryLength:" + length + ")" ) ;
        }
        check(false) ;
        if( UnsafeMode ) {
            if( BIG_ENDIAN ) {
                return unsafe.getInt( address+index ) ;
            }
            return Unsafe.swap( unsafe.getInt( address+index ) ) ;
        }
        if( BIG_ENDIAN ) {
            return NativeIO.getInt( address+index ) ;
        }
        return Unsafe.swap( NativeIO.getInt( address+index ) ) ;
    }
    
    /**
     * long設定.
     * @param index 対象のインデックス位置を設定します.
     * @param value 設定対象の情報を設定します.
     */
    public void putLong( final int index,long value ) {
        if( this.address == 0L ) {
            throw new IllegalStateException( "既にクリアされています" ) ;
        }
        if( index < 0 || index+7 >= length ) {
            throw new IllegalArgumentException( "範囲外のインデックス値が設定されています(index:" +
                index + " memoryLength:" + length + ")" ) ;
        }
        check(true) ;
        if( !BIG_ENDIAN ) {
            value = Unsafe.swap( value ) ;
        }
        if( UnsafeMode ) {
            unsafe.putLong( address+index,value ) ;
        }
        else {
            NativeIO.putLong( address+index,value ) ;
        }
    }
    
    /**
     * long取得.
     * @param index 対象のインデックス位置を設定します.
     * @return long 情報が返されます.
     */
    public long getLong( final int index ) {
        if( this.address == 0L ) {
            throw new IllegalStateException( "既にクリアされています" ) ;
        }
        if( index < 0 || index+7 >= length ) {
            throw new IllegalArgumentException( "範囲外のインデックス値が設定されています(index:" +
                index + " memoryLength:" + length + ")" ) ;
        }
        check(false) ;
        if( UnsafeMode ) {
            if( BIG_ENDIAN ) {
                return unsafe.getLong( address+index ) ;
            }
            return Unsafe.swap( unsafe.getLong( address+index ) ) ;
        }
        if( BIG_ENDIAN ) {
            return NativeIO.getLong( address+index ) ;
        }
        return Unsafe.swap( NativeIO.getLong( address+index ) ) ;
    }
    
    /**
     * float設定.
     * @param index 対象のインデックス位置を設定します.
     * @param value 設定対象の情報を設定します.
     */
    public void putFloat( final int index,final float value ) {
        putInt( index,Float.floatToIntBits( value ) ) ;
    }
    
    /**
     * float取得.
     * @param index 対象のインデックス位置を設定します.
     * @return float 情報が返されます.
     */
    public float getFloat( final int index ) {
        return Float.intBitsToFloat( getInt( index ) ) ;
    }
    
    /**
     * double設定.
     * @param index 対象のインデックス位置を設定します.
     * @param value 設定対象の情報を設定します.
     */
    public void putDouble( final int index,final double value ) {
        putLong( index,Double.doubleToLongBits( value ) ) ;
    }
    
    /**
     * double取得.
     * @param index 対象のインデックス位置を設定します.
     * @return double 情報が返されます.
     */
    public double getDouble( final int index ) {
        return Double.longBitsToDouble( getLong( index ) ) ;
    }
    
    /**
     * IndexOf.
     * @param binary 検索対象バイナリを設定します.
     * @return int 操作位置が返されます.
     */
    public int indexOf( final byte[] binary ) {
        return indexOf( binary,0 ) ;
    }
    
    /**
     * IndexOf.
     * @param binary 検索対象バイナリを設定します.
     * @param index 対象の開始位置を設定します.
     * @return int 操作位置が返されます.
     */
    public int indexOf( final byte[] binary,final int index ) {
        if( this.address == 0L ) {
            throw new IllegalStateException( "既にクリアされています" ) ;
        }
        int len ;
        if( binary == null || ( len = binary.length ) <= 0 ) {
            return -1 ;
        }
        else if( index < 0 || index + len > this.length ) {
            return -1 ;
        }
        check(false) ;
        int ret = NativeIO.indexOf( address+index,this.length-index,binary,len ) ;
        if( ret == -1 ) {
            return -1 ;
        }
        return ret + index ;
    }
    
    /**
     * IndexOf.
     * @param binary 検索対象バイナリを設定します.
     * @param index 対象の開始位置を設定します.
     * @param length 対象の長さを設定します.
     * @return int 操作位置が返されます.
     */
    public int indexOf( final byte[] binary,final int index,final int length ) {
        if( this.address == 0L ) {
            throw new IllegalStateException( "既にクリアされています" ) ;
        }
        int len ;
        if( binary == null || ( len = binary.length ) <= 0 ) {
            return -1 ;
        }
        else if( length <= 0 || index < 0 || index + len > index + length || index + length > this.length ) {
            return -1 ;
        }
        check(false) ;
        int ret = NativeIO.indexOf( address+index,length,binary,len ) ;
        if( ret == -1 ) {
            return -1 ;
        }
        return ret + index ;
    }
    
    /**
     * lastIndexOf.
     * @param binary 操作対象のデータを設定します.
     * @return int 操作位置が返されます.
     */
    public int lastIndexOf( final byte[] binary ) {
        return lastIndexOf( binary,this.length-1 ) ;
    }
    
    /**
     * lastIndexOf.
     * @param binary 操作対象のデータを設定します.
     * @param index 対象の開始位置を設定します.
     * @return int 操作位置が返されます.
     */
    public int lastIndexOf( final byte[] binary,int index ) {
        if( this.address == 0L ) {
            throw new IllegalStateException( "既にクリアされています" ) ;
        }
        int len ;
        if( binary == null || ( len = binary.length ) <= 0 ) {
            return -1 ;
        }
        else if( index >= this.length ) {
            index = length-1 ;
        }
        else if( index - len < 0 ) {
            return -1 ;
        }
        check(false) ;
        return NativeIO.lastIndexOf( address,index,this.length,binary,len ) ;
    }
    
    /**
     * 指定位置から長さまでのメモリを先頭アドレスに移動.
     * @param index 対象のインデックス値を設定します.
     * @param length 対象の長さを設定します.
     * @return boolean [true]の場合、成功しました.
     */
    public boolean compact( final int index,final int length ) {
        if( this.address == 0L ) {
            throw new IllegalStateException( "既にクリアされています" ) ;
        }
        if( length <= 0 || index <= 0 || index+length >= this.length ) {
            return false ;
        }
        check(true) ;
        NativeIO.compact( address,this.length,index,length ) ;
        return true ;
    }
    
    /**
     * Javaバイナリ変換.
     * @return byte[] 変換されたJavaバイナリ情報が返されます.
     */
    public byte[] toBinary() {
        return toBinary( 0 ) ;
    }
    
    /**
     * Javaバイナリ変換.
     * @param len 変換先のデータ長を設定します.
     * @return byte[] 変換されたJavaバイナリ情報が返されます.
     */
    public byte[] toBinary( int len ) {
        if( address == 0L ) {
            return null ;
        }
        if( len <= 0 || len > length ) {
            len = length ;
        }
        byte[] ret = new byte[ len ] ;
        NativeIO.getBinary( address,ret,0,len ) ;
        return ret ;
    }
}

