package org.maachang.dbm.engine ;

import java.io.IOException;
import java.util.Enumeration;

import org.maachang.util.FnvHash;

/**
 * MaachangDbm2Engine.
 * 
 * @version 2008/06/05
 * @author masahito suzuki
 * @since MaachangDBM 1.12
 */
public class M2Engine {
    
    /**
     * Key管理.
     */
    private M2Key key = null ;
    
    /**
     * 要素管理.
     */
    private M2Sector sector = null ;
    
    /**
     * キーロックオブジェクト.
     */
    private final M2Lock nsync = new M2Lock() ;
    
    /**
     * M2EngineShutdown.
     */
    private M2EngineShutdown m2Down = null ;
    
    /**
     * 基本同期.
     */
    private final Object sync = new Object() ;
    
    /**
     * コンストラクタ.
     */
    private M2Engine() {
        
    }
    
    /**
     * コンストラクタ.
     * <BR><BR>
     * 条件を設定してオブジェクトを生成します.
     * <BR>
     * @param key 対象のキー管理オブジェクトを設定します.
     * @param sector 対象の要素管理オブジェクトを設定します.
     * @exception Exception 例外.
     */
    public M2Engine( M2Key key,M2Sector sector ) throws Exception {
        if( key == null || sector == null ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        this.key = key ;
        this.sector = sector ;
        this.m2Down = new M2EngineShutdown( this ) ;
        
    }
    
    /**
     * デストラクタ.
     * <BR>
     * @exception Exception 例外.
     */
    protected void finalize() throws Exception {
        this.destroy() ;
    }
    
    /**
     * オブジェクト破棄.
     */
    public void destroy() {
        this.m2Down.run() ;
    }
    
    /**
     * オブジェクト破棄.
     */
    protected void destroyObject() {
        synchronized( sync ) {
            if( sector != null ) {
                sector.destroy() ;
            }
            if( key != null ) {
                key.destroy() ;
            }
            this.key = null ;
            this.sector = null ;
        }
    }
    
    /**
     * シャットダウンフックをONに設定.
     */
    public void setShutdown() {
        Runtime.getRuntime().addShutdownHook( m2Down ) ;
    }
    
    /**
     * 情報を設定.
     * <BR>
     * @param key 対象のキー情報を設定します.
     * @param value 対象の情報を設定します.
     * @exception Exception 例外.
     */
    public void put( byte[] key,byte[] value ) throws Exception {
        if( isUse() == false ) {
            throw new IOException( "既に破棄されています" ) ;
        }
        if( key == null || key.length <= 0 ||
            value == null || value.length <= 0 ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        int code = FnvHash.fnv32a( key ) ;
        nsync.lock( code ) ;
        try {
            long vp = sector.writeAll( MDbmDefine.SECTOR_TYPE_NORMAL,
                value,0,value.length ) ;
            this.key.add( code,key,
                ( int )( vp & 0x00000000ffffffffL ),
                ( int )( ( vp & 0xffffffff00000000L ) >> 32L ) ) ;
        } finally {
            nsync.unlock( code ) ;
        }
    }
    
    /**
     * 情報を削除.
     * <BR>
     * @param key 対象のキー情報を設定します.
     * @exception Exception 例外.
     */
    public void remove( byte[] key ) throws Exception {
        if( isUse() == false ) {
            throw new IOException( "既に破棄されています" ) ;
        }
        if( key == null || key.length <= 0 ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        int code = FnvHash.fnv32a( key ) ;
        nsync.lock( code ) ;
        try {
            this.key.remove( code,key ) ;
        } finally {
            nsync.unlock( code ) ;
        }
    }
    
    /**
     * 情報を取得.
     * <BR>
     * @param key 対象のキー情報を設定します.
     * @return byte[] 対象の情報が返されます.
     * @exception Exception 例外.
     */
    public byte[] get( byte[] key ) throws Exception {
        if( isUse() == false ) {
            throw new IOException( "既に破棄されています" ) ;
        }
        if( key == null || key.length <= 0 ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        byte[] ret = null ;
        int code = FnvHash.fnv32a( key ) ;
        nsync.lock( code ) ;
        try {
            long vp = this.key.get( code,key ) ;
            if( vp >= 0L ) {
                ret = sector.readAll( ( int )( vp & 0x00000000ffffffffL ),
                    ( int )( ( vp & 0xffffffff00000000L ) >> 32L ) ) ;
            }
        } finally {
            nsync.unlock( code ) ;
        }
        return ret ;
    }
    
    /**
     * 指定キーが存在するかチェック.
     * <BR>
     * @param key チェック対象のキー内容を設定します.
     * @return boolean [true]の場合、情報が存在します.
     * @exception Exception 例外.
     */
    public boolean containsKey( byte[] key) throws Exception {
        if( isUse() == false ) {
            throw new IOException( "既に破棄されています" ) ;
        }
        if( key == null || key.length <= 0 ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        boolean ret = false ;
        int code = FnvHash.fnv32a( key ) ;
        nsync.lock( code ) ;
        try {
            long vp = this.key.get( code,key ) ;
            ret = ( vp >= 0L ) ? true : false ;
        } finally {
            nsync.unlock( code ) ;
        }
        return ret ;
    }
    
    /**
     * 指定位置の内容を書き込む.
     * <BR>
     * @param key 対象のキー情報を設定します.
     * @param value 対象の情報を設定します.
     * @param pos 対象のポジションを設定します.
     * @param off 対象のオフセット値を設定します.
     * @param length 対象のデータ長を設定します.
     * @return int 書き込まれたデータ長が返されます.
     * @exception Exception 例外.
     */
    public int write( byte[] key,byte[] value,int pos,int off,int length )
        throws Exception {
        if( isUse() == false ) {
            throw new IOException( "既に破棄されています" ) ;
        }
        if( key == null || key.length <= 0 || value == null || value.length <= 0 ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        if( off <= 0 ) {
            off = 0 ;
        }
        if( length <= 0 ) {
            length = value.length ;
        }
        if( off + length >= value.length ) {
            length = value.length - off ;
        }
        int ret = 0 ;
        int code = FnvHash.fnv32a( key ) ;
        nsync.lock( code ) ;
        try {
            long vp = this.key.get( code,key ) ;
            if( vp <= -1L ) {
                return -1 ;
            }
            int p = 0 ;
            int pnt = ( int )( vp & 0x00000000ffffffffL ) ;
            int fileNo = ( int )( ( vp & 0xffffffff00000000L ) >> 32L ) ;
            M2SectorData data = new M2SectorData() ;
            for( ;; ) {
                sector.readOne( data,pnt,fileNo ) ;
                int len = data.getLength() ;
                if( p + len > pos ) {
                    int st = pos - p ;
                    if( st <= -1 ) {
                        st = 0 ;
                    }
                    byte[] b = data.getData() ;
                    for( int i = st ; i < len ; i ++ ) {
                        if( ret >= length ) {
                            sector.writeOne( data,pnt,fileNo ) ;
                            return ret ;
                        }
                        b[ i ] = value[ off ] ;
                        off ++ ;
                        ret ++ ;
                    }
                    sector.writeOne( data,pnt,fileNo ) ;
                }
                if( data.getNextNo() <= -1 || data.getNextFileNo() <= -1 ) {
                    break ;
                }
                p += len ;
                pnt = data.getNextNo() ;
                fileNo = data.getNextFileNo() ;
            }
        } finally {
            nsync.unlock( code ) ;
        }
        return ret ;
    }
    
    /**
     * 指定位置の内容を読み込む.
     * <BR>
     * @param key 対象のキー情報を設定します.
     * @param value 対象の情報を設定します.
     * @param pos 対象のポジションを設定します.
     * @param off 対象のオフセット値を設定します.
     * @param length 対象のデータ長を設定します.
     * @return int 取得されたデータ長が返されます.
     * @exception Exception 例外.
     */
    public int read( byte[] key,byte[] value,int pos,int off,int length )
        throws Exception {
        if( isUse() == false ) {
            throw new IOException( "既に破棄されています" ) ;
        }
        if( key == null || key.length <= 0 || value == null || value.length <= 0 ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        if( off <= 0 ) {
            off = 0 ;
        }
        if( length <= 0 ) {
            length = value.length ;
        }
        if( off + length >= value.length ) {
            length = value.length - off ;
        }
        int ret = 0 ;
        int code = FnvHash.fnv32a( key ) ;
        nsync.lock( code ) ;
        try {
            long vp = this.key.get( code,key ) ;
            if( vp <= -1L ) {
                return -1 ;
            }
            int p = 0 ;
            int pnt = ( int )( vp & 0x00000000ffffffffL ) ;
            int fileNo = ( int )( ( vp & 0xffffffff00000000L ) >> 32L ) ;
            M2SectorData data = new M2SectorData() ;
            for( ;; ) {
                sector.readOne( data,pnt,fileNo ) ;
                int len = data.getLength() ;
                if( p + len > pos ) {
                    int st = pos - p ;
                    if( st <= -1 ) {
                        st = 0 ;
                    }
                    byte[] b = data.getData() ;
                    for( int i = st ; i < len ; i ++ ) {
                        if( ret >= length ) {
                            return ret ;
                        }
                        value[ off ] = b[ i ] ;
                        off ++ ;
                        ret ++ ;
                    }
                }
                if( data.getNextNo() <= -1 || data.getNextFileNo() <= -1 ) {
                    break ;
                }
                p += len ;
                pnt = data.getNextNo() ;
                fileNo = data.getNextFileNo() ;
            }
        } finally {
            nsync.unlock( code ) ;
        }
        return ret ;
    }
    
    /**
     * キー情報一覧を取得.
     * <BR>
     * @param nextKey 次のキー位置を保持するオブジェクトを設定します.<BR>
     *                [null]を設定した場合、初めから取得します.
     * @return NextKey 次のキー情報を格納したオブジェクトが返されます.<BR>
     *                 [null]の場合、それ以上キー情報は存在しません.
     * @exception Exception 例外.
     */
    public M2NextKey next( M2NextKey nextKey ) throws Exception {
        if( isUse() == false ) {
            throw new IOException( "既に破棄されています" ) ;
        }
        return key.nextKey( nextKey ) ;
    }
    
    /**
     * キー内容を列挙.
     * <BR><BR>
     * @return  Enumeration<byte[]> 列挙オブジェクトが返されます.
     */
    public Enumeration<byte[]> elements() {
        return new M2Enumeration( this ) ;
    }
    
    /**
     * 新しいシーケンスIDを取得.
     * <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 key.getHash().sequenceId( no ) ;
    }
    
    /**
     * 格納情報数を取得.
     * <BR>
     * @return int 格納情報数が返されます.<BR>
     *             [-1]が返された場合、オブジェクトは既に破棄されています.
     */
    public int size() {
        int ret = -1 ;
        try {
            if( isUse() == true ) {
                ret = key.size() ;
            }
        } catch( Exception e ) {
            ret = -1 ;
        }
        return ret ;
    }
    
    /**
     * このオブジェクトが有効かチェック.
     * <BR>
     * @return boolean [true]の場合、有効です.
     */
    public boolean isUse() {
        boolean ret = false ;
        synchronized( sync ) {
            ret = ( key != null && sector != null ) ;
        }
        return ret ;
    }
    
    /**
     * 同期オブジェクトを取得.
     * <BR>
     * @return Object 同期オブジェクトが返されます.
     */
    public Object sync() {
        return sync ;
    }
    
    /**
     * Sectorオブジェクトを取得.
     * @return M2Sector セクタオブジェクトが返されます.
     */
    public M2Sector getSector() {
        return sector ;
    }
    
    /**
     * Keyオブジェクトを取得.
     * @return M2Key Keyオブジェクトが返されます.
     */
    public M2Key getKey() {
        return key ;
    }
}

class M2EngineShutdown extends Thread {
    M2Engine engine = null ;
    protected M2EngineShutdown( M2Engine engine ) {
        this.engine = engine ;
    }
    public void run() {
        if( engine != null ) {
            engine.destroyObject() ;
        }
        engine = null ;
    }
}

