package org.maachang.jni.io;

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

/**
 * Snappy解凍処理.
 * 
 * @version 2012/11/09
 * @author  masahito suzuki
 * @since   SeabassNativeIO-1.0.3
 */
public class SnappyInputStream extends InputStream {
    protected static final byte[] MAGIC = SnappyOutputStream.MAGIC ;
    protected static final int MAGIC_LENGTH = SnappyOutputStream.MAGIC_LENGTH ;
    
    private static final int READ_BUFFER_LENGTH = SnappyOutputStream.DEST_BUFFER_LENGTH * 2 ;
    private static final int DEST_BUFFER_LENGTH = SnappyOutputStream.WRITE_BUFFER_LENGTH ;
    
    private InputStream parent = null ;
    private boolean _first = true ;
    private boolean _end = false ;
    private long _srcAddr = 0L ;
    private long _destAddr = 0L ;
    private byte[] _tmp = null ;
    private final int[] _destWkLen = new int[ 1 ] ;
    private int _destLen = 0 ;
    private int _destPos = 0 ;
    
    private SnappyInputStream() {
        
    }
    
    /**
     * コンストラクタ.
     * @param parent 親InputStreamを設定します.
     */
    public SnappyInputStream( InputStream parent ) {
        if( parent == null ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        this.parent = parent ;
        _srcAddr = DirectMemoryIO.malloc( READ_BUFFER_LENGTH ) ;
        _destAddr = DirectMemoryIO.malloc( DEST_BUFFER_LENGTH ) ;
        _tmp = new byte[ DEST_BUFFER_LENGTH ] ;
    }
    
    /** デストラクタ. **/
    protected void finalize() throws Exception {
        try {
            close() ;
        } catch( Exception e ) {
        }
    }
    
    /**
     * クローズ処理.
     */
    public void close() {
        if( _srcAddr == 0L ) {
            return ;
        }
        try {
            parent.close() ;
        } catch( Exception e ) {
        }
        DirectMemoryIO.free( _srcAddr ) ;
        _srcAddr = 0L ;
        DirectMemoryIO.free( _destAddr ) ;
        _destAddr = 0L ;
        _tmp = null ;
        _first = true ;
        
        parent = 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( _srcAddr == 0L ) {
            throw new IOException( "既にクローズしています" ) ;
        }
        if( _end ) {
            return -1 ;
        }
        if( _destLen <= _destPos ) {
            _readSnappy() ;
            if( _destLen <= _destPos && _end ) {
                return -1 ;
            }
        }
        int ret = ( DirectMemoryIO.get( _destAddr,_destPos ) & 0x000000ff ) ;
        _destPos ++ ;
        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( _srcAddr == 0L ) {
            throw new IOException( "既にクローズしています" ) ;
        }
        if( _end ) {
            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( _destLen <= _destPos ) {
                _readSnappy() ;
                if( _destLen <= _destPos && _end ) {
                    if( cnt == 0 ) {
                        return -1 ;
                    }
                    return cnt ;
                }
                else if( len > _destLen ) {
                    plen = _destLen ;
                }
                else {
                    plen = len ;
                }
            }
            else {
                plen = _destLen - _destPos ;
                if( len < plen ) {
                    plen = len ;
                }
            }
            DirectMemoryIO.getBinary( _destAddr,_destPos,b,off,plen ) ;
            _destPos += plen ;
            off += plen ;
            len -= plen ;
            cnt += plen ;
        }
    }
    
    /**
     * markサポート.
     * @return boolean [false]が返されます.
     */
    public boolean markSupported() {
        return false ;
    }
    
    /**
     * クローズされているかチェック.
     * @return boolean [true]の場合、クローズしています.
     */
    public boolean isClose() {
        return _srcAddr == 0L ;
    }
    
    private int _readPos = 0 ;
    private int _readLen = 0 ;
    
    /** 解凍ステータス. **/
    private static final int STATE_NOT = -1 ;
    private static final int STATE_SUCCESS = 0 ;
    
    /** データ取得. **/
    private void _readSnappy() throws IOException {
        if( _end ) {
            return ;
        }
        if( _readLen > _readPos &&
            _decompress() != STATE_NOT ) {
            return ;
        }
        int len ;
        while( true ) {
            len = parent.read( _tmp,0,DEST_BUFFER_LENGTH ) ;
            if( len <= -1 ) {
                _decompress() ;
                _end = true ;
                return ;
            }
            if( _readLen + len > READ_BUFFER_LENGTH ) {
                _readLen = _readLen-_readPos ;
                DirectMemoryIO.compact( _srcAddr,_readPos,READ_BUFFER_LENGTH,_readLen ) ;
                _readPos = 0 ;
                if( _readLen + len > READ_BUFFER_LENGTH ) {
                    throw new IOException( "Snappy解凍処理に失敗:" +
                        "塊長が"+READ_BUFFER_LENGTH+"byteを越している可能性がある:" + (_readLen + len) + "byte" ) ;
                }
            }
            DirectMemoryIO.putBinary( _srcAddr,_readLen,_tmp,0,len ) ;
            _readLen += len ;
            if( _decompress() == STATE_NOT ) {
                continue ;
            }
            break ;
        }
    }
    
    /** 解凍処理. **/
    private int _decompress() throws IOException {
        if( _first ) {
            if( _readLen < MAGIC_LENGTH ) {
                return STATE_NOT ;
            }
            for( int i = 0 ; i < MAGIC_LENGTH ; i ++ ) {
                if( DirectMemoryIO.get( _srcAddr,i ) != MAGIC[ i ] ) {
                    throw new IOException( "Snappy圧縮形式ではありません" ) ;
                }
            }
            _readPos += MAGIC_LENGTH ;
            _first = false ;
        }
        int chunkedLen = DirectMemoryIO.getInt( _srcAddr,_readPos ) ;
        if( chunkedLen + _readPos + 4 <= _readLen ) {
            int res = NativeIO.snappyDecompress( _srcAddr+(long)(_readPos+4),chunkedLen,_destAddr,_destWkLen ) ;
            if( res != 0 ) {
                throw new IOException( "Snappy解凍処理に失敗しました" ) ;
            }
            _readPos = _readPos + 4 + chunkedLen ;
            _destLen = _destWkLen[ 0 ] ;
            _destPos = 0 ;
            return STATE_SUCCESS ;
        }
        return STATE_NOT ;
    }
}

