package org.maachang.dbm ;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.maachang.dbm.engine.M2Engine;
import org.maachang.dbm.engine.M2NextKey;
import org.maachang.util.FnvHash;

/**
 * MaachangDbmでの１つのトランザクションを管理.
 * 
 * @version 2008/01/18
 * @author masahito suzuki
 * @since MaachangDBM 1.02
 */
class MDbmTransaction implements MDbm {
    
    /**
     * トランザクション処理タイプ : 情報追加.
     */
    protected static final int TYPE_PUT = 1 ;
    
    /**
     * トランザクション処理タイプ : 情報削除.
     */
    protected static final int TYPE_REMOVE = 2 ;
    
    /**
     * DBMオブジェクト.
     */
    private MDbmImpl impl = null ;
    
    /**
     * トランザクションデータ管理.
     */
    private Map<MBinary,MTransaction> man = null ;
    
    /**
     * 処理カウント.
     */
    private int count = -1 ;
    
    /**
     * コンストラクタ.
     */
    private MDbmTransaction() {
        
    }
    
    /**
     * コンストラクタ.
     * <BR>
     * @param impl DBMオブジェクトを設定します.
     */
    public MDbmTransaction( MDbmImpl impl ) {
        this.impl = impl ;
        this.man = Collections.synchronizedMap( new HashMap<MBinary,MTransaction>() ) ;
        this.count = 0 ;
    }
    
    /**
     * デストラクタ.
     */
    protected void finalize() throws Exception {
        try {
            commit() ;
        } catch( Exception e ) {
        }
    }
    
    /**
     * MDbmオブジェクトを取得.
     * <BR>
     * @return MDbm オブジェクトが返されます.
     */
    public MDbm getParent() {
        return impl ;
    }
    
    /**
     * クリアー処理.
     * <BR>
     * @exception Exception 例外.
     */
    public void close() throws Exception {
        commit() ;
    }
    
    /**
     * コミット処理.
     * <BR>
     * @exception Exception 例外.
     */
    public synchronized void commit() throws Exception {
        if( isUse() == false ) {
            throw new IOException( "既にオブジェクトは破棄されています" ) ;
        }
        if( man.size() <= 0 ) {
            return ;
        }
        this.check() ;
        try {
            int len = man.size() ;
            MTransaction[] trans = new MTransaction[ len ] ;
            Object[] k = man.keySet().toArray() ;
            for( int i = 0 ; i < len ; i ++ ) {
                trans[ i ] = man.get( ( MBinary )k[ i ] ) ;
            }
            k = null ;
            clearMan() ;
            Arrays.sort( trans ) ;
            for( int i = 0 ; i < len ; i ++ ) {
                int type = trans[ i ].getType() ;
                switch( type ) {
                    case TYPE_PUT :
                        impl.put( trans[ i ].getKey().getBinary(),trans[ i ].getValue() ) ;
                        break ;
                    case TYPE_REMOVE :
                        impl.remove( trans[ i ].getKey().getBinary() ) ;
                        break ;
                }
                trans[ i ] = null ;
            }
        } catch( Exception e ) {
            clearMan() ;
            throw e ;
        }
    }
    
    /**
     * ロールバック処理.
     * <BR>
     * @exception Exception 例外.
     */
    public synchronized void rollback() throws Exception {
        if( isUse() == false ) {
            throw new IOException( "既にオブジェクトは破棄されています" ) ;
        }
        clearMan() ;
    }
    
    /**
     * データ登録が可能かチェック.
     * <BR>
     * @exception Exception 例外.
     */
    public synchronized void check() throws Exception {
        if( isUse() == false ) {
            throw new IOException( "既にオブジェクトは破棄されています" ) ;
        }
    }
    
    /**
     * 情報を設定.
     * <BR>
     * @param key 対象のキー情報を設定します.
     * @param value 対象の情報を設定します.
     * @exception Exception 例外.
     */
    public synchronized void put( byte[] key,byte[] value ) throws Exception {
        if( isUse() == false ) {
            throw new IOException( "既にオブジェクトは破棄されています" ) ;
        }
        if( key == null || value == null || value.length <= 0 ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        MBinary b = new MBinary( key ) ;
        MTransaction t = new MTransaction( count,TYPE_PUT,b,value ) ;
        man.put( b,t ) ;
        count ++ ;
    }
    
    /**
     * 情報を削除.
     * <BR>
     * @param key 対象のキー情報を設定します.
     * @exception Exception 例外.
     */
    public synchronized void remove( byte[] key ) throws Exception {
        if( isUse() == false ) {
            throw new IOException( "既にオブジェクトは破棄されています" ) ;
        }
        MBinary b = new MBinary( key ) ;
        MTransaction t = man.get( b ) ;
        if( t == null ) {
            if( impl.containsKey( key ) == false ) {
                return ;
            }
        }
        else {
            man.remove( b ) ;
            return ;
        }
        t = new MTransaction( count,TYPE_REMOVE,b,null ) ;
        man.put( b,t ) ;
        count ++ ;
    }
    
    /**
     * 情報を取得.
     * <BR>
     * @param key 対象のキー情報を設定します.
     * @return byte[] 対象の情報が返されます.
     * @exception Exception 例外.
     */
    public synchronized byte[] get( byte[] key ) throws Exception {
        if( isUse() == false ) {
            throw new IOException( "既にオブジェクトは破棄されています" ) ;
        }
        MBinary b = new MBinary( key ) ;
        MTransaction t = man.get( b ) ;
        if( t == null ) {
            return impl.get( key ) ;
        }
        if( t.getType() == TYPE_REMOVE ) {
            return null ;
        }
        return t.getValue() ;
    }
    
    /**
     * 指定キーが存在するかチェック.
     * <BR>
     * @param key チェック対象のキー内容を設定します.
     * @return boolean [true]の場合、情報が存在します.
     * @exception Exception 例外.
     */
    public synchronized boolean containsKey( byte[] key) throws Exception {
        if( isUse() == false ) {
            throw new IOException( "既にオブジェクトは破棄されています" ) ;
        }
        MBinary b = new MBinary( key ) ;
        MTransaction t = man.get( b ) ;
        if( t == null ) {
            return impl.containsKey( key ) ;
        }
        if( t.getType() == TYPE_REMOVE ) {
            return false ;
        }
        return true ;
    }
    
    /**
     * キー内容を列挙.
     * <BR><BR>
     * @return  Enumeration<byte[]> 列挙オブジェクトが返されます.
     */
    public Enumeration<byte[]> elements() {
        if( isUse() == false ) {
            return null ;
        }
        return new MDbmTranEnum( impl.getEngine(),man ) ;
    }
    
    /**
     * 新しいシーケンスIDを取得.
     * <BR><BR>
     * 新しいシーケンスIDを取得します.<BR>
     * このメソッドはトランザクションに対応しません.
     * <BR>
     * @param no シーケンスNoを設定します.<BR>
     *           [0-63]まで利用可能です.
     * @return long 新しいシーケンスIDが返されます.
     * @exception Exception 例外.
     */
    public long sequenceId( int no ) throws Exception {
        if( isUse() == false ) {
            throw new IOException( "既にオブジェクトは破棄されています" ) ;
        }
        return impl.sequenceId( no ) ;
    }
    
    /**
     * 格納情報数を取得.
     * <BR>
     * @return int 格納情報数が返されます.<BR>
     *             [-1]が返された場合、オブジェクトは既に破棄されています.
     */
    public synchronized int size() {
        if( isUse() == false ) {
            return -1 ;
        }
        int pls = 0 ;
        int len = man.size() ;
        if( len > 0 ) {
            Object[] k = man.keySet().toArray() ;
            for( int i = 0 ; i < len ; i ++ ) {
                switch( man.get( ( MBinary )k[ i ] ).getType() ) {
                    case TYPE_PUT : pls ++ ; break ;
                    case TYPE_REMOVE : pls -- ; break ;
                }
            }
        }
        return impl.size() + pls ;
    }
    
    /**
     * MaachangDbm展開ディレクトリを取得.
     * <BR>
     * @return String MaachangDbm展開ディレクトリ名が返されます.
     */
    public synchronized String getDirectory() {
        if( isUse() == false ) {
            return null ;
        }
        return impl.getDirectory() ;
    }
    
    /**
     * このオブジェクトが有効かチェック.
     * <BR>
     * @return boolean [true]の場合、有効です.
     */
    public synchronized boolean isUse() {
        if( impl == null || man == null ) {
            return false ;
        }
        return impl.isUse() ;
    }
    
    /**
     * このオブジェクトがトランザクション対応かチェック.
     * <BR>
     * @return boolean [true]の場合、トランザクションに対応しています.
     */
    public synchronized boolean isTransaction() {
        return true ;
    }
    
    private void clearMan() {
        if( man != null ) {
            man.clear() ;
        }
        count = 0 ;
    }
    
}

/**
 * バイナリキー管理.
 */
class MBinary {
    private byte[] binary = null ;
    int hash = -1 ;
    
    public MBinary( byte[] binary )
        throws Exception {
        this.binary = binary ;
        this.hash = FnvHash.fnv32a( binary ) ;
    }
    
    protected void finalize() throws Exception {
        this.binary = null ;
    }
    
    public byte[] getBinary() {
        return this.binary ;
    }
    
    public int hashCode() {
        return this.hash ;
    }
    
    public boolean equals( Object o ) {
        if( o == null || ( o instanceof MBinary ) == false ) {
            return false ;
        }
        MBinary c = ( MBinary )o ;
        byte[] b = c.getBinary() ;
        if( c.hashCode() == this.hash && b.length == this.binary.length ) {
            int len = this.binary.length ;
            for( int i = 0 ; i < len ; i ++ ) {
                if( b[ i ] != this.binary[ i ] ) {
                    return false ;
                }
            }
            return true ;
        }
        return false ;
    }
}

/**
 * １つのトランザクション命令.
 */
class MTransaction implements Comparable<MTransaction> {
    
    int no = -1 ;
    int type = -1 ;
    private MBinary key = null ;
    private byte[] value = null ;
    
    public MTransaction( int no,int type,MBinary key,byte[] value ) {
        this.no = no ;
        this.type = type ;
        this.key = key ;
        this.value = value ;
    }
    
    public int getType() {
        return type ;
    }
    
    public MBinary getKey() {
        return key ;
    }
    
    public byte[] getValue() {
        return value ;
    }
    
    public int getNo() {
        return no ;
    }
    
    public int compareTo( MTransaction o ) {
        // 低いほうを先頭に(降順).
        return no - o.no ;
    }
}

/**
 * トランザクション部分を含むキーリスト表示用.
 */
class MDbmTranEnum implements Enumeration<byte[]> {
    
    private M2Engine engine = null ;
    private M2NextKey next = null ;
    private Map<MBinary,MTransaction> man = null ;
    private List<MTransaction> putTrans = null ;
    private int pos = -1 ;
    
    protected MDbmTranEnum( M2Engine engine,Map<MBinary,MTransaction> man ) {
        this.engine = engine ;
        this.man = man ;
    }
    
    protected void finalize() throws Exception {
        engine = null ;
        next = null ;
        man = null ;
        putTrans = null ;
        pos = -1 ;
    }
    
    public boolean hasMoreElements() {
        boolean ret = false ;
        if( pos <= -1 ) {
            ret = hasFile() ;
            if( ret == false ) {
                createTranList() ;
            }
        }
        if( pos >= 0 ) {
            ret = hasTran() ;
        }
        return ret ;
    }
    
    public byte[] nextElement() {
        byte[] ret = null ;
        if( pos <= -1 ) {
            ret = nextFile() ;
            if( ret == null ) {
                createTranList() ;
            }
        }
        if( pos >= 0 ) {
            ret = nextTran() ;
        }
        return ret ;
    }
    
    private boolean hasFile() {
        boolean ret = false ;
        try {
            M2NextKey n = next ;
            for( ;; ) {
                n = this.engine.next( n ) ;
                if( n == null ) {
                    ret = false ;
                    break ;
                }
                MTransaction t = man.get( new MBinary( n.getKey() ) ) ;
                if( t == null || t.getType() != MDbmTransaction.TYPE_REMOVE ) {
                    ret = true ;
                    break ;
                }
            }
        } catch( Exception e ) {
            ret = false ;
        }
        return ret ;
    }
    
    private boolean hasTran() {
        if( pos <= -1 || putTrans == null || putTrans.size() <= pos ) {
            return false ;
        }
        return true ;
    }
    
    private byte[] nextFile() {
        byte[] ret = null ;
        try {
            for( ;; ) {
                next = this.engine.next( next ) ;
                if( next == null ) {
                    ret = null ;
                    break ;
                }
                MTransaction t = man.get( new MBinary( next.getKey() ) ) ;
                if( t == null || t.getType() != MDbmTransaction.TYPE_REMOVE ) {
                    ret = next.getKey() ;
                    break ;
                }
            }
        } catch( Exception e ) {
            ret = null ;
        }
        return ret ;
    }
    
    private byte[] nextTran() {
        if( pos <= -1 || putTrans == null || putTrans.size() <= pos ) {
            return null ;
        }
        byte[] ret = putTrans.get( pos ).getKey().getBinary() ;
        pos ++ ;
        return ret ;
    }
    
    private void createTranList() {
        if( pos > 0 ) {
            return ;
        }
        pos = 0 ;
        if( man.size() <= 0 ) {
            
            return ;
        }
        ArrayList<MTransaction> lst = new ArrayList<MTransaction>() ;
        Object[] o = man.keySet().toArray() ;
        int len = o.length ;
        for( int i = 0 ; i < len ; i ++ ) {
            MTransaction t = man.get( ( MBinary )o[ i ] ) ;
            if( t.getType() == MDbmTransaction.TYPE_PUT ) {
                lst.add( t ) ;
            }
        }
        o = null ;
        if( lst.size() <= 0 ) {
            lst = null ;
        }
        putTrans = lst ;
    }
}

