package org.maachang.jni.io;

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

/**
 * NativeOutputStream.
 * 
 * @version 2010/06/04
 * @author  masahito suzuki
 * @since   SeabassNativeIO-1.0.0
 */
public class NativeOutputStream extends OutputStream {
    private static final int ACCESS_MODE = NativeIODefine.MODE_SEQ ;
    private static final int IO_OPT = NativeIODefine.OPT_WRITE ;
    private static final int TEMP_LEN = 4096 ;
    private long address = 0L ;
    private int position = 0 ;
    private long handle = -1L ;
    
    private NativeOutputStream() {
        
    }
    
    /**
     * ファイルオープン.
     * @param name 対象のファイル名を設定します.
     * @exception Exception 例外.
     */
    public NativeOutputStream( String name ) throws Exception {
        this( true,name ) ;
    }
    
    /**
     * ファイルオープン.
     * @param mode [true]の場合、既存ファイルが存在した場合、削除します.
     * @param name 対象のファイル名を設定します.
     * @exception Exception 例外.
     */
    public NativeOutputStream( boolean mode,String name ) throws Exception {
        if( name == null || ( name = name.trim() ).length() <= 0 ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        name = NativeIODefine.fullPath( name ) ;
        if( mode ) {
            if( NativeIODefine.useFile( name ) ) {
                this.handle = NativeIO.open( ACCESS_MODE,NativeIODefine.OPEN_TRUNCATE,IO_OPT,NativeIODefine.nativeString( name ) ) ;
            }
            else {
                this.handle = NativeIO.open( ACCESS_MODE,NativeIODefine.OPEN_ALWAYS,IO_OPT,NativeIODefine.nativeString( name ) ) ;
            }
        }
        else {
            this.handle = NativeIO.open( ACCESS_MODE,NativeIODefine.OPEN_ALWAYS,IO_OPT,NativeIODefine.nativeString( name ) ) ;
        }
        if( this.handle == -1L ) {
            throw new IOException( "指定ファイル[" + name + "]のオープンに失敗しました" ) ;
        }
        this.address = DirectMemoryIO.malloc( TEMP_LEN ) ;
    }
    
    protected void finalize() throws Exception {
        try {
            close() ;
        } catch( Exception e ) {
        }
    }
    
    /**
     * ファイルクローズ.
     * @exception IOException I/O例外.
     */
    public void close() throws IOException {
        if( this.handle > -1L ) {
            flush() ;
            NativeIO.close( this.handle ) ;
            this.handle = -1L ;
            this.position = 0 ;
            if ( this.address != 0L ) {
                DirectMemoryIO.free( this.address ) ;
                this.address = 0L ;
            }
        }
    }
    
    /**
     * ファイル更新.
     * @exception IOException I/O例外.
     */
    public void flush() throws IOException {
        if( handle <= -1L ) {
            throw new IOException( "ファイルは既にクローズしています" ) ;
        }
        if( position > 0 ) {
            NativeIO.sWrite( handle,address,position ) ;
            position = 0 ;
        }
    }
    
    /**
     * 書き込み処理.
     * @param b 書き込み情報を設定します.
     * @exception IOException I/O例外.
     */
    public void write(int b) throws IOException {
        if( handle <= -1L ) {
            throw new IOException( "ファイルは既にクローズしています" ) ;
        }
        if( position >= TEMP_LEN ) {
            NativeIO.sWrite( handle,address,position ) ;
            position = 0 ;
        }
        DirectMemoryIO.put( address,position,(byte)b ) ;
        position ++ ;
    }
    
    /**
     * 書き込み処理.
     * @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( handle <= -1L ) {
            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( len > ( TEMP_LEN - position ) ) {
            int plen = ( TEMP_LEN - position ) ;
            while( true ) {
                DirectMemoryIO.putBinary( address,position,b,off,plen ) ;
                position += plen ;
                if( position == TEMP_LEN ) {
                    NativeIO.sWrite( handle,address,position ) ;
                    position = 0 ;
                }
                else {
                    position = plen ;
                    break ;
                }
                off += plen ;
                len -= plen ;
                if( len < TEMP_LEN ) {
                    plen = len ;
                }
                else {
                    plen = TEMP_LEN ;
                }
            }
        }
        else {
            DirectMemoryIO.putBinary( address,position,b,off,len ) ;
            position += len ;
        }
    }
    
    /**
     * NativeBufferで書き込み処理.
     * <p>※このメソッドと、通常のwriteメソッドを併用しないでください</p>
     * @param buf 対象のNativeBufferを設定します.
     * @return int 読み込みサイズが返されます.
     */
    public int writeBuffer( final NativeBuffer buf ) throws IOException {
        if( buf == null || buf.isClear() ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        if( handle <= -1L ) {
            throw new IOException( "ファイルは既にクローズしています" ) ;
        }
        return NativeIO.sWrite( handle,buf.getAddress(),buf.getLength() ) ;
    }
    
    /**
     * NativeBufferで書き込み処理.
     * <p>※このメソッドと、通常のwriteメソッドを併用しないでください</p>
     * @param buf 対象のNativeBufferを設定します.
     * @param off 対象のオフセット値を設定します.
     * @param len 対象の長さを設定します.
     * @return int 読み込みサイズが返されます.
     */
    public int writeBuffer( final NativeBuffer buf,int off,int len ) throws IOException {
        int bufLen ;
        if( buf == null || buf.isClear() || ( bufLen = buf.getLength() ) <= 0 || off < 0 ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        len = ( len <= 0 ) ? bufLen - off : len ;
        if( len <= 0 || len > bufLen ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        if( handle == -1L ) {
            throw new IOException( "オブジェクトは既にクローズしています" ) ;
        }
        return NativeIO.sWrite( handle,buf.getAddress()+off,len ) ;
    }
    
    /**
     * 現在のファイルサイズを取得.
     * @return long ファイルサイズが返されます.
     * @exception Exception 例外.
     */
    public long length() throws Exception {
        if( handle == -1L ) {
            throw new IOException( "オブジェクトは既にクローズしています" ) ;
        }
        return NativeIO.getLength( handle ) ;
    }
}
