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 DecompressMemoryLzo {
    protected static final byte[] MAGIC = CompressMemoryLzo.MAGIC ;
    protected static final int MAGIC_LENGTH = CompressMemoryLzo.MAGIC_LENGTH ;
    protected static final int HEAD_LENGTH = CompressMemoryLzo.HEAD_LENGTH ;
    protected static final int CHUNKED_HEAD = CompressMemoryLzo.CHUNKED_HEAD ;
    protected static final int DEFAULT_WRK_BUFFER_LENGTH = CompressMemoryLzo.DEFAULT_WRK_BUFFER_LENGTH ;
    
    protected static final int DEFAULT_TMP_BUFFER_LENGTH = 0x00020000 ;
    protected static final int DEFAULT_EXEC_BUFFER_LENGTH = 0x0000ffff ;
    
    private NormalBuffer outBuffer = null ;
    private long tmpAddr = 0L ;
    private long wrkBuffer = 0L ;
    private int wrkBufferLength = 0 ;
    private int adler32Code = 0 ;
    private int srcAdler32Code = 0 ;
    private int srcLength = 0 ;
    
    /**
     * コンストラクタ.
     * @exception Exception 例外.
     */
    public DecompressMemoryLzo()
        throws Exception {
        _create( -1 ) ;
    }
    
    /**
     * デストラクタ.
     */
    protected void finalize() throws Exception {
        close() ;
    }
    
    /** オブジェクト生成. **/
    private void _create( int wrkBufferLen )
        throws Exception {
        if( wrkBufferLen < DEFAULT_WRK_BUFFER_LENGTH ) {
            wrkBufferLen = DEFAULT_WRK_BUFFER_LENGTH ;
        }
        wrkBuffer = DirectMemoryIO.malloc( wrkBufferLen ) ;
        DirectMemoryIO.memset( wrkBuffer,(byte)0,wrkBufferLen ) ;
        wrkBufferLength = wrkBufferLen ;
        tmpAddr = DirectMemoryIO.malloc( DEFAULT_TMP_BUFFER_LENGTH ) ;
        outBuffer = null ;
        adler32Code = 0 ;
        srcAdler32Code = 0 ;
        srcLength = 0 ;
        _clear() ;
    }
    
    /**
     * クローズ処理.
     */
    public void close() {
        if( wrkBuffer != 0L ) {
            DirectMemoryIO.free( wrkBuffer ) ;
            wrkBuffer = 0L ;
            wrkBufferLength = 0 ;
        }
        if( tmpAddr != 0L ) {
            DirectMemoryIO.free( tmpAddr ) ;
            tmpAddr = 0L ;
        }
        outBuffer = null ;
        adler32Code = 0 ;
        srcAdler32Code = 0 ;
        srcLength = 0 ;
        _clear() ;
    }
    
    /**
     * クローズチェック.
     * @return boolean クローズされている場合は[true]が返されます.
     */
    public boolean isClose() {
        return wrkBuffer == 0L ;
    }
    
    /**
     * リセット処理.
     * @exception Exception 例外.
     */
    public void reset() throws Exception {
        if( wrkBuffer == 0L ) {
            throw new IOException( "既にクローズされています" ) ;
        }
        if( tmpAddr != 0L ) {
            DirectMemoryIO.free( tmpAddr ) ;
            tmpAddr = 0L ;
        }
        DirectMemoryIO.memset( wrkBuffer,(byte)0,wrkBufferLength ) ;
        outBuffer = null ;
        adler32Code = 0 ;
        srcAdler32Code = 0 ;
        srcLength = 0 ;
        _clear() ;
    }
    
    /**
     * 解凍処理終了.
     * @return NormalBuffer 解凍結果のバッファを取得.
     * @exception Exception 例外.
     */
    public NormalBuffer finish()
        throws Exception {
        if( wrkBuffer == 0L ) {
            throw new IOException( "既にクローズされています" ) ;
        }
        if( outBuffer == null ) {
            throw new IOException( "解凍処理が行われていません" ) ;
        }
        if( srcAdler32Code != adler32Code ) {
            throw new IOException( "adler32が一致しません(src:" + srcAdler32Code + "dest:" + adler32Code + ")" ) ;
        }
        if( srcLength != _pos ) {
            throw new IOException( "長さが一致しません" ) ;
        }
        return outBuffer ;
    }
    
    /**
     * 現在の解凍サイズを取得.
     * @return int 現在の解凍サイズが返されます.
     * @exception Exception 例外.
     */
    public int getSize()
        throws Exception {
        if( wrkBuffer == 0L ) {
            throw new IOException( "既にクローズされています" ) ;
        }
        return _pos ;
    }
    
    /**
     * 解凍元のサイズを取得.
     * @return int 圧縮元のサイズが返されます.
     * @exception Exception 例外.
     */
    public int getSrcSize()
        throws Exception {
        if( wrkBuffer == 0L ) {
            throw new IOException( "既にクローズされています" ) ;
        }
        return srcLength ;
    }
    
    /**
     * 現在のチェック値を取得.
     * @return int 現在のチェック値が返されます.
     * @exception Exception 例外.
     */
    public int getAdler32()
        throws Exception {
        if( wrkBuffer == 0L ) {
            throw new IOException( "既にクローズされています" ) ;
        }
        return adler32Code ;
    }
    
    private int _useBufPos = 0 ;
    private int _useBufLen = 0 ;
    private long _addr = 0L ;
    private int _pos = 0 ;
    private final int[] _wrk = new int[ 1 ] ;
    
    /** 解凍用情報をクリア. **/
    private void _clear() {
        _useBufPos = 0 ;
        _useBufLen = 0 ;
        _addr = 0 ;
        _wrk[ 0 ] = 0 ;
    }
    
    /**
     * 解凍処理.
     * @param buf 対象のバッファを設定します.
     * @param offset 対象のオフセット値を設定します.
     * @param length 対象の長さを設定します.
     * @exception Exception 例外.
     */
    public void decompress( 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 res ;
        int bufPos = offset ;
        int bufLen = 0 ;
        int allLen = 0 ;
        int chunkedLen = 0 ;
        long bufAddr = buf.getAddress() ;
        while( true ) {
            if( length <= allLen ) {
                break ;
            }
            if( length - allLen >= DEFAULT_EXEC_BUFFER_LENGTH ) {
                bufLen = DEFAULT_EXEC_BUFFER_LENGTH ;
            }
            else {
                bufLen = length - allLen ;
            }
            if( _useBufLen + bufLen > DEFAULT_TMP_BUFFER_LENGTH ) {
                DirectMemoryIO.compact( tmpAddr,_useBufPos,DEFAULT_TMP_BUFFER_LENGTH,_useBufLen-_useBufPos ) ;
                _useBufLen -= _useBufPos ;
                _useBufPos = 0 ;
                DirectMemoryIO.memcpy( tmpAddr+(long)_useBufLen,bufAddr+(long)bufPos,bufLen ) ;
                _useBufLen += bufLen ;
            }
            else {
                DirectMemoryIO.memcpy( tmpAddr+(long)_useBufLen,bufAddr+(long)bufPos,bufLen ) ;
                _useBufLen += bufLen ;
            }
            while( true ) {
                if( outBuffer == null ) {
                    if( HEAD_LENGTH > _useBufLen ) {
                        break ;
                    }
                    for( int i = 0 ; i < MAGIC_LENGTH ; i ++ ) {
                        if( DirectMemoryIO.get( tmpAddr,i ) != MAGIC[ i ] ) {
                            throw new IOException( "LZO形式ではありません" ) ;
                        }
                    }
                    srcAdler32Code = DirectMemoryIO.getInt( tmpAddr,MAGIC_LENGTH ) ;
                    srcLength = DirectMemoryIO.getInt( tmpAddr,MAGIC_LENGTH + 4 ) ;
                    if( ( srcLength & 0x80000000 ) != 0 ) {
                        throw new IOException( "復元データ長が不正です" ) ;
                    }
                    chunkedLen = ( DirectMemoryIO.getShort( tmpAddr,HEAD_LENGTH ) & 0x0000ffff ) ;
                    outBuffer = new FastMemoryBuffer( srcLength ) ;
                    _addr = outBuffer.getAddress() ;
                    _pos = 0 ;
                    if( chunkedLen+_useBufPos+CHUNKED_HEAD > _useBufLen ) {
                        break ;
                    }
                    _useBufPos = HEAD_LENGTH ;
                }
                else {
                    chunkedLen = (DirectMemoryIO.getShort( tmpAddr,_useBufPos )&0x0000ffff) ;
                    if( chunkedLen+_useBufPos+CHUNKED_HEAD > _useBufLen ) {
                        break ;
                    }
                }
                if( ( res = NativeIO.lzo1xDecompress( tmpAddr+(long)(_useBufPos+CHUNKED_HEAD),chunkedLen,_addr+(long)_pos,_wrk,wrkBuffer )) != 0 ) {
                    throw new IOException( "解凍処理に失敗しました:" + res ) ;
                }
                adler32Code = NativeIO.adler32( adler32Code,_addr+(long)_pos,_wrk[ 0 ] ) ;
                _useBufPos += chunkedLen + CHUNKED_HEAD ;
                _pos += _wrk[ 0 ] ;
            }
            allLen += bufLen ;
            bufPos += bufLen ;
            bufLen = 0 ;
        }
    }
    
    /**
     * 解凍処理.
     * @param buf 対象のバッファを設定します.
     * @param offset 対象のオフセット値を設定します.
     * @param length 対象の長さを設定します.
     * @exception Exception 例外.
     */
    public void decompress( 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( "指定引数のオフセット＋長さが第一引数バッファ長を越しています" ) ;
        }
        int res ;
        int bufPos = offset ;
        int bufLen = 0 ;
        int allLen = 0 ;
        int chunkedLen = 0 ;
        while( true ) {
            if( length <= allLen ) {
                break ;
            }
            if( length - allLen >= DEFAULT_EXEC_BUFFER_LENGTH ) {
                bufLen = DEFAULT_EXEC_BUFFER_LENGTH ;
            }
            else {
                bufLen = length - allLen ;
            }
            if( _useBufLen + bufLen > DEFAULT_TMP_BUFFER_LENGTH ) {
                DirectMemoryIO.compact( tmpAddr,_useBufPos,DEFAULT_TMP_BUFFER_LENGTH,_useBufLen-_useBufPos ) ;
                _useBufLen -= _useBufPos ;
                _useBufPos = 0 ;
                DirectMemoryIO.putBinary( tmpAddr,_useBufLen,buf,bufPos,bufLen ) ;
                _useBufLen += bufLen ;
            }
            else {
                DirectMemoryIO.putBinary( tmpAddr,_useBufLen,buf,bufPos,bufLen ) ;
                _useBufLen += bufLen ;
            }
            while( true ) {
                if( outBuffer == null ) {
                    if( HEAD_LENGTH > _useBufLen ) {
                        break ;
                    }
                    for( int i = 0 ; i < MAGIC_LENGTH ; i ++ ) {
                        if( DirectMemoryIO.get( tmpAddr,i ) != MAGIC[ i ] ) {
                            throw new IOException( "LZO形式ではありません" ) ;
                        }
                    }
                    srcAdler32Code = DirectMemoryIO.getInt( tmpAddr,MAGIC_LENGTH ) ;
                    srcLength = DirectMemoryIO.getInt( tmpAddr,MAGIC_LENGTH + 4 ) ;
                    if( ( srcLength & 0x80000000 ) != 0 ) {
                        throw new IOException( "復元データ長が不正です" ) ;
                    }
                    chunkedLen = ( DirectMemoryIO.getShort( tmpAddr,HEAD_LENGTH ) & 0x0000ffff ) ;
                    outBuffer = new FastMemoryBuffer( srcLength ) ;
                    _addr = outBuffer.getAddress() ;
                    _pos = 0 ;
                    if( chunkedLen+_useBufPos+CHUNKED_HEAD > _useBufLen ) {
                        break ;
                    }
                    _useBufPos = HEAD_LENGTH ;
                }
                else {
                    chunkedLen = (DirectMemoryIO.getShort( tmpAddr,_useBufPos )&0x0000ffff) ;
                    if( chunkedLen+_useBufPos+CHUNKED_HEAD > _useBufLen ) {
                        break ;
                    }
                }
                if( ( res = NativeIO.lzo1xDecompress( tmpAddr+(long)(_useBufPos+CHUNKED_HEAD),chunkedLen,_addr+(long)_pos,_wrk,wrkBuffer )) != 0 ) {
                    throw new IOException( "解凍処理に失敗しました:" + res ) ;
                }
                adler32Code = NativeIO.adler32( adler32Code,_addr+(long)_pos,_wrk[ 0 ] ) ;
                _useBufPos += chunkedLen + CHUNKED_HEAD ;
                _pos += _wrk[ 0 ] ;
            }
            allLen += bufLen ;
            bufPos += bufLen ;
            bufLen = 0 ;
        }
    }
}
