package org.maachang.dbm.engine ;

import java.io.IOException;
import java.util.Arrays;

import org.maachang.rawio.Rawio;
import org.maachang.rawio.RawioInstance;
import org.maachang.rawio.mapping.MappingOp;
import org.maachang.rawio.mapping.Mappingio;
import org.maachang.util.FileUtil;

/**
 * Hash管理(Raw).
 * 
 * @version 2008/06/05
 * @author masahito suzuki
 * @since MaachangDBM 1.14
 */
public class M2RawHash {
    
    /**
     * Hashサイズ.
     */
    public static final int MAX_HASH_SIZE = MDbmEnv.HASH ;
    
    /**
     * フラグ管理サイズ.
     */
    private static final int FLAG_POS_OFFSET = M2RawHashFlag.convertRect( MDbmEnv.HASH ) ;
    
    /**
     * Hashマスク.
     */
    public static final int MASK_HASH = MAX_HASH_SIZE - 1 ;
    
    /**
     * シーケンスID最大数.
     */
    public static final int MAX_SEQUENCE_ID = MDbmDefine.MAX_SEQUENCE_ID ;
    
    /**
     * 1Hashに格納するサイズ.
     */
    public static final int ONE_HASH = 8 ;
    
    /**
     * Mappingio.
     */
    private Mappingio io = null ;
    
    /**
     * MappingOp(Main).
     */
    private MappingOp op = null ;
    
    /**
     * ファイル名.
     */
    private String filename = null ;
    
    /**
     * データサイズ.
     */
    private int size = -1 ;
    
    /**
     * Hashポジション管理.
     */
    private M2RawHashFlag posMan = null ;
    
    /**
     * コンストラクタ.
     */
    private M2RawHash() {
        
    }
    
    /**
     * コンストラクタ.
     * <BR><BR>
     * ファイル名を設定して、オブジェクトを生成します.
     * <BR>
     * @param filename 対象のファイル名を設定します.
     * @exception Exception 例外.
     */
    public M2RawHash( String filename ) throws Exception {
        if( filename == null || ( filename = filename.trim() ).length() <= 0 ) {
            throw new IllegalArgumentException( "ファイル名は不正です" ) ;
        }
        filename = FileUtil.getFullPath( filename ) ;
        boolean isFile = FileUtil.isFileExists( filename ) ;
        Mappingio io = ( Mappingio )RawioInstance.open( true,filename ) ;
        int sector = io.getSector() ;
        int all = FLAG_POS_OFFSET + ( MAX_HASH_SIZE * ONE_HASH ) + ( MAX_SEQUENCE_ID * 8 ) + 4 ;
        all = ( all / sector ) + ( ( all % sector != 0 ) ? 1 : 0 ) ;
        MappingOp op ;
        if( isFile == false ) {
            io.expansion( all ) ;
            op = io.addMapping( 0,all ) ;
            initFile( op ) ;
        }
        else {
            op = io.addMapping( 0,all ) ;
        }
        M2RawHashFlag pflg = new M2RawHashFlag( op,MAX_HASH_SIZE ) ;
        this.io = io ;
        this.op = op ;
        this.filename = filename ;
        this.posMan = pflg ;
    }
    
    /**
     * デストラクタ.
     * <BR>
     * @exception Exception 例外.
     */
    protected void finalize() throws Exception {
        this.destroy() ;
    }
    
    /**
     * オブジェクト破棄.
     */
    public synchronized void destroy() {
        if( io != null ) {
            try {
                ( ( Mappingio )io ).flush() ;
            } catch( Exception e ) {
            }
            ( ( Rawio )( ( Mappingio )io ).getBaseio() ).destroy() ;
        }
        io = null ;
        op = null ;
        filename = null ;
        size = -1 ;
        posMan = null ;
    }
    
    /**
     * Hashポジションを設定.
     * <BR>
     * @param hash 対象のHashコードを設定します.
     * @param pos 対象のポジション値を設定します.
     * @param fileNo 対象のファイルNoを設定します.
     * @exception Exception 例外.
     */
    public synchronized void put( int hash,int pos,int fileNo ) throws Exception {
        if( check() == false ) {
            throw new IOException( "既に破棄されています" ) ;
        }
        if( hash <= -1 || hash >= MAX_HASH_SIZE ) {
            throw new IllegalArgumentException( "指定Hashコード("+hash+")は範囲外です" ) ;
        }
        op.writeLong( ( ( ( long )pos & 0x00000000ffffffffL ) |
            ( ( ( long )fileNo & 0x00000000ffffffffL ) << 32L ) ),
            FLAG_POS_OFFSET + ( hash * ONE_HASH ) ) ;
        if( pos == -1 || fileNo == -1 ) {
            this.posMan.removePos( hash ) ;
        }
        else {
            this.posMan.setPos( hash ) ;
        }
    }
    
    /**
     * Hashポジションを削除.
     * <BR>
     * @param hash 対象のHashコードを設定します.
     * @exception Exception 例外.
     */
    public synchronized void remove( int hash ) throws Exception {
        this.put( hash,-1,-1 ) ;
    }
    
    /**
     * Hashポジションを取得.
     * <BR>
     * @param hash 対象のHashコードを設定します.
     * @return int 対象のポジション値が返されます.<BR>
     *              [-1]の場合、無効です.
     * @exception Exception 例外.
     */
    public synchronized long get( int hash ) throws Exception {
        if( check() == false ) {
            throw new IOException( "既に破棄されています" ) ;
        }
        if( hash <= -1 || hash >= MAX_HASH_SIZE ) {
            throw new IllegalArgumentException( "指定Hashコード("+hash+")は範囲外です" ) ;
        }
        return op.readLong( FLAG_POS_OFFSET + ( hash * ONE_HASH ) ) ;
    }
    
    /**
     * 次の有効Hash位置を取得.
     * @param pos 検索開始位置を設定します.
     * @return int 有効なHash位置が返されます.<BR>
     *             [-1]の場合、終端に到達しました.
     * @exception Exception 例外.
     */
    public synchronized int useHash( int pos ) throws Exception {
        if( check() == false ) {
            throw new IOException( "既に破棄されています" ) ;
        }
        return this.posMan.useNextPos( pos ) ;
    }
    
    /**
     * ファイル名を取得.
     * <BR>
     * @return String ファイル名が返されます.
     */
    public synchronized String getFileName() {
        if( check() == false ) {
            return null ;
        }
        return this.filename ;
    }
    
    /**
     * シーケンスオフセット値.
     */
    private static final int SEQUENCE_OFFSET = FLAG_POS_OFFSET + ( MAX_HASH_SIZE * ONE_HASH ) ;
    
    /**
     * 新しいシーケンスIDを取得.
     * <BR><BR>
     * 新しいシーケンスIDを取得します.
     * <BR>
     * @param no シーケンスNoを設定します.<BR>
     *           [0-63]まで利用可能です.
     * @return long 新しいシーケンスIDが返されます.
     * @exception Exception 例外.
     */
    public synchronized long sequenceId( int no ) throws Exception {
        if( check() == false ) {
            throw new IOException( "既に破棄されています" ) ;
        }
        if( no < 0 || no >= MAX_SEQUENCE_ID ) {
            return -1L ;
        }
        int off = SEQUENCE_OFFSET + ( no * 8 ) ;
        long ret = op.readLong( off ) ;
        if( ret >= Long.MAX_VALUE ) {
            ret = 0L ;
        }
        else {
            ret += 1L ;
        }
        op.writeLong( ret,off ) ;
        return ret ;
    }
    
    /**
     * サイズを１インクリメント.
     * @exception Exception 例外.
     */
    public synchronized void addOne() throws Exception {
        if( this.size <= -1 ) {
            readSize() ;
        }
        this.size ++ ;
        writeSize() ;
    }
    
    /**
     * サイズを１デクリメント.
     * @exception Exception 例外.
     */
    public synchronized void deleteOne() throws Exception {
        if( this.size <= -1 ) {
            readSize() ;
        }
        this.size -- ;
        if( this.size <= 0 ) {
            this.size = 0 ;
        }
        writeSize() ;
    }
    
    /**
     * サイズを取得.
     * @exception Exception 例外.
     */
    public synchronized int getSize() throws Exception {
        if( this.size <= -1 ) {
            readSize() ;
        }
        return this.size ;
    }
    
    /**
     * このオブジェクトが有効かチェック.
     * <BR>
     * @return boolean [true]の場合、有効です.
     */
    public synchronized boolean isUse() {
        return check() ;
    }
    
    private boolean check() {
        if( this.op == null ) {
            return false ;
        }
        return true ;
    }
    
    /**
     * ファイル初期化.
     */
    private static final void initFile( MappingOp op ) throws Exception {
        int off = ( MAX_HASH_SIZE * ONE_HASH ) + FLAG_POS_OFFSET ;
        byte[] b = new byte[ off + ( 8 * MAX_SEQUENCE_ID ) + 4 ] ;
        Arrays.fill( b,( byte )0xff ) ;
        for( int i = 0 ; i < FLAG_POS_OFFSET ; i ++ ) {
            b[ i ] = 0 ;
        }
        for( int i = 0 ; i < MAX_SEQUENCE_ID ; i ++ ) {
            b[ off ] = 0 ;
            b[ off+1 ] = 0 ;
            b[ off+2 ] = 0 ;
            b[ off+3 ] = 0 ;
            b[ off+4 ] = 0 ;
            b[ off+5 ] = 0 ;
            b[ off+6 ] = 0 ;
            b[ off+7 ] = 0 ;
            off += 8 ;
        }
        op.writeBytes( b,0,0,b.length ) ;
    }
    
    /**
     * データ格納オフセット.
     */
    private static final int DATA_SIZE_OFFSET = FLAG_POS_OFFSET + ( MAX_HASH_SIZE * ONE_HASH ) + ( MAX_SEQUENCE_ID * 8 ) ;
    
    /**
     * データサイズを取得.
     */
    private void readSize() throws Exception {
        this.size = op.readInt( DATA_SIZE_OFFSET ) ;
        if( this.size <= -1 ) {
            this.size = 0 ;
        }
    }
    
    /**
     * データサイズを書き込む.
     */
    private void writeSize() throws Exception {
        op.writeInt( this.size,DATA_SIZE_OFFSET ) ;
    }
}

/**
 * Hashポジション管理.
 */
class M2RawHashFlag {
    protected static final int ETC_MASK = 0x0000001f ;
    protected static final int FSS_MASK = ~ETC_MASK ;
    protected static final int RIGHT_SHIFT = 5 ;
    private MappingOp op = null ;
    private int size = -1 ;
    
    private M2RawHashFlag() {
        
    }
    
    public M2RawHashFlag( MappingOp op,int size )
        throws Exception {
        this.op = op ;
        this.size = size ;
    }
    
    public static final int convertRect( int size ) {
        return ( ( ( size & FSS_MASK ) >> RIGHT_SHIFT ) + ( ( ( size & ETC_MASK ) != 0 ) ? 1 : 0 ) ) * 4 ;
    }
    
    public void setPos( int pos ) throws Exception {
        int thisPos = ( ( pos & FSS_MASK ) >> RIGHT_SHIFT ) ;
        int innerPos = ( pos & ETC_MASK ) ;
        int target = mfArray( thisPos ) ;
        if( ( ( ( 1 << innerPos ) ) & target ) != 0 ) {
            throw new IOException( "指定項番["+pos+"]は既にONです" ) ;
        }
        target = ( 1 << innerPos ) | target ;
        mfArray( thisPos,target ) ;
    }
    
    public void removePos( int pos ) throws Exception {
        int thisPos = ( ( pos & FSS_MASK ) >> RIGHT_SHIFT ) ;
        int innerPos = ( pos & ETC_MASK ) ;
        int target = mfArray( thisPos ) ;
        if( ( ( ( 1 << innerPos ) ) & target ) == 0 ) {
            throw new IOException( "指定項番["+pos+"]は既にOFFです" ) ;
        }
        target = ( ~( 1 << innerPos ) ) & target ;
        mfArray( thisPos,target ) ;
    }
    
    public int useNextPos( int pos ) throws Exception {
        int one = 0 ;
        pos ++ ;
        if( ( pos & ETC_MASK ) != 0 ) {
            one = mfArray( ( ( pos & FSS_MASK ) >> RIGHT_SHIFT ) ) ;
        }
        for( ;; ) {
            if( pos >= size ) {
                return -1 ;
            }
            if( ( pos & ETC_MASK ) == 0 ) {
                one = mfArray( ( ( pos & FSS_MASK ) >> RIGHT_SHIFT ) ) ;
                if( one == 0 ) {
                    pos += 32 ;
                    continue ;
                }
            }
            if( targetPos( pos,one ) == true ) {
                return pos ;
            }
            pos ++ ;
        }
    }
    
    private int mfArray( int no ) throws Exception {
        return op.readInt( ( no * 4 ) ) ;
    }
    
    private void mfArray( int no,int n ) throws Exception {
        op.writeInt( n,( no * 4 ) ) ;
    }
    
    private boolean targetPos( int pos,int one ) throws Exception {
        return ( ( ( 1 << ( pos & ETC_MASK ) ) ) & one ) != 0 ;
    }
}
