package org.maachang.jni.io;

import java.io.IOException;
import java.io.OutputStream;

/**
 * メモリOutputStream.
 * 
 * @version 2011/01/09
 * @author  masahito suzuki
 * @since   SeabassNativeIO-1.0.2
 */
public class MemoryOutputStream extends OutputStream {
    
    private static final int DEFAULT_MEMORY = 4096 ;
    private static final int OUTPUT_INSERT_LENGTH = 512 ;
    private FastMemoryBuffer nBuf ;
    private int offset ;
    private int length ;
    
    /**
     * コンストラクタ.
     */
    public MemoryOutputStream() {
        nBuf = new FastMemoryBuffer( DEFAULT_MEMORY ) ;
        offset = 0 ;
        length = DEFAULT_MEMORY ;
    }
    
    /**
     * コンストラクタ.
     * @param size バッファサイズを設定します.
     */
    public MemoryOutputStream( int size ) {
        if( size <= 16 ) {
            size = DEFAULT_MEMORY ;
        }
        nBuf = new FastMemoryBuffer( size ) ;
        offset = 0 ;
        length = size ;
    }
    
    /**
     * デストラクタ.
     */
    protected void finalize() throws Exception {
        try {
            close() ;
        } catch( Exception e ) {
        }
    }
    
    /**
     * クローズ処理.
     * @exception IOException I/O例外.
     */
    public void close() throws IOException {
        nBuf = null ;
    }
    
    /**
     * 書き込み処理.
     * @exception IOException I/O例外.
     */
    public void flush() throws IOException {
        if( nBuf == null ) {
            throw new IOException( "既にクローズしています" ) ;
        }
    }
    
    /** バッファ増加. **/
    private void addBuffer( int n ) {
        int len = (int)( (double)( length + n ) * 1.5d ) ;
        DirectMemoryIO.realloc( nBuf.address,len,length ) ;
        nBuf.length = len ;
        length = len ;
    }
    
    /**
     * 書き込み処理.
     * @param b 書き込み情報を設定します.
     * @exception IOException I/O例外.
     */
    public void write(int b) throws IOException {
        if( nBuf == null ) {
            throw new IOException( "既にクローズしています" ) ;
        }
        if( length <= offset ) {
            addBuffer( 1 ) ;
        }
        nBuf.putByte( offset++,(byte)b ) ;
    }
    
    /**
     * 書き込み処理.
     * @param b 対象のバイナリを設定します.
     * @exception IOException I/O例外.
     */
    public void write(byte[] b) throws IOException {
        if (b == null) {
            throw new NullPointerException( "バイナリが存在しません" );
        }
        write( b,0,b.length ) ;
    }
    
    /**
     * 書き込み処理.
     * @param b 対象のバイナリを設定します.
     * @param off 対象のオフセット値を設定します.
     * @param len 対象の長さを設定します.
     * @exception IOException I/O例外.
     */
    public void write(byte b[], int off, int len) throws IOException {
        if( nBuf == null ) {
            throw new IOException( "既にクローズしています" ) ;
        }
        if (b == null) {
            throw new NullPointerException( "バイナリが存在しません" );
        } else if ((off < 0) || (off > b.length) || (len < 0) ||
               ((off + len) > b.length) || ((off + len) < 0)) {
            throw new IndexOutOfBoundsException( "指定範囲が不正です" );
        } else if (len == 0) {
            return;
        }
        if( length <= offset ) {
            addBuffer( len ) ;
        }
        nBuf.putBinary( offset,b,off,len ) ;
        offset += len ;
    }
    
    /**
     * 指定OutputStreamに現在のバッファ内容をセット.
     * @param out 対象のOutputStreamを設定します.
     * @exception IOException I/O例外.
     */
    public void writeTo(OutputStream out) throws IOException {
        if( nBuf == null ) {
            throw new IOException( "既にクローズしています" ) ;
        }
        if( out == null ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        byte[] b = new byte[ OUTPUT_INSERT_LENGTH ] ;
        int off = 0 ;
        int len = offset ;
        int one ;
        while( true ) {
            if( len <= 0 ) {
                break ;
            }
            if( len > OUTPUT_INSERT_LENGTH ) {
                one = OUTPUT_INSERT_LENGTH ;
            }
            else {
                one = len ;
            }
            nBuf.getBinary( off,b,0,one ) ;
            out.write( b,0,one ) ;
            off += one ;
            len -= one ;
        }
    }
    
    /**
     * 現在の情報をバイナリ変換.
     * @return byte[] バイナリ情報が返されます.
     * @exception IOException I/O例外.
     */
    public byte[] toByteArray() throws IOException {
        if( nBuf == null ) {
            throw new IOException( "既にクローズしています" ) ;
        }
        byte[] ret = new byte[ offset ] ;
        nBuf.getBinary( 0,ret,0,offset ) ;
        return ret ;
    }
    
    /**
     * NativeBufferを返却.
     * @return NativeBuffer NativeBufferが返されます.
     * @exception IOException I/O例外.
     */
    public NativeBuffer toNativeBuffer() throws IOException {
        if( nBuf == null ) {
            throw new IOException( "既にクローズしています" ) ;
        }
        DirectMemoryIO.realloc( nBuf.address,offset,nBuf.length ) ;
        nBuf.length = offset ;
        length = offset ;
        return nBuf ;
    }
}
