package org.maachang.jni.io;

import java.io.IOException;
import java.io.InputStream;

/**
 * Lzo解凍処理.
 * 
 * @version 2011/01/04
 * @author  masahito suzuki
 * @since   SeabassNativeIO-1.0.2
 */
public class LzoInputStream extends InputStream {
    protected static final byte[] MAGIC = LzoOutputStream.MAGIC ;
    protected static final int MAGIC_LENGTH = LzoOutputStream.MAGIC_LENGTH ;
    protected static final int WRK_BUFFER_LENGTH = LzoOutputStream.WRK_BUFFER_LENGTH ;
    
    private static final int NBUFFER_LENGTH = 262144 ;
    private static final int SRC_BUFFER_LENGTH = 131070 ;
    private static final int TMP_BUFFER_LENGTH = 32767 ;
    private InputStream parent = null ;
    private boolean firstFlag = true ;
    private boolean endFlag = false ;
    
    private long _addr = 0L ;
    private int _len = 0 ;
    private int _pos = 0 ;
    private long _srcAddr = 0L ;
    private int _srcPos = 0 ;
    private int _srcLen = 0 ;
    private byte[] _tmp = null ;
    
    private long _workMem = 0L ;
    private int[] _wrk = new int[ 1 ] ;
    private int _adler32 = 0 ;
    
    private LzoInputStream() {
        
    }
    
    /**
     * コンストラクタ.
     * @param parent 親InputStreamを設定します.
     */
    public LzoInputStream( InputStream 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[ TMP_BUFFER_LENGTH ] ;
    }
    
    /** デストラクタ. **/
    protected void finalize() throws Exception {
        try {
            close() ;
        } catch( Exception e ) {
        }
    }
    
    /**
     * クローズ処理.
     */
    public void close() {
        DirectMemoryIO.free( _addr ) ;
        _addr = 0L ;
        DirectMemoryIO.free( _srcAddr ) ;
        _srcAddr = 0L ;
        DirectMemoryIO.free( _workMem ) ;
        _workMem = 0L ;
        _tmp = null ;
    }
    
    /**
     * 残り容量を取得.
     * @return int 残り容量が返されます.
     * @exception IOException I/O例外.
     */
    public int available() throws IOException {
        return -1 ;
    }
    
    /**
     * 読み込み処理.
     * @return int 読み込まれた情報が返されます.
     * @exception IOException I/O例外.
     */
    public int read() throws IOException {
        if( _workMem == 0L ) {
            throw new IOException( "既にクローズしています" ) ;
        }
        if( endFlag ) {
            return -1 ;
        }
        if( _len <= _pos ) {
            _readLzo() ;
            if( _len <= _pos && endFlag ) {
                return -1 ;
            }
        }
        int ret = ( DirectMemoryIO.get( _addr,_pos ) & 0x000000ff ) ;
        _pos ++ ;
        return ret ;
    }
    
    /**
     * 読み込み処理.
     * @param b 読み込み対象のバイナリを設定します.
     * @return int 読み込まれた情報が返されます.
     * @exception IOException I/O例外.
     */
    public int read(byte b[]) throws IOException {
        if (b == null) {
            throw new NullPointerException( "バイナリが存在しません" );
        }
        return read(b, 0, b.length);
    }
    
    /**
     * 読み込み処理.
     * @param b 対象のバイナリを設定します.
     * @param off 対象のオフセット値を設定します.
     * @param len 対象の長さを設定します.
     * @return int 読み込み長が返されます.
     * @exception IOException I/O例外.
     */
    public int read(byte b[], int off, int len) throws IOException {
        if( _workMem == 0L ) {
            throw new IOException( "既にクローズしています" ) ;
        }
        if( endFlag ) {
            return -1 ;
        }
        if (b == null) {
            throw new NullPointerException( "バイナリが存在しません" );
        } else if (off < 0 || len < 0 || len+off > b.length) {
            throw new IndexOutOfBoundsException( "指定範囲が不正です" );
        }
        int plen ;
        int cnt = 0 ;
        while( true ) {
            if( len <= 0 ) {
                return cnt ;
            }
            if( _len <= _pos ) {
                _readLzo() ;
                if( _len <= _pos && endFlag ) {
                    if( cnt == 0 ) {
                        return -1 ;
                    }
                    return cnt ;
                }
                else if( len > _len ) {
                    plen = _len ;
                }
                else {
                    plen = len ;
                }
            }
            else {
                plen = _len - _pos ;
                if( len < plen ) {
                    plen = len ;
                }
            }
            DirectMemoryIO.getBinary( _addr,_pos,b,off,plen ) ;
            _pos += plen ;
            off += plen ;
            len -= plen ;
            cnt += plen ;
        }
    }
    
    /** 解凍ステータス. **/
    private static final int STATE_NOT = 0 ;
    private static final int STATE_SUCCESS = 1 ;
    private static final int STATE_END = 9 ;
    
    /** データ取得. **/
    private void _readLzo() throws IOException {
        if( endFlag ) {
            return ;
        }
        if( _srcLen > _srcPos &&
            _decompress() != STATE_NOT ) {
            return ;
        }
        int len ;
        while( true ) {
            len = parent.read( _tmp,0,TMP_BUFFER_LENGTH ) ;
            if( len <= -1 ) {
                if( _decompress() == STATE_NOT ) {
                    throw new IOException( "LZO解凍処理に失敗:" +
                        "終端まで読み込んだが、解凍対象の塊長よりデータ長が短い" ) ;
                }
                return ;
            }
            if( _srcLen + len > SRC_BUFFER_LENGTH ) {
                _srcLen = _srcLen-_srcPos ;
                DirectMemoryIO.compact( _srcAddr,_srcPos,SRC_BUFFER_LENGTH,_srcLen ) ;
                _srcPos = 0 ;
                if( _srcLen + len > SRC_BUFFER_LENGTH ) {
                    throw new IOException( "LZO解凍処理に失敗:" +
                        "塊長が64Kを越している可能性がある" ) ;
                }
            }
            DirectMemoryIO.putBinary( _srcAddr,_srcLen,_tmp,0,len ) ;
            _srcLen += len ;
            if( _decompress() == STATE_NOT ) {
                continue ;
            }
            break ;
        }
    }
    
    /** 解凍処理. **/
    private int _decompress() throws IOException {
        if( firstFlag ) {
            if( _srcLen < MAGIC_LENGTH ) {
                return STATE_NOT ;
            }
            for( int i = 0 ; i < MAGIC_LENGTH ; i ++ ) {
                if( DirectMemoryIO.get( _srcAddr,i ) != MAGIC[ i ] ) {
                    throw new IOException( "LZO形式ではありません" ) ;
                }
            }
            _srcPos += MAGIC_LENGTH ;
            firstFlag = false ;
        }
        int chunkedLen = (DirectMemoryIO.getShort( _srcAddr,_srcPos )&0x0000ffff) ;
        if( chunkedLen == 0 ) {
            if( _srcPos + 6 <= _srcLen ) {
                int srcAdler32 = DirectMemoryIO.getInt( _srcAddr,_srcPos + 2 ) ;
                if( _adler32 != srcAdler32 ) {
                    throw new IOException( "解凍結果のチェックサムが一致しません" ) ;
                }
                _srcPos = _srcPos + 6 ;
                endFlag = true ;
                return STATE_END ;
            }
            return STATE_NOT ;
        }
        if( chunkedLen + _srcPos + 2 <= _srcLen ) {
            int res = NativeIO.lzo1xDecompress( _srcAddr+(long)(_srcPos+2),chunkedLen,_addr,_wrk,_workMem ) ;
            if( res != 0 ) {
                throw new IOException( "解凍処理に失敗しました:" + res ) ;
            }
            _srcPos = _srcPos + 2 + chunkedLen ;
            _adler32 = NativeIO.adler32( _adler32,_addr,_wrk[0] ) ;
            _len = _wrk[0] ;
            _pos = 0 ;
            return STATE_SUCCESS ;
        }
        return STATE_NOT ;
    }
    
    /**
     * markサポート.
     * @return boolean [false]が返されます.
     */
    public boolean markSupported() {
        return false ;
    }
    
    /**
     * クローズされているかチェック.
     * @return boolean [true]の場合、クローズしています.
     */
    public boolean isClose() {
        return _workMem == 0L ;
    }
}

