package org.maachang.leveldb ;

import java.io.File;

/**
 * Leveldb.
 *
 * @version 2014/07/29
 * @author  masahito suzuki
 * @since   leveldb-1.00
 */
public final class Leveldb {
    
    protected volatile boolean closeFlag = true ;
    protected long addr = 0L ;
    protected String path ;
    protected int write_buffer_size ;
    protected int max_open_files ;
    protected int block_size ;
    protected int block_restart_interval ;
    
    /**
     * コンストラクタ.
     * @param path 対象のファイル名を設定します.
     * @exception Exception 例外.
     */
    public Leveldb( String path ) throws Exception {
        this( path,-1,-1,-1,-1 ) ;
    }
    
    /**
     * コンストラクタ.
     * @param path 対象のファイル名を設定します.
     * @param write_buffer_size leveldbの「write_buffer_size」値を設定します.
     * @param max_open_files leveldbの「max_open_files」値を設定します.
     * @param block_size leveldbの「block_size」値を設定します.
     * @param block_restart_interval leveldbの「block_restart_interval」値を設定します.
     * @exception Exception 例外.
     */
    public Leveldb( String path,int write_buffer_size,
        int max_open_files,int block_size,int block_restart_interval )
        throws Exception {
        if( path == null || ( path = path.trim() ).length() <= 0 ) {
            return ;
        }
        if( write_buffer_size < 0 ) {
            write_buffer_size = -1 ;
        }
        if( max_open_files < 0 ) {
            max_open_files = -1 ;
        }
        if( block_size < 0 ) {
            block_size = -1 ;
        }
        if( block_restart_interval < 0 ) {
            block_restart_interval = -1 ;
        }
        File f = new File( path ) ;
        String s = f.getCanonicalPath();
        JniBuffer b = new JniBuffer() ;
        b.setJniChar( s ) ;
        
        long a = jni.leveldb_open( b.address(),write_buffer_size,max_open_files,
            block_size,block_restart_interval ) ;
        
        b.destroy() ;
        
        if( a == 0L ) {
            throw new LeveldbException( "Leveldbのオープンに失敗:" + s ) ;
        }
        this.addr = a ;
        this.path = s ;
        this.write_buffer_size = write_buffer_size ;
        this.max_open_files = max_open_files ;
        this.block_size = block_size ;
        this.block_restart_interval = block_restart_interval ;
        
        closeFlag = false ;
    }
    
    /**
     * デストラクタ.
     */
    protected final void finalize() throws Exception {
        close() ;
    }
    
    /**
     * クローズ.
     */
    public synchronized final void close() {
        if( !closeFlag && addr != 0L ) {
            closeFlag = true ;
            jni.leveldb_close( addr ) ;
            addr = 0L ;
        }
    }
    
    /**
     * クローズしているかチェック.
     * @return boolean [true]の場合、クローズしています.
     */
    public final boolean isClose() {
        return closeFlag ;
    }
    
    /** check. **/
    protected final void check() {
        if( closeFlag ) {
            throw new LeveldbException( "既にクローズされています" ) ;
        }
    }
    
    /**
     * オープンパス名を取得.
     * @return String オープンパス名が返却されます.
     */
    public final String getPath() {
        return path ;
    } 
    
    /**
     * write_buffer_sizeを取得.
     * @return int 値が返却されます.
     */
    public final int write_buffer_size() {
        return write_buffer_size ;
    }
    
    /**
     * max_open_filesを取得.
     * @return int 値が返却されます.
     */
    public final int max_open_files() {
        return max_open_files ;
    }
    
    /**
     * block_sizeを取得.
     * @return int 値が返却されます.
     */
    public final int block_size() {
        return block_size ;
    }
    
    /**
     * block_restart_intervalを取得.
     * @return int 値が返却されます.
     */
    public final int block_restart_interval() {
        return block_restart_interval ;
    }
    
    /**
     * 情報セット.
     * @param key 対象のキーを設定します.
     * @param value 対象の要素を設定します.
     */
    public final void put( final JniBuffer key,final JniBuffer value ) {
        check() ;
        if( key.position() == 0 || value.position() == 0 ) {
            throw new LeveldbException( "引数は不正です" ) ;
        }
        if( jni.leveldb_put( addr,key.address(),key.position(),
            value.address(),value.position() ) == -1 ) {
            throw new LeveldbException( "書き込み処理は失敗しました" ) ;
        }
    }
    
    /**
     * 情報取得.
     * @param out 取得用のJniBufferを設定します.
     * @param key 対象のキーを設定します.
     * @return int 取得されたデータ長が返却されます.
     */
    public final int get( final JniBuffer out,final JniBuffer key ) {
        check() ;
        if( key.position() == 0 ) {
            throw new LeveldbException( "キー情報が設定されていません" ) ;
        }
        long[] n = new long[]{ out.address() } ;
        int len = jni.leveldb_get( addr,key.address(),key.position(),
            n,out.length() ) ;
        if( len <= 0 ) {
            return 0 ;
        }
        
        // leveldb_getでバッファが拡張された場合.
        if( len > out.length() ) {
            
            // バッファ内容を再セット.
            out.set( n[ 0 ],len,len ) ;
        }
        else {
            
            // ポジジョンをセット.
            out.position( len ) ;
        }
        return len ;
    }
    
    /**
     * 情報削除.
     * @param key 対象のキーを設定します.
     * @return boolean [true]の場合、削除されました.
     */
    public final boolean remove( final JniBuffer key ) {
        check() ;
        if( key.position() == 0 ) {
            throw new LeveldbException( "キー情報が設定されていません" ) ;
        }
        return jni.leveldb_remove( addr,key.address(),key.position() ) != -1 ;
    }
    
    /**
     * 状態取得
     * @param out 取得用のJniBufferを設定します.
     * @param cmd 対象のコマンドを設定します.
     *            [leveldb.num-files-at-level?]
     *            このコマンドの後の?に番号をセットします.
     *            leveldb.stats
     *            ステータスが返却されます.
     *            leveldb.sstables
     *            sstable情報が返却されます.
     * @return int 取得されたデータ長が返却されます.
     */
    public final int property( final JniBuffer out,final JniBuffer cmd ) {
        check() ;
        if( cmd.position() == 0 ) {
            throw new LeveldbException( "コマンド情報が設定されていません" ) ;
        }
        long[] n = new long[]{ out.address() } ;
        int len = jni.leveldb_property( addr,cmd.address(),cmd.position(),
            n,out.length() ) ;
        if( len <= 0 ) {
            return 0 ;
        }
        
        // leveldb_getでバッファが拡張された場合.
        if( len > out.length() ) {
            
            // バッファ内容を再セット.
            out.set( n[ 0 ],len,len ) ;
        }
        else {
            
            // ポジジョンをセット.
            out.position( len ) ;
        }
        return len ;
    }
    
    /**
     * Iteratorを取得.
     * @return LeveldbIterator Iteratorオブジェクトが返却されます.
     */
    public final LeveldbIterator iterator() {
        return new LeveldbIterator( this ) ;
    }
    
    /**
     * データベース情報の削除.
     * @param path 対象のファイル名を設定します.
     * @exception Exception 例外.
     */
    public static final void destroy( String path )
        throws Exception {
        destroy( path,-1,-1,-1,-1 ) ;
    }
    
    /**
     * データベース情報の削除.
     * @param path 対象のファイル名を設定します.
     * @param write_buffer_size leveldbの「write_buffer_size」値を設定します.
     * @param max_open_files leveldbの「max_open_files」値を設定します.
     * @param block_size leveldbの「block_size」値を設定します.
     * @param block_restart_interval leveldbの「block_restart_interval」値を設定します.
     * @exception Exception 例外.
     */
    public static final void destroy( String path,int write_buffer_size,
        int max_open_files,int block_size,int block_restart_interval )
        throws Exception {
        if( path == null || ( path = path.trim() ).length() <= 0 ) {
            return ;
        }
        if( write_buffer_size < 0 ) {
            write_buffer_size = -1 ;
        }
        if( max_open_files < 0 ) {
            max_open_files = -1 ;
        }
        if( block_size < 0 ) {
            block_size = -1 ;
        }
        if( block_restart_interval < 0 ) {
            block_restart_interval = -1 ;
        }
        File f = new File( path ) ;
        String s = f.getCanonicalPath();
        JniBuffer b = new JniBuffer() ;
        b.setJniChar( s ) ;
        
        jni.leveldb_destroy( b.address(),write_buffer_size,max_open_files,
            block_size,block_restart_interval ) ;
        
        b.destroy() ;
    }
    
    /**
     * データベース情報の修復.
     * @param path 対象のファイル名を設定します.
     * @exception Exception 例外.
     */
    public static final void repair( String path )
        throws Exception {
        repair( path,-1,-1,-1,-1 ) ;
    }
    
    /**
     * データベース情報の修復.
     * @param path 対象のファイル名を設定します.
     * @param write_buffer_size leveldbの「write_buffer_size」値を設定します.
     * @param max_open_files leveldbの「max_open_files」値を設定します.
     * @param block_size leveldbの「block_size」値を設定します.
     * @param block_restart_interval leveldbの「block_restart_interval」値を設定します.
     * @exception Exception 例外.
     */
    public static final void repair( String path,int write_buffer_size,
        int max_open_files,int block_size,int block_restart_interval )
        throws Exception {
        if( path == null || ( path = path.trim() ).length() <= 0 ) {
            return ;
        }
        if( write_buffer_size < 0 ) {
            write_buffer_size = -1 ;
        }
        if( max_open_files < 0 ) {
            max_open_files = -1 ;
        }
        if( block_size < 0 ) {
            block_size = -1 ;
        }
        if( block_restart_interval < 0 ) {
            block_restart_interval = -1 ;
        }
        File f = new File( path ) ;
        String s = f.getCanonicalPath();
        JniBuffer b = new JniBuffer() ;
        b.setJniChar( s ) ;
        
        jni.leveldb_repair( b.address(),write_buffer_size,max_open_files,
            block_size,block_restart_interval ) ;
        
        b.destroy() ;
    }
}
