package org.maachang.dbm.engine ;

import java.io.IOException;


/**
 * キー情報.
 * 
 * @version 2008/06/06
 * @author masahito suzuki
 * @since MaachangDBM 1.12
 */
public class M2Key {
    
    /**
     * ハッシュ管理.
     */
    private M2RawHash hash = null ;
    
    /**
     * セクタ管理.
     */
    private M2Sector sector = null ;
    
    /**
     * コンストラクタ.
     */
    private M2Key() {
        
    }
    
    /**
     * コンストラクタ.
     * @param hash ハッシュを設定します.
     * @param sector セクタを設定します.
     */
    public M2Key( M2RawHash hash,M2Sector sector ) {
        this.hash = hash ;
        this.sector = sector ;
    }
    
    /**
     * デストラクタ.
     */
    protected void finalize() throws Exception {
        destroy() ;
    }
    
    /**
     * オブジェクトを破棄.
     */
    public void destroy() {
        if( hash != null ) {
            hash.destroy() ;
        }
        hash = null ;
        sector = null ;
    }
    
    /**
     * 新しいキーを追加.
     * @param code 対象のHashコードを設定します.
     * @param key 対象のキーを設定します.
     * @param no 要素のセクタNoを設定します.
     * @param fileNo 要素のファイルNoを設定します.
     * @exception Exception 例外.
     */
    public void add( int code,byte[] key,int no,int fileNo )
        throws Exception {
        int code2 = secondHash( key ) ;
        M2KeyChild ch = new M2KeyChild() ;
        // 検索結果、一致する内容が存在する場合.
        if( search( ch,code,code2,key ) == true ) {
            // 前に設定されている要素は削除.
            sector.removeAll( ch.getElementPos(),ch.getElementFileNo() ) ;
            // 次の要素に対して、ポジションを上書き.
            ch.setElementPos( no ) ;
            ch.setElementFileNo( fileNo ) ;
            M2SectorData data = ch.header( sector.getBlockSize() ) ;
            sector.writeOne( data,ch.getStartPos(),ch.getStartFileNo() ) ;
        }
        // Hash管理には、何もセットされていない新規状態.
        else if( ch.getStartPos() == -1 ) {
            // 新しい領域で生成.
            ch.clear() ;
            ch.setElementPos( no ) ;
            ch.setElementFileNo( fileNo ) ;
            ch.setHashCode( code ) ;
            ch.setHashCode2( code2 ) ;
            ch.setData( key ) ;
            byte[] b = ch.save() ;
            // セクタに書き込む.
            long p = sector.writeAll( MDbmDefine.SECTOR_TYPE_KEY,b,0,b.length ) ;
            int newChPos = ( int )( p & 0x00000000ffffffffL ) ;
            int newChFNo = ( int )( ( p & 0xffffffff00000000L ) >> 32L ) ;
            // ハッシュにキー位置の内容を書き込む.
            hash.put( mask( code ),newChPos,newChFNo ) ;
            // Key数を１インクリメント.
            hash.addOne() ;
            ch = null ;
        }
        // Hash管理に条件が存在するので、次の領域に付加する.
        else {
            // 新しい領域を保存する.
            M2KeyChild newCh = new M2KeyChild() ;
            newCh.setBefPos( ch.getStartPos() ) ;
            newCh.setBefFileNo( ch.getStartFileNo() ) ;
            newCh.setElementPos( no ) ;
            newCh.setElementFileNo( fileNo ) ;
            newCh.setHashCode( code ) ;
            newCh.setHashCode2( code2 ) ;
            newCh.setData( key ) ;
            byte[] b = newCh.save() ;
            long p = sector.writeAll( MDbmDefine.SECTOR_TYPE_KEY,b,0,b.length ) ;
            int newChPos = ( int )( p & 0x00000000ffffffffL ) ;
            int newChFNo = ( int )( ( p & 0xffffffff00000000L ) >> 32L ) ;
            // キャッシュ追加.
            //putCache( newChPos,newChFNo,newCh ) ;
            // 一番最後の領域に対して、新しい領域と連結させる.
            ch.setNextPos( newChPos ) ;
            ch.setNextFileNo( newChFNo ) ;
            M2SectorData data = ch.header( sector.getBlockSize() ) ;
            sector.writeOne( data,ch.getStartPos(),ch.getStartFileNo() ) ;
            // Key数を１インクリメント.
            hash.addOne() ;
        }
    }
    
    /**
     * 指定キーを削除.
     * また、キーに対する要素も削除されます.
     * @param code 対象のHashコードを設定します.
     * @param key 削除対象のKeyを設定します.
     * @exception Exception 例外.
     */
    public void remove( int code,byte[] key ) throws Exception {
        int code2 = secondHash( key ) ;
        M2KeyChild ch = new M2KeyChild() ;
        // 検索結果、一致する内容が存在する場合.
        if( search( ch,code,code2,key ) == true ) {
            // 前に設定されている要素は削除.
            sector.removeAll( ch.getElementPos(),ch.getElementFileNo() ) ;
            sector.removeAll( ch.getStartPos(),ch.getStartFileNo() ) ;
            // Key数を１デクリメント.
            hash.deleteOne() ;
            // 削除内容に対して、前後の条件が存在する場合、
            // その内容を修正する.
            int befPos = ch.getBefPos() ;
            int befFileNo = ch.getBefFileNo() ;
            int nextPos = ch.getNextPos() ;
            int nextFileNo = ch.getNextFileNo() ;
            // 前後データ存在確認.
            if( nextPos <= -1 || nextFileNo <= -1 ) {
                nextPos = -1 ;
                nextFileNo = -1 ;
            }
            if( befPos <= -1 || befFileNo <= -1 ) {
                befPos = -1 ;
                befFileNo = -1 ;
            }
            // 前後の条件が存在しない場合.
            if( nextPos <= -1 && nextFileNo <= -1 &&
                befPos <= -1 && befFileNo <= -1 ) {
                // Hash情報を破棄.
                hash.remove( mask( code ) ) ;
            }
            else {
                // 削除前の条件が存在する場合.
                if( befPos >= 0 && befFileNo >= 0 ) {
                    M2SectorData data = new M2SectorData() ;
                    sector.readOne( data,befPos,befFileNo ) ;
                    ch.create( data,befPos,befFileNo ) ;
                    ch.setNextPos( nextPos ) ;
                    ch.setNextFileNo( nextFileNo ) ;
                    ch.header( sector.getBlockSize(),data ) ;
                    sector.writeOne( data,befPos,befFileNo ) ;
                }
                // 削除後の条件が存在する場合.
                if( nextPos >= 0 && nextFileNo >= 0 ) {
                    M2SectorData data = new M2SectorData() ;
                    sector.readOne( data,nextPos,nextFileNo ) ;
                    ch.create( data,nextPos,nextPos ) ;
                    ch.setBefPos( befPos ) ;
                    ch.setBefFileNo( befFileNo ) ;
                    ch.header( sector.getBlockSize(),data ) ;
                    sector.writeOne( data,nextPos,nextFileNo ) ;
                    // 先頭の条件の場合.
                    if( befPos <= -1 || befFileNo <= -1 ) {
                        // Hash管理に直接書き込む.
                        hash.put( ( mask( code ) ),nextPos,nextFileNo ) ;
                    }
                }
            }
        }
    }
    
    /**
     * 指定キーの要素を取得.
     * @param code 対象のHashコードを設定します.
     * @param key 取得対象のKeyを設定します.
     * @return long 要素に対する項番が返されます.<BR>
     *              [-1L]の場合、対象キーは存在しません.
     * @exception Exception 例外.
     */
    public long get( int code,byte[] key ) throws Exception {
        int code2 = secondHash( key ) ;
        M2KeyChild ch = new M2KeyChild() ;
        // 検索結果、一致する内容が存在する場合.
        if( search( ch,code,code2,key ) == true ) {
            return ( long )( ( ( long )ch.getElementPos() & 0x00000000ffffffffL ) |
                ( ( ( long )ch.getElementFileNo() & 0x00000000ffffffffL ) << 32L ) ) ;
        }
        return -1L ;
    }
    
    /**
     * キー一覧を取得.
     * @param next 対象のNextKeyを設定します.
     * @return M2NextKey 次のNextKeyが返されます.
     * @exception Exception 例外.
     */
    public M2NextKey nextKey( M2NextKey next )
        throws Exception {
        if( next == null ) {
            next = new M2NextKey() ;
        }
        if( next.getCount() >= hash.getSize() ) {
            return null ;
        }
        boolean result = false ;
        int pos = -1 ;
        int fno = -1 ;
        for( ;; ) {
            if( next.getPos() <= -1 && next.getFileNo() <= 0 ) {
                int code = hash.useHash( next.getHashNo() ) ;
                if( code <= -1 ) {
                    return null ;
                }
                next.setHashNo( code ) ;
                long pf = hash.get( code ) ;
                if( pf <= -1L ) {
                    continue ;
                }
                pos = ( int )( pf & 0x00000000ffffffffL ) ;
                fno = ( int )( ( pf & 0xffffffff00000000L ) >> 32L ) ;
                result = true ;
            }
            else {
                pos = next.getPos() ;
                fno = next.getFileNo() ;
                result = true ;
            }
            if( result == true ) {
                byte[] b = sector.readAll( pos,fno ) ;
                M2KeyChild ch = new M2KeyChild( b,pos,fno,-1,-1 ) ;
                next.setPos( ch.getNextPos() ) ;
                next.setFileNo( ch.getNextFileNo() ) ;
                next.setKey( ch.getData() ) ;
                next.addCount() ;
                return next ;
            }
        }
    }
    
    /**
     * キーサイズを取得.
     * @return int キーサイズが返されます.
     * @exception Exception 例外.
     */
    public int size() throws Exception {
        return hash.getSize() ;
    }
    
    /**
     * Hashオブジェクトを取得.
     * @return M2RawHash Hashオブジェクトが返されます.
     */
    public M2RawHash getHash() {
        return hash ;
    }
    
    /**
     * 指定Key情報で検索.
     */
    private boolean search( M2KeyChild ch,int code,int code2,byte[] key ) throws Exception {
        for( ;; ) {
            try {
                ch.clear() ;
                int hcd = mask( code ) ;
                long startPos = hash.get( hcd ) ;
                if( startPos <= -1L ) {
                    return false ;
                }
                int pos = ( int )( startPos & 0x00000000ffffffffL ) ;
                int fno = ( int )( ( startPos & 0xffffffff00000000L ) >> 32L ) ;
                M2SectorData data = new M2SectorData() ;
                for( ;; ) {
                    if( pos <= -1 || fno <= -1 ) {
                        return false ;
                    }
                    // キャッシュが存在しない場合は、直接取得.
                    sector.readOne( data,pos,fno ) ;
                    if( data.getLength() <= -1 ) {
                        return false ;
                    }
                    if( useChild( ch,data,pos,fno,hcd,code,code2,key ) ) {
                        return true ;
                    }
                    pos = ch.getNextPos() ;
                    fno = ch.getNextFileNo() ;
                }
            } catch( Exception e ) {
            }
        }
    }
    
    /**
     * １つのKeyが正しいか確認.
     */
    private boolean useChild( M2KeyChild ch,M2SectorData data,
        int pos,int fno,int hcd,int code,int code2,byte[] key )
        throws Exception {
        ch.create( data,pos,fno ) ;
        if( hcd != mask( ch.getHashCode() ) ) {
            throw new IOException( "不正なHash値を検知しました" ) ;
        }
        // Keyが一致しない場合.
        if( ch.getHashCode() != code ||
            ch.getHashCode2() != code2 ||
            ch.getLength() != key.length ) {
            return false ;
        }
        for( ;; ) {
            if( data.getNextNo() <= -1 || data.getNextFileNo() <= -1 ) {
                break ;
            }
            sector.readOne( data,data.getNextNo(),data.getNextFileNo() ) ;
            ch.addData( data ) ;
        }
        int len = key.length ;
        byte[] b = ch.getData() ;
        for( int i = 0 ; i < len ; i ++ ) {
            if( key[ i ] != b[ i ] ) {
                return false ;
            }
        }
        return true ;
    }
    
    /**
     * Hashコードのマスク値を計算.
     */
    private static final int mask( int code ) {
        return code & M2RawHash.MASK_HASH ;
        //return 1 ;
    }
    
    /**
     * セカンドKeyを生成.
     */
    private static final int secondHash( byte[] key ) {
        int len = key.length ;
        long c = 0x000000009876ABCDL ;
        for( int i = len-1 ; i >= 0 ; i -- ) {
            c = ( c << 5L ) + ( c << 2L ) + c + key[ i ] ;
        }
        return ( int )( c & 0x00000000ffffffffL ) ;
    }
    
}

