package org.maachang.jni.io ;

import java.nio.BufferOverflowException;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.ReadOnlyBufferException;

/**
 * BufferI/O.
 * 
 * @version 2010/07/14
 * @author  masahito suzuki
 * @since   SeabassNativeIO-1.0.2
 */
public class BufferIO {
    protected static final boolean UnsafeMode = Unsafe.UNSAFE_MODE ;
    protected static final sun.misc.Unsafe unsafe = Unsafe.unsafe ;
    private ByteBuffer origin ;
    private int position ;
    private int limit ;
    private int capacity ;
    private long addr ;
    private boolean readOnly ;
    
    /**
     * コンストラクタ.
     */
    private BufferIO() {
        
    }
    
    /**
     * コンストラクタ.
     * @param len 指定長のByteBufferラッパを設定します.
     */
    public BufferIO( int len ) {
        init( len ) ;
        readOnly = false ;
    }
    
    /**
     * コンストラクタ.
     * @param buf 指定ByteBufferのByteBufferラッパーを設定します.
     */
    public BufferIO( ByteBuffer buf ) {
        init( buf ) ;
        readOnly = false ;
    }
    
    /**
     * コンストラクタ.
     * <p>読み込み専用のByteBufferラッパーを設定します.</p>
     * @param bin 対象のバイナリ情報を設定します.
     */
    public BufferIO( byte[] bin ) {
        if( bin == null ) {
            throw new NullPointerException() ;
        }
        if( bin.length <= 0 ) {
            throw new IllegalArgumentException( "長さゼロのバイナリが設定されています" ) ;
        }
        init( bin.length ) ;
        NativeIO.putBinary( addr,bin,0,bin.length ) ;
        readOnly = true ;
    }
    
    /** 初期化処理. **/
    private void init( int len ) {
        if( len <= 0 ) {
            throw new IllegalArgumentException( "長さはゼロ以上を設定してください" ) ;
        }
        try {
            origin = ByteBuffer.allocateDirect( len ) ;
            addr = Unsafe.getDirectByteBufferNativeAddress( origin ) ;
            position = 0 ;
            limit = origin.capacity() ;
            capacity = limit ;
        } catch( RuntimeException re ) {
            throw re ;
        } catch( Exception e ) {
            throw new RuntimeException( e ) ;
        }
    }
    
    /** 初期化処理. **/
    private void init( ByteBuffer buf ) {
        if( buf == null ) {
            throw new NullPointerException() ;
        }
        else if( !buf.isDirect() ) {
            throw new IllegalArgumentException( "指定ByteBufferはダイレクトバッファを設定してください" ) ;
        }
        try {
            origin = buf ;
            addr = Unsafe.getDirectByteBufferNativeAddress( origin ) ;
            position = 0 ;
            limit = origin.capacity() ;
            capacity = limit ;
        } catch( RuntimeException re ) {
            throw re ;
        } catch( Exception e ) {
            throw new RuntimeException( e ) ;
        }
    }
    
    /**
     * ByteBufferオブジェクトを取得.
     * @return ByteBufferオブジェクトが返されます.
     */
    public ByteBuffer flush() {
        if( readOnly ) {
            return origin ;
        }
        origin.position( position ) ;
        origin.limit( limit ) ;
        return origin ;
    }
    
    /**
     * FlipしてByteBufferオブジェクトを取得.
     * @return ByteBufferオブジェクトが返されます.
     */
    public ByteBuffer flipFlush() {
        if( readOnly ) {
            return origin ;
        }
        origin.position( 0 ) ;
        origin.limit( position ) ;
        return origin ;
    }
    
    /**
     * ByteBuffer側の同期を取る.
     * @return BufferIO このオブジェクトが返されます.
     */
    public BufferIO sync() {
        position = origin.position() ;
        limit = origin.limit() ;
        return this ;
    }
    
    /**
     * ByteBufferを取得.
     * @return ByteBufferが返されます.
     */
    public ByteBuffer getByteBuffer() {
        if( readOnly ) {
            origin.position( 0 ) ;
            origin.limit( origin.capacity() ) ;
        }
        return origin ;
    }
    
    /**
     * オブジェクトクリア.
     * @return BufferIO このオブジェクトが返されます.
     */
    public BufferIO clear() {
        if( readOnly ) {
            return this ;
        }
        position = 0 ;
        limit = capacity ;
        return this ;
    }
    
    /**
     * Flip処理.
     * <p>java.nio.Buffer.flipと同処理.</p>
     * @return BufferIO このオブジェクトが返されます.
     */
    public BufferIO flip() {
        limit = position ;
        position = 0 ;
        return this ;
    }
    
    /**
     * capacity取得.
     * @return int capacity値が返されます.
     */
    public int capacity() {
        return capacity ;
    }
    
    /**
     * 読み込み専用オブジェクトがチェック.
     * @return boolean [true]の場合、読み込み専用です.
     */
    public boolean isReadOnly() {
        return readOnly ;
    }
    
    /**
     * 有効データが存在するかチェック.
     * @return boolean [true]の場合は、データが存在します.
     */
    public boolean hasRemaining() {
        return limit > position ;
    }
    
    /**
     * 有効データ長を取得.
     * @return int 有効データ長が返されます.
     */
    public int remaining() {
        return limit - position ;
    }
    
    /**
     * 現在のリミット値を取得.
     * @return int 現在のリミット値が返されます.
     */
    public int limit() {
        return limit ;
    }
    
    /**
     * リミット値を設定.
     * <p>読み込み専用の場合は、処理はスルーされます.</p>
     * @param n 新しいリミット値を設定します.
     * @return BufferIO このオブジェクトが返されます.
     */
    public BufferIO limit( int n ) {
        if( readOnly ) {
            return this ;
        }
        if( n < 0 ) {
            n = 0 ;
        }
        else if( n > capacity ) {
            n = capacity ;
        }
        limit = n ;
        if( limit < position ) {
            position = limit ;
        }
        return this ;
    }
    
    /**
     * 現在のポジションを取得.
     * @return int 現在のポジションが返されます.
     */
    public int position() {
        return position ;
    }
    
    /**
     * ポジション値を設定.
     * <p>読み込み専用の場合は、処理はスルーされます.</p>
     * @param n 新しいポジション値を設定します.
     * @return BufferIO このオブジェクトが返されます.
     */
    public BufferIO position( int n ) {
        if( readOnly ) {
            return this ;
        }
        if( n < 0 ) {
            n = 0 ;
        }
        else if( n > limit ) {
            n = limit ;
        }
        position = n ;
        return this ;
    }
    
    /**
     * 現在のバッファを圧縮します.
     * <p>読み込み専用の場合は、例外が発生します.</p>
     * @return BufferIO このオブジェクトが返されます.
     */
    public BufferIO compact() {
        if( readOnly ) {
            throw new ReadOnlyBufferException() ;
        }
        if( limit == position ) {
            limit = 0 ;
            position = 0 ;
            return this ;
        }
        else if( position == 0 ) {
            return this ;
        }
        NativeIO.compact( addr,capacity,position,limit ) ;
        limit = limit - position ;
        position = 0 ;
        return this ;
    }
    
    /**
     * １バイト読み込み.
     * <p>読み込み専用の場合は、例外が発生します.</p>
     * @return byte 読み込まれた１バイト情報が返されます.
     */
    public byte get() {
        if( readOnly ) {
            throw new ReadOnlyBufferException() ;
        }
        if( position >= limit ) {
            throw new BufferOverflowException() ;
        }
        byte ret ;
        if( UnsafeMode ) {
            ret = unsafe.getByte( addr+position ) ;
        }
        else {
            ret = NativeIO.getByte( addr+position ) ;
        }
        position ++ ;
        return ret ;
    }
    
    /**
     * 指定位置の１バイト情報を読み込み.
     * @param index 読み込み位置を指定します.
     * @return byte 読み込まれた１バイト情報が返されます.
     */
    public byte get(int index) {
        if( index < 0 || index >= capacity ) {
            throw new IndexOutOfBoundsException() ;
        }
        if( UnsafeMode ) {
            return unsafe.getByte( addr+index ) ;
        }
        return NativeIO.getByte( addr+index ) ;
    }
    
    /**
     * バイナリ情報を読み込み.
     * <p>読み込み専用の場合は、例外が発生します.</p>
     * @param dst 読み込みを行うバイナリ情報を設定します.
     * @return BufferIO このオブジェクトが返されます.
     */
    public BufferIO get(byte[] dst) {
        if( readOnly ) {
            throw new ReadOnlyBufferException() ;
        }
        if( dst == null ) {
            throw new NullPointerException() ;
        }
        else if( dst.length <= 0 ) {
            return this ;
        }
        else if( limit - position < dst.length ) {
            throw new BufferUnderflowException() ;
        }
        NativeIO.getBinary( addr+position,dst,0,dst.length ) ;
        position += dst.length ;
        return this ;
    }
    
    /**
     * バイナリ情報を読み込み.
     * <p>読み込み専用の場合は、例外が発生します.</p>
     * @param dst 読み込みを行うバイナリ情報を設定します.
     * @param length 対象の長さを設定します.
     * @return BufferIO このオブジェクトが返されます.
     */
    public BufferIO get(byte[] dst, int length) {
        return get( dst,0,length ) ;
    }
    
    /**
     * バイナリ情報を読み込み.
     * <p>読み込み専用の場合は、例外が発生します.</p>
     * @param dst 読み込みを行うバイナリ情報を設定します.
     * @param offset 対象のオフセット値を設定します.
     * @param length 対象の長さを設定します.
     * @return BufferIO このオブジェクトが返されます.
     */
    public BufferIO get(byte[] dst, int offset, int length) {
        if( readOnly ) {
            throw new ReadOnlyBufferException() ;
        }
        if( dst == null ) {
            throw new NullPointerException() ;
        }
        else if( position < 0 || position + length > limit ) {
            throw new BufferUnderflowException() ;
        }
        else if ((offset < 0) || (offset > dst.length) || (length < 0) ||
            ((offset + length) > dst.length) || ((offset + length) < 0)) {
            throw new IndexOutOfBoundsException( "範囲外の値が設定されています" ) ;
        }
        else if (length <= 0) {
            return this ;
        }
        NativeIO.getBinary( addr+position,dst,offset,length ) ;
        position += offset ;
        return this ;
    }
    
    /**
     * １バイト書き込み.
     * <p>読み込み専用の場合は、例外が発生します.</p>
     * @param b 書き込み対象の１バイト情報を設定します.
     * @return BufferIO このオブジェクトが返されます.
     */
    public BufferIO put(byte b) {
        if( readOnly ) {
            throw new ReadOnlyBufferException() ;
        }
        if( position >= limit ) {
            throw new BufferOverflowException() ;
        }
        if( UnsafeMode ) {
            unsafe.putByte( addr+position,b ) ;
        }
        else {
            NativeIO.putByte( addr+position,b ) ;
        }
        position ++ ;
        return this ;
    }
    
    /**
     * １バイト書き込み.
     * <p>読み込み専用の場合は、例外が発生します.</p>
     * @param index 書き込み先の位置を設定します.
     * @param b 書き込み対象の１バイト情報を設定します.
     * @return BufferIO このオブジェクトが返されます.
     */
    public BufferIO put(int index, byte b) {
        if( readOnly ) {
            throw new ReadOnlyBufferException() ;
        }
        if( index < 0 || index >= capacity ) {
            throw new IndexOutOfBoundsException() ;
        }
        if( UnsafeMode ) {
            unsafe.putByte( addr+index,b ) ;
        }
        else {
            NativeIO.putByte( addr+index,b ) ;
        }
        return this ;
    }
    
    /**
     * バイナリ情報を書き込み.
     * <p>読み込み専用の場合は、例外が発生します.</p>
     * @param src 書き込み対象のバイナリを設定します.
     * @return BufferIO このオブジェクトが返されます.
     */
    public BufferIO put(byte[] src) {
        if( readOnly ) {
            throw new ReadOnlyBufferException() ;
        }
        if( src == null ) {
            throw new NullPointerException() ;
        }
        else if( src.length <= 0 ) {
            return this ;
        }
        else if( limit - position < src.length ) {
            throw new BufferOverflowException() ;
        }
        NativeIO.putBinary( addr+position,src,0,src.length ) ;
        position += src.length ;
        return this ;
    }
    
    /**
     * バイナリ情報を書き込み.
     * <p>読み込み専用の場合は、例外が発生します.</p>
     * @param src 書き込み対象のバイナリを設定します.
     * @param length 対象の長さを設定します.
     * @return BufferIO このオブジェクトが返されます.
     */
    public BufferIO put(byte[] src, int length) {
        return put( src,0,length ) ;
    }
    
    /**
     * バイナリ情報を書き込み.
     * <p>読み込み専用の場合は、例外が発生します.</p>
     * @param src 書き込み対象のバイナリを設定します.
     * @param offset 対象のオフセット値を設定します.
     * @param length 対象の長さを設定します.
     * @return BufferIO このオブジェクトが返されます.
     */
    public BufferIO put(byte[] src, int offset, int length) {
        if( readOnly ) {
            throw new ReadOnlyBufferException() ;
        }
        if( src == null ) {
            throw new NullPointerException() ;
        }
        else if( position < 0 || position + length > limit ) {
            throw new BufferOverflowException() ;
        }
        else if ((offset < 0) || (offset > src.length) || (length < 0) ||
            ((offset + length) > src.length) || ((offset + length) < 0)) {
            throw new IndexOutOfBoundsException( "範囲外の値が設定されています" ) ;
        }
        else if (length <= 0) {
            return this ;
        }
        NativeIO.putBinary( addr+position,src,offset,length ) ;
        position += offset ;
        return this ;
    }
    
    /**
     * BufferIOを書き込み.
     * <p>読み込み専用の場合は、例外が発生します.</p>
     * @param src 書き込み対象のBufferIOを設定します.
     * @return BufferIO このオブジェクトが返されます.
     */
    public BufferIO put(BufferIO src) {
        if( readOnly ) {
            throw new ReadOnlyBufferException() ;
        }
        else if( src == null ) {
            throw new NullPointerException() ;
        }
        else if( this == src ) {
            throw new RuntimeException( "同一オブジェクトは指定できません" ) ;
        }
        if( src.readOnly ) {
            if( src.capacity + position > limit ) {
                throw new BufferOverflowException() ;
            }
            NativeIO.memcpy( addr+position,src.addr,src.capacity ) ;
            position += src.capacity ;
        }
        else {
            int len = src.remaining() ;
            if( len <= 0 ) {
                return this ;
            }
            if( len + position > limit ) {
                throw new BufferOverflowException() ;
            }
            NativeIO.memcpy( addr+position,src.addr+src.position,len ) ;
            position += len ;
        }
        return this ;
    }
    
    /**
     * BufferIOを書き込み.
     * <p>読み込み専用の場合は、例外が発生します.</p>
     * @param index 書き込み先頭のインデックス値を設定します.
     * @param src 書き込み対象のBufferIOを設定します.
     * @return BufferIO このオブジェクトが返されます.
     */
    public BufferIO put(int index, BufferIO src) {
        if( readOnly ) {
            throw new ReadOnlyBufferException() ;
        }
        else if( src == null ) {
            throw new NullPointerException() ;
        }
        else if( this == src ) {
            throw new RuntimeException( "同一オブジェクトは指定できません" ) ;
        }
        if( index < 0 || index >= capacity ) {
            throw new IndexOutOfBoundsException() ;
        }
        if( src.readOnly ) {
            if( src.capacity + index > capacity ) {
                throw new BufferOverflowException() ;
            }
            NativeIO.memcpy( addr+index,src.addr,src.capacity ) ;
        }
        else {
            int len = src.remaining() ;
            if( len <= 0 ) {
                return this ;
            }
            if( len + index > capacity ) {
                throw new BufferOverflowException() ;
            }
            NativeIO.memcpy( addr+index,src.addr+src.position,len ) ;
        }
        return this ;
    }
    
    /**
     * 文字列変換.
     * @return String 変換された文字列が返されます.
     */
    public String toString() {
        return new StringBuilder().append( " capacity:" ).append( capacity ).
            append( " limit:" ).append( limit ).
            append( " position:" ).append( position ).toString() ;
    }
    
}
