package org.maachang.jni.io;

import java.io.IOException;

/**
 * メモリ上でのLZO圧縮処理.
 * 
 * @version 2011/01/04
 * @author  masahito suzuki
 * @since   SeabassNativeIO-1.0.0
 */
public final class CompressMemoryLzo {
    protected static final byte[] MAGIC = new byte[]{(byte)'L',(byte)'Z',(byte)'O'} ;
    protected static final int MAGIC_LENGTH = MAGIC.length ;
    protected static final int HEAD_LENGTH = MAGIC_LENGTH + 8 ;
    protected static final int CHUNKED_HEAD = 2 ;
    protected static final int DEFAULT_WRK_BUFFER_LENGTH = 0x00010000 ;
    
    protected static final int DEFAULT_BUFFER_LENGTH = 0x00080000 ;
    protected static final int DEFAULT_TMP_BUFFER_LENGTH = 0x00007fff ;
    
    private NormalBuffer outBuffer = null ;
    private long tmpAddress = 0L ;
    private long wrkBuffer = 0L ;
    private int wrkBufferLength = 0 ;
    private int adler32Code = 0 ;
    
    private long _pos = 0L ;
    private int _bufLen = 0 ;
    private int _srcLen = 0 ;
    private long _addr = 0L ;
    private int[] _wrk = new int[ 1 ] ;
    
    /**
     * コンストラクタ.
     * @exception Exception 例外.
     */
    public CompressMemoryLzo()
        throws Exception {
        _create( -1,null,-1 ) ;
    }
    
    /**
     * コンストラクタ.
     * @param len 対象の初期バッファを設定します.
     * @exception Exception 例外.
     */
    public CompressMemoryLzo( int len )
        throws Exception {
        _create( len,null,-1 ) ;
    }
    
    /**
     * コンストラクタ.
     * @param buf 対象のNormalBufferを設定します.
     * @exception Exception 例外.
     */
    public CompressMemoryLzo( NormalBuffer buf )
        throws Exception {
        _create( -1,buf,-1 ) ;
    }
    
    /**
     * デストラクタ.
     */
    protected void finalize() throws Exception {
        close() ;
    }
    
    /** オブジェクト生成. **/
    private void _create( int len,NormalBuffer buf,int wrkBufferLen )
        throws Exception {
        if( len < DEFAULT_BUFFER_LENGTH ) {
            len = DEFAULT_BUFFER_LENGTH ;
        }
        if( buf == null ) {
            buf = new FastMemoryBuffer( len ) ;
        }
        else if( buf.getLength() < DEFAULT_BUFFER_LENGTH ) {
            len = DEFAULT_BUFFER_LENGTH ;
            _defaultBuffer( buf ) ;
        }
        if( wrkBufferLen < DEFAULT_WRK_BUFFER_LENGTH ) {
            wrkBufferLen = DEFAULT_WRK_BUFFER_LENGTH ;
        }
        wrkBuffer = DirectMemoryIO.malloc( wrkBufferLen ) ;
        DirectMemoryIO.memset( wrkBuffer,(byte)0,wrkBufferLen ) ;
        wrkBufferLength = wrkBufferLen ;
        outBuffer = buf ;
        tmpAddress = 0L ;
        adler32Code = 0 ;
        _pos = HEAD_LENGTH ;
        _bufLen = buf.getLength() ;
        _srcLen = 0 ;
        _addr = buf.getAddress() ;
        _wrk[0] = 0 ;
        _putHeader( buf ) ;
    }
    
    /**
     * クローズ処理.
     */
    public void close() {
        if( wrkBuffer != 0L ) {
            DirectMemoryIO.free( wrkBuffer ) ;
            wrkBuffer = 0L ;
        }
        if( tmpAddress != 0L ) {
            DirectMemoryIO.free( tmpAddress ) ;
            tmpAddress = 0L ;
        }
        wrkBufferLength = 0 ;
        outBuffer = null ;
        adler32Code = 0 ;
        _pos = 0L ;
        _bufLen = 0 ;
        _srcLen = 0 ;
        _addr = 0L ;
        _wrk[0] = 0 ;
    }
    
    /**
     * クローズチェック.
     * @return boolean クローズされている場合は[true]が返されます.
     */
    public boolean isClose() {
        return wrkBuffer == 0L ;
    }
    
    /**
     * リセット処理.
     * @exception Exception 例外.
     */
    public void reset() throws Exception {
        if( wrkBuffer == 0L ) {
            throw new IOException( "既にクローズされています" ) ;
        }
        if( tmpAddress != 0L ) {
            DirectMemoryIO.free( tmpAddress ) ;
            tmpAddress = 0L ;
        }
        adler32Code = 0 ;
        DirectMemoryIO.memset( wrkBuffer,(byte)0,wrkBufferLength ) ;
        _pos = HEAD_LENGTH ;
        _srcLen = 0 ;
        _wrk[0] = 0 ;
        if( outBuffer.getLength() > DEFAULT_BUFFER_LENGTH ) {
            _defaultBuffer( outBuffer ) ;
            _bufLen = DEFAULT_BUFFER_LENGTH ;
            _addr = outBuffer.getAddress() ;
        }
        _putHeader( outBuffer ) ;
    }
    
    /**
     * 圧縮処理終了.
     * @return NormalBuffer 圧縮結果のバッファを取得.
     * @exception Exception 例外.
     */
    public NormalBuffer finish()
        throws Exception {
        if( wrkBuffer == 0L ) {
            throw new IOException( "既にクローズされています" ) ;
        }
        outBuffer.putInt( MAGIC_LENGTH,adler32Code ) ;
        outBuffer.putInt( MAGIC_LENGTH + 4,_srcLen ) ;
        return outBuffer ;
    }
    
    /**
     * 現在の圧縮サイズを取得.
     * @return int 現在の圧縮サイズが返されます.
     * @exception Exception 例外.
     */
    public int getSize()
        throws Exception {
        if( wrkBuffer == 0L ) {
            throw new IOException( "既にクローズされています" ) ;
        }
        return (int)_pos ;
    }
    
    /**
     * 圧縮元のサイズを取得.
     * @return int 圧縮元のサイズが返されます.
     * @exception Exception 例外.
     */
    public int getSrcSize()
        throws Exception {
        if( wrkBuffer == 0L ) {
            throw new IOException( "既にクローズされています" ) ;
        }
        return _srcLen ;
    }
    
    /**
     * 現在のチェック値を取得.
     * @return int 現在のチェック値が返されます.
     * @exception Exception 例外.
     */
    public int getAdler32()
        throws Exception {
        if( wrkBuffer == 0L ) {
            throw new IOException( "既にクローズされています" ) ;
        }
        return adler32Code ;
    }
    
    /**
     * 圧縮処理.
     * @param buf 対象のバッファを設定します.
     * @param offset 対象のオフセット値を設定します.
     * @param length 対象の長さを設定します.
     * @exception Exception 例外.
     */
    public void compress( NativeBuffer buf,int offset,int length )
        throws Exception {
        if( wrkBuffer == 0L ) {
            throw new IOException( "既にクローズされています" ) ;
        }
        if( buf == null || offset < 0 || length <= 0 ) {
            throw new IllegalArgumentException( "引数が不正です" ) ;
        }
        if( buf.getLength() < offset + length ) {
            throw new IllegalArgumentException( "指定引数のオフセット＋長さが第一引数バッファ長を越しています" ) ;
        }
        int tmpLen = 0 ;
        int tmpPos = offset ;
        int oneLen ;
        int res ;
        long addr = buf.getAddress() ;
        while( true ) {
            if( length <= tmpLen ) {
                break ;
            }
            else if( length - tmpLen >= DEFAULT_TMP_BUFFER_LENGTH ) {
                oneLen = DEFAULT_TMP_BUFFER_LENGTH ;
            }
            else {
                oneLen = length - tmpLen ;
            }
            if( _bufLen < _pos + oneLen + CHUNKED_HEAD ) {
                addBuffer() ;
            }
            if( (res = NativeIO.lzo1xCompress( addr+tmpPos,oneLen,_addr+(long)(_pos+CHUNKED_HEAD),_wrk,wrkBuffer )) != 0 ) {
                throw new IOException( "圧縮処理に失敗しました:" + res ) ;
            }
            adler32Code = NativeIO.adler32( adler32Code,addr+tmpPos,oneLen ) ;
            DirectMemoryIO.putShort( _addr,(int)_pos,(short)(_wrk[ 0 ]&0x0000ffff) ) ;
            _pos += (long)(_wrk[ 0 ] + CHUNKED_HEAD) ;
            tmpLen += oneLen ;
            tmpPos += oneLen ;
        }
        _srcLen += length ;
    }
    
    /**
     * 圧縮処理.
     * @param buf 対象のバッファを設定します.
     * @param offset 対象のオフセット値を設定します.
     * @param length 対象の長さを設定します.
     * @exception Exception 例外.
     */
    public void compress( byte[] buf,int offset,int length )
        throws Exception {
        if( wrkBuffer == 0L ) {
            throw new IOException( "既にクローズされています" ) ;
        }
        if( buf == null || offset < 0 || length <= 0 ) {
            throw new IllegalArgumentException( "引数が不正です" ) ;
        }
        if( buf.length < offset + length ) {
            throw new IllegalArgumentException( "指定引数のオフセット＋長さが第一引数バッファ長を越しています" ) ;
        }
        if( tmpAddress == 0L ) {
            tmpAddress = DirectMemoryIO.malloc( DEFAULT_TMP_BUFFER_LENGTH ) ;
        }
        int tmpLen = 0 ;
        int tmpPos = offset ;
        int oneLen ;
        int res ;
        while( true ) {
            if( length <= tmpLen ) {
                break ;
            }
            else if( length - tmpLen >= DEFAULT_TMP_BUFFER_LENGTH ) {
                oneLen = DEFAULT_TMP_BUFFER_LENGTH ;
            }
            else {
                oneLen = length - tmpLen ;
            }
            if( _bufLen < _pos + oneLen + CHUNKED_HEAD ) {
                addBuffer() ;
            }
            DirectMemoryIO.putBinary( tmpAddress,0,buf,tmpPos,oneLen ) ;
            if( (res = NativeIO.lzo1xCompress( tmpAddress,oneLen,_addr+(long)(_pos+CHUNKED_HEAD),_wrk,wrkBuffer )) != 0 ) {
                throw new IOException( "圧縮処理に失敗しました:" + res ) ;
            }
            adler32Code = NativeIO.adler32( adler32Code,tmpAddress,oneLen ) ;
            DirectMemoryIO.putShort( _addr,(int)_pos,(short)(_wrk[ 0 ]&0x0000ffff) ) ;
            _pos += (long)(_wrk[ 0 ] + CHUNKED_HEAD) ;
            tmpLen += oneLen ;
            tmpPos += oneLen ;
        }
        _srcLen += length ;
    }
    
    /**
     * ヘッダセット.
     */
    private static final void _putHeader( NormalBuffer buf ) {
        buf.putBinary( 0,MAGIC,0,MAGIC_LENGTH ) ;
        buf.putInt( MAGIC_LENGTH,0 ) ;
    }
    
    /**
     * デフォルトバッファ生成.
     */
    private static final void _defaultBuffer( NormalBuffer buf ) {
        if( buf instanceof AbstractFastNativeBuffer ) {
            DirectMemoryIO.free(((AbstractFastNativeBuffer)buf).address) ;
            ((AbstractFastNativeBuffer)buf).address = DirectMemoryIO.malloc( DEFAULT_BUFFER_LENGTH ) ;
            ((AbstractFastNativeBuffer)buf).length = DEFAULT_BUFFER_LENGTH ;
        }
        else {
            DirectMemoryIO.free(((AbstractNativeBuffer)buf).address) ;
            ((AbstractNativeBuffer)buf).address = DirectMemoryIO.malloc( DEFAULT_BUFFER_LENGTH ) ;
            ((AbstractNativeBuffer)buf).length = DEFAULT_BUFFER_LENGTH ;
        }
    }
    
    /**
     * メモリサイズを増やす.
     */
    private void addBuffer() {
        if( outBuffer instanceof AbstractFastNativeBuffer ) {
            _addr = DirectMemoryIO.realloc( _addr,_bufLen,_bufLen + DEFAULT_BUFFER_LENGTH + CHUNKED_HEAD ) ;
            _bufLen = _bufLen + DEFAULT_BUFFER_LENGTH + CHUNKED_HEAD ;
            ((AbstractFastNativeBuffer)outBuffer).address = _addr ;
            ((AbstractFastNativeBuffer)outBuffer).length = _bufLen ;
        }
        else {
            _addr = DirectMemoryIO.realloc( _addr,_bufLen,_bufLen + DEFAULT_BUFFER_LENGTH + CHUNKED_HEAD ) ;
            _bufLen = _bufLen + DEFAULT_BUFFER_LENGTH + CHUNKED_HEAD ;
            ((AbstractNativeBuffer)outBuffer).address = _addr ;
            ((AbstractNativeBuffer)outBuffer).length = _bufLen ;
        }
    }
}
