package org.maachang.jni.io;

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

/**
 * Lzo圧縮処理.
 * 
 * @version 2011/01/04
 * @author  masahito suzuki
 * @since   SeabassNativeIO-1.0.2
 */
public class LzoOutputStream extends OutputStream {
    protected static final byte[] MAGIC = new byte[]{(byte)'@',(byte)'L',(byte)'Z',(byte)'O',(byte)'F'} ;
    protected static final int MAGIC_LENGTH = MAGIC.length ;
    protected static final int WRK_BUFFER_LENGTH = 0x00010000 ;
    
    private static final int NBUFFER_LENGTH = 65535 ;
    private static final int SRC_BUFFER_LENGTH = 32767 ;
    private OutputStream parent = null ;
    private boolean finishFlag = false ;
    private boolean firstFlag = true ;
    
    private long _addr = 0L ;
    private long _srcAddr = 0L ;
    private int _srcPos = 0 ;
    private byte[] _tmp = null ;
    
    private long _workMem = 0L ;
    private int[] _wrk = new int[ 1 ] ;
    private int _adler32 = 0 ;
    
    private LzoOutputStream() {
        
    }
    
    /**
     * コンストラクタ.
     * @param parent 親OutputStreamを設定します.
     */
    public LzoOutputStream( OutputStream parent ) {
        if( parent == null ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        this.parent = parent ;
        _addr = DirectMemoryIO.malloc( NBUFFER_LENGTH ) ;
        _srcAddr = DirectMemoryIO.malloc( SRC_BUFFER_LENGTH ) ;
        _workMem = DirectMemoryIO.malloc( WRK_BUFFER_LENGTH ) ;
        _tmp = new byte[ NBUFFER_LENGTH ] ;
    }
    
    /** デストラクタ. **/
    protected void finalize() throws Exception {
        try {
            close() ;
        } catch( Exception e ) {
        }
    }
    
    /**
     * クローズ処理.
     * @exception Exception 例外.
     */
    public void close() throws IOException {
        if( _workMem == 0L ) {
            return ;
        }
        finish() ;
        DirectMemoryIO.free( _addr ) ;
        _addr = 0L ;
        DirectMemoryIO.free( _srcAddr ) ;
        _srcAddr = 0L ;
        DirectMemoryIO.free( _workMem ) ;
        _workMem = 0L ;
        _tmp = null ;
        parent.close() ;
    }
    
    /**
     * 終端処理.
     * <p>この処理で、LZO圧縮を終了させます.</p>
     * @exception Exception 例外.
     */
    public void finish() throws IOException {
        if( finishFlag ) {
            return ;
        }
        flush() ;
        DirectMemoryIO.putShort( _addr,0,(short)0 ) ;
        DirectMemoryIO.putInt( _addr+2,0,_adler32 ) ;
        DirectMemoryIO.getBinary( _addr,0,_tmp,0,6 ) ;
        parent.write( _tmp,0,6 ) ;
        parent.flush() ;
        finishFlag = true ;
    }
    
    /**
     * 更新.
     * @exception IOException I/O例外.
     */
    public void flush() throws IOException {
        if( _workMem == 0L ) {
            throw new IOException( "既にクローズしています" ) ;
        }
        if( _srcPos <= 0 || finishFlag ) {
            return ;
        }
        int res = NativeIO.lzo1xCompress( _srcAddr,_srcPos,_addr+2L,_wrk,_workMem ) ;
        if( res != 0 ) {
            throw new IOException( "LZO圧縮処理に失敗:" + res ) ;
        }
        _adler32 = NativeIO.adler32( _adler32,_srcAddr,_srcPos ) ;
        DirectMemoryIO.putShort( _addr,0,(short)_wrk[0] ) ;
        DirectMemoryIO.getBinary( _addr,0,_tmp,0,_wrk[0]+2 ) ;
        if( firstFlag ) {
            parent.write( MAGIC,0,MAGIC_LENGTH ) ;
            firstFlag = false ;
        }
        parent.write( _tmp,0,_wrk[0]+2 ) ;
        _srcPos = 0 ;
    }
    
    /**
     * 書き込み処理.
     * @param b 書き込み情報を設定します.
     * @exception IOException I/O例外.
     */
    public void write(int b) throws IOException {
        if( _workMem == 0L || finishFlag ) {
            if( _workMem == 0L ) {
                throw new IOException( "既にクローズしています" ) ;
            }
            else {
                throw new IOException( "既に終了しています" ) ;
            }
        }
        if( _srcPos >= SRC_BUFFER_LENGTH ) {
            flush() ;
        }
        DirectMemoryIO.put( _srcAddr,_srcPos,(byte)b ) ;
        _srcPos ++ ;
    }
    
    /**
     * 書き込み処理.
     * @param b 対象のバイナリを設定します.
     * @exception IOException I/O例外.
     */
    public void write(byte[] b) throws IOException {
        if( _workMem == 0L || finishFlag ) {
            if( _workMem == 0L ) {
                throw new IOException( "既にクローズしています" ) ;
            }
            else {
                throw new IOException( "既に終了しています" ) ;
            }
        }
        if (b == null) {
            throw new NullPointerException( "バイナリが存在しません" );
        }
        write( b,0,b.length ) ;
    }
    
    /**
     * 書き込み処理.
     * @param b 対象のバイナリを設定します.
     * @exception IOException I/O例外.
     */
    public void write(byte b[], int off, int len) throws IOException {
        if( _workMem == 0L || finishFlag ) {
            if( _workMem == 0L ) {
                throw new IOException( "既にクローズしています" ) ;
            }
            else {
                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 > ( SRC_BUFFER_LENGTH - _srcPos ) ) {
            int plen = ( SRC_BUFFER_LENGTH - _srcPos ) ;
            while( true ) {
                DirectMemoryIO.putBinary( _srcAddr,_srcPos,b,off,plen ) ;
                _srcPos += plen ;
                if( _srcPos == SRC_BUFFER_LENGTH ) {
                    flush() ;
                }
                else {
                    _srcPos = plen ;
                    break ;
                }
                off += plen ;
                len -= plen ;
                if( len < SRC_BUFFER_LENGTH ) {
                    plen = len ;
                }
                else {
                    plen = SRC_BUFFER_LENGTH ;
                }
            }
        }
        else {
            DirectMemoryIO.putBinary( _srcAddr,_srcPos,b,off,len ) ;
            _srcPos += len ;
        }
    }
    
    /**
     * finish処理が呼ばれたかチェック.
     * @return boolean [true]の場合、finish処理が呼び出されています.
     */
    public boolean isFinish() {
        return finishFlag ;
    }
    
    /**
     * クローズされているかチェック.
     * @return boolean [true]の場合、クローズしています.
     */
    public boolean isClose() {
        return _workMem == 0L ;
    }
}
