package org.maachang.leveldb ;

import java.util.*;

/**
 * LeveldbのMap実装.
 * 
 * @version 2014/07/31
 * @author  masahito suzuki
 * @since   leveldb-1.00
 */
@SuppressWarnings("unchecked")
public class LevelMap implements Map<String,Object> {
    
    /** キーバッファを、ローカルストレージ単位で保持. **/
    protected static final ThreadLocal<JniBuffer> keyBuffer =
        new ThreadLocal<JniBuffer>() ;
    
    /** 要素バッファをローカルストレージ単位で保持. **/
    protected static final ThreadLocal<JniBuffer> valueBuffer =
        new ThreadLocal<JniBuffer>() ;
    
    /** LevelDbオブジェクト. **/
    protected Leveldb leveldb ;
    
    /** LevelDbマップセット用オブジェクト. **/
    protected LevelMapSet set ;
    
    /**
     * コンストラクタ.
     * @param name 対象のデータベース名を設定します.
     * @exception Exception 例外.
     */
    public LevelMap( String name ) throws Exception {
        this( name,-1,-1,-1,-1 ) ;
    }
    
    /**
     * コンストラクタ.
     * @param name 対象のデータベース名を設定します.
     * @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」値を設定します.
     */
    public LevelMap( String name,int write_buffer_size,
        int max_open_files,int block_size,int block_restart_interval )
        throws Exception {
        this.leveldb = new Leveldb( name,write_buffer_size,
            max_open_files,block_size,block_restart_interval ) ;
        
        set = new LevelMapSet( this ) ;
    }
    
    /**
     * デストラクタ.
     */
    protected final void finalize() throws Exception {
        close() ;
    }
    
    /**
     * オブジェクトクローズ.
     */
    public final void close() {
        leveldb.close() ;
    }
    
    /**
     * Leveldbオブジェクトを取得.
     * @return Leveldb Leveldbオブジェクトが返却されます.
     */
    public final Leveldb getLevedb() {
        return leveldb ;
    }
    
    /**
     * WriteBatchMapの取得.
     * @return WriteBatchMap WriteBatchMapが返却されます.
     */
    public final WriteBatchMap writeBatch() {
        return new WriteBatchMap( this ) ;
    }
    
    /**
     * KeyBufferを取得.
     * @return JniBuffer KeyBufferが返却されます.
     * @exception Exception 例外.
     */
    public static final JniBuffer getKeyBuffer()
        throws Exception {
        return getKeyBuffer( null ) ;
    }
    
    /**
     * KeyBufferを取得.
     * @param name 対象のキー名を設定した場合、その内容がバッファに割り当てられます.
     * @return JniBuffer KeyBufferが返却されます.
     * @exception Exception 例外.
     */
    public static final JniBuffer getKeyBuffer( String name )
        throws Exception {
        JniBuffer ret = keyBuffer.get() ;
        if( ret == null ) {
            ret = new JniBuffer() ;
            keyBuffer.set( ret ) ;
        }
        if( name != null ) {
            ret.setString( name ) ;
        }
        return ret ;
    }
    
    /**
     * ValueBufferを取得.
     * @return JniBuffer ValueBufferが返却されます.
     * @exception Exception 例外.
     */
    public static final JniBuffer getValueBuffer()
        throws Exception {
        return getValueBuffer( null ) ;
    }
    
    /**
     * ValueBufferを取得.
     * @param value 対象の要素を設定した場合、その内容がバッファに割り当てられます.
     * @return JniBuffer ValueBufferが返却されます.
     * @exception Exception 例外.
     */
    public static final JniBuffer getValueBuffer( Object value )
        throws Exception {
        JniBuffer ret = valueBuffer.get() ;
        if( ret == null ) {
            ret = new JniBuffer() ;
            valueBuffer.set( ret ) ;
        }
        if( value != null ) {
            ValueBinary.encode( ret,value ) ;
        }
        return ret ;
    }
    
    /**
     * キャッシュクリア.
     * ThreadLocalで管理しているキャッシュ情報をクリアします.
     */
    public final void clearCache() {
        
        // ThreadLocalの内容をクリア.
        JniBuffer v = keyBuffer.get() ;
        if( v != null ) {
            keyBuffer.remove() ;
            v.destroy() ;
        }
        v = valueBuffer.get() ;
        if( v != null ) {
            valueBuffer.remove() ;
            v.destroy() ;
        }
    }
    
    /**
     * 情報クリア.
     * ※Iteratorで処理をするので、件数が多い場合は、処理に時間がかかります.
     * この処理を呼び出すと、対象のLeveldbに登録されている
     * すべての要素をすべてクリアします.
     */
    public final void clear() {
        try {
            
            // Iteratorで削除するので、超遅い.
            LeveldbIterator it = leveldb.iterator() ;
            JniBuffer key = getKeyBuffer( null ) ;
            while( it.valid() ) {
                if( it.key( key ) > 0 ) {
                    leveldb.remove( key ) ;
                }
                it.next() ;
            }
        } catch( LeveldbException le ) {
            throw le ;
        } catch( Exception e ) {
            throw new LeveldbException( e ) ;
        }
    }
    
    /**
     * 指定Map情報の内容をすべてセット.
     * @param toMerge 追加対象のMapを設定します.
     */
    public final void putAll(Map toMerge) {
        Object k ;
        Iterator it = toMerge.keySet().iterator() ;
        while( it.hasNext() ) {
            k = it.next() ;
            if( k instanceof String ) {
                put( (String)k,toMerge.get( k ) ) ;
            }
        }
    }
    
    /**
     * 指定要素が存在するかチェック.
     * ※Iteratorでチェックするので、件数が多い場合は、処理に時間がかかります.
     * @param value 対象のValueを設定します.
     * @return boolean trueの場合、一致する条件が存在します.
     */
    public final boolean containsValue(Object value) {
        
        // Iteratorで、存在するまでチェック(超遅い).
        try {
            LeveldbIterator it = leveldb.iterator() ;
            JniBuffer v = getValueBuffer( null ) ;
            if( value == null ) {
                while( it.valid() ) {
                    if( it.value( v ) > 0 ) {
                        if( ValueBinary.decode( v ) == null ) {
                            return true ;
                        }
                    }
                    it.next() ;
                }
            }
            else {
                while( it.valid() ) {
                    if( it.value( v ) > 0 ) {
                        if( value.equals( ValueBinary.decode( v ) ) ) {
                            return true ;
                        }
                    }
                    it.next() ;
                }
            }
            return false ;
        } catch( LeveldbException le ) {
            throw le ;
        } catch( Exception e ) {
            throw new LeveldbException( e ) ;
        }
    }
    
    /**
     * この処理はLeveMapでは何もしません.
     * return 例外が返却されます.
     */
    public Set entrySet() {
        throw new LeveldbException( "サポートされていません" ) ;
    }
    
    /**
     * この処理はLeveMapでは何もしません.
     * return 例外が返却されます.
     */
    public Collection values() {
        throw new LeveldbException( "サポートされていません" ) ;
    }
    
    /**
     * 指定キーの情報をセット.
     * @param name 対象のキーを設定します.
     * @param value 対象の要素を設定します.
     *              この条件は、数値、文字列、日付系(java.util.Date),配列、
     *              List、Map、Set、Serializableオブジェクト以外をセットすると、
     *              エラーととなります.
     * @return Object [null]が返却されます.
     */
    public final Object put(String name, Object value) {
        if( name == null ) {
            return null ;
        }
        else if( value != null && value instanceof LevelMap ) {
            throw new LeveldbException( "要素にLevelMap要素は設定できません" ) ;
        }
        try {
            JniBuffer keyBuf = getKeyBuffer( name ) ;
            JniBuffer valBuf = getValueBuffer( value ) ;
            leveldb.put( keyBuf,valBuf ) ;
            keyBuf.clear() ; valBuf.clear() ;
            return null ;
        } catch( LeveldbException le ) {
            throw le ;
        } catch( Exception e ) {
            throw new LeveldbException( e ) ;
        }
    }
    
    /**
     * 指定キー情報が存在するかチェック.
     * @param key 対象のキーを設定します.
     * @return boolean [true]の場合、存在します.
     */
    public final boolean containsKey(Object key) {
        if( key == null ) {
            return false ;
        }
        try {
            JniBuffer keyBuf = getKeyBuffer( (String)key ) ;
            JniBuffer valBuf = getValueBuffer( null ) ;
            int res = leveldb.get( valBuf,keyBuf ) ;
            keyBuf.clear() ;valBuf.clear() ;
            return res != 0 ;
        } catch( LeveldbException le ) {
            throw le ;
        } catch( Exception e ) {
            throw new LeveldbException( e ) ;
        }
    }
    
    /**
     * 指定キー情報に対する要素を取得.
     * @param key 対象のキーを設定します.
     * @return Object 対象の要素が返却されます.
     */
    public final Object get(Object key) {
        if( key == null ) {
            return null ;
        }
        try {
            JniBuffer keyBuf = getKeyBuffer( (String)key ) ;
            JniBuffer valBuf = getValueBuffer( null ) ;
            int res = leveldb.get( valBuf,keyBuf ) ;
            if( res == 0 ) {
                return null ;
            }
            Object ret = ValueBinary.decode( valBuf ) ;
            keyBuf.clear() ;valBuf.clear() ;
            return ret ;
        } catch( LeveldbException le ) {
            throw le ;
        } catch( Exception e ) {
            throw new LeveldbException( e ) ;
        }
    }
    
    /**
     * 指定キーの情報を削除.
     * @param key 対象のキーを設定します.
     * @return Object 削除できた場合[true]が返却されます.
     */
    public final Object remove(Object key) {
        if( key == null ) {
            return false ;
        }
        try {
            JniBuffer keyBuf = getKeyBuffer( (String)key ) ;
            Object ret = leveldb.remove( keyBuf ) ;
            keyBuf.clear() ;
            return ret ;
        } catch( LeveldbException le ) {
            throw le ;
        } catch( Exception e ) {
            throw new LeveldbException( e ) ;
        }
    }
    
    /**
     * 情報が空かチェック.
     * return boolean [false]が返却されます.
     */
    public final boolean isEmpty() {
        try {
            // 1件以上のIteratorが存在する場合は[false].
            LeveldbIterator it = leveldb.iterator() ;
            if( it.valid() ) {
                it.close() ;
                return false ;
            }
            it.close() ;
            return true ;
        } catch( LeveldbException le ) {
            throw le ;
        } catch( Exception e ) {
            throw new LeveldbException( e ) ;
        }
    }
    
    /**
     * Setオブジェクトを取得.
     * @return Set Setオブジェクトが返却されます.
     */
    public final Set keySet() {
        return set ;
    }
    
    /**
     * 登録データ数を取得.
     * ※Iteratorでカウントするので、件数が多い場合は、処理に時間がかかります.
     * return int 登録データ数が返却されます.
     */
    public final int size() {
        try {
            int ret = 0 ;
            
            // Iteratorで削除するので、超遅い.
            LeveldbIterator it = leveldb.iterator() ;
            while( it.valid() ) {
                ret ++ ;
                it.next() ;
            }
            it.close() ;
            return ret ;
        } catch( LeveldbException le ) {
            throw le ;
        } catch( Exception e ) {
            throw new LeveldbException( e ) ;
        }
    }
    
    /**
     * この処理はLeveMapでは何もしません.
     * @param set 例外が発生します.
     */
    public final void getAllKey( Set<Object> set ) {
        throw new LeveldbException( "サポートされていません" ) ;
    }
    
    /**
     * この処理はLeveMapでは何もしません.
     * @param set 例外が発生します.
     */
    public final void getAllValues( Set<Object> set ) {
        throw new LeveldbException( "サポートされていません" ) ;
    }
    
    
    /**
     * この処理はLeveMapでは何もしません.
     * @return String 空文字が返却されます.
     */
    public final String toString() {
        
        // 何もしない.
        return "" ;
    }
    
    /** LevelMapSet. **/
    private static final class LevelMapSet implements Set<String> {
        private final LevelMap map ;
        public LevelMapSet( LevelMap map ) {
            this.map = map ;
        }
        
        public final boolean add(String arg0) {
            map.put( arg0,null ) ;
            return false;
        }
        public final boolean addAll(Collection<? extends String> arg0) {
            Object o ;
            Iterator it = arg0.iterator() ;
            while( it.hasNext() ) {
                o = it.next() ;
                if( o instanceof String ) {
                    add( (String)o ) ;
                }
            }
            return true ;
        }
        public final void clear() {
            map.clear() ;
        }
        public final boolean contains(Object arg0) {
            return map.containsKey( arg0 ) ;
        }
        public final boolean containsAll(Collection<?> arg0) {
            Object o ;
            Iterator it = arg0.iterator() ;
            while( it.hasNext() ) {
                o = it.next() ;
                if( o instanceof String && map.containsKey(o) ) {
                    continue ;
                }
                return false ;
            }
            return true ;
        }
        public final boolean isEmpty() {
            return map.isEmpty() ;
        }
        public final Iterator<String> iterator() {
            return new LevelMapIterator( map ) ;
        }
        public final boolean remove(Object arg0) {
            return (Boolean)map.remove( arg0 );
        }
        public final boolean removeAll(Collection<?> arg0) {
            boolean ret = false ;
            Object o ;
            Iterator it = arg0.iterator() ;
            while( it.hasNext() ) {
                o = it.next() ;
                if( o instanceof String && (Boolean)map.remove(o) ) {
                    ret = true ;
                }
            }
            return ret ;
        }
        public final boolean retainAll(Collection<?> arg0) {
            throw new LeveldbException( "サポートされていません" ) ;
        }
        public final int size() {
            return map.size() ;
        }
        public final Object[] toArray() {
            throw new LeveldbException( "サポートされていません" ) ;
        }
        public final <T> T[] toArray(T[] arg0) {
            throw new LeveldbException( "サポートされていません" ) ;
        }
    }
    
    /** LevelMapIterator. **/
    private static final class LevelMapIterator implements Iterator<String> {
        private final LevelMap map ;
        private final LeveldbIterator itr ;
        private String nowKey ;
        public LevelMapIterator( LevelMap map ) {
            this.map = map ;
            this.itr = map.leveldb.iterator() ;
        }
        public final boolean hasNext() {
            if( !itr.valid() ) {
                nowKey = null ;
                return false ;
            }
            return true ;
        }
        public final String next() {
            try {
                JniBuffer buf = LevelMap.getKeyBuffer( null ) ;
                if( itr.key( buf ) <= 0 ) {
                    nowKey = null ;
                }
                else {
                    nowKey = buf.getString() ;
                }
                buf.clear() ;
                itr.next();
                return nowKey ;
            } catch( LeveldbException le ) {
                throw le ;
            } catch( Exception e ) {
                throw new LeveldbException( e ) ;
            }
        }
        public final void remove() {
            if( nowKey != null ) {
                map.remove( nowKey ) ;
            }
        }
    }
}
