package org.maachang.mimdb.core.impl;

import java.lang.reflect.Array;

import org.maachang.mimdb.core.util.ObjectLinkedList;

/**
 * Hashインデックス.
 * バイナリサーチ用のキーを元にHashインデックスを作成します.
 * 
 * @version 2013/10/17
 * @author masahito suzuki
 * @since MasterInMemDB 1.00
 */
@SuppressWarnings("unchecked")
public final class HashIndex<T> {
    
    /** Hash要素. **/
    private static final class HashElement<T> {
        T value ;
        int no ;
    }
    
    /** Hash管理情報. **/
    protected final HashElement<T>[][] hash ;
    
    /** マスク長. **/
    protected final int mask ;
    
    /**
     * コンストラクタ.
     */
    private HashIndex() {
        hash = null ;
        mask = -1 ;
    }
    
    /**
     * コンストラクタ.
     * @param index バイナリサーチ用のインデックスを設定します.
     */
    public HashIndex( final Object index ) {
        final int len = Array.getLength( index ) ;
        final int bitLen = MimdbUtils.bitMask( len ) << 1 ;
        final int msk = bitLen - 1 ;
        
        T n ;
        int hash,no ;
        
        // インデックス情報を集計.
        ObjectLinkedList<Integer> ary ;
        ObjectLinkedList<Integer>[] work = new ObjectLinkedList[ bitLen ] ;
        for( int i = 0 ; i < len ; i ++ ) {
            n = (T)Array.get( index,i ) ;
            hash = n.hashCode() ;
            no = hash & msk ;
            
            if( ( ary = work[ no ] ) == null ) {
                ary = new ObjectLinkedList<Integer>() ;
                work[ no ] = ary ;
            }
            ary.offer( i ) ;
        }
        
        int lenJ,j ;
        
        // 集計したWork情報を、Hash要素に変換.
        HashElement<T> h ;
        HashElement<T>[] ha ;
        HashElement<T>[][] hman = new HashElement[ bitLen ][] ;
        for( int i = 0 ; i < bitLen ; i ++ ) {
            if( ( ary = work[ i ] ) == null ) {
                hman[ i ] = new HashElement[ 0 ] ;
            }
            else {
                lenJ = ary.size() ;
                ha = new HashElement[ lenJ ] ;
                hman[ i ] = ha ;
                for( j = 0 ; j < lenJ ; j ++ ) {
                    no = ary.poll() ;
                    h = new HashElement<T>() ;
                    h.value = (T)Array.get( index,no ) ;
                    h.no = no ;
                    ha[ j ] = h ;
                }
            }
        }
        work = null ;
        this.hash = hman ;
        this.mask = msk ;
    }
    
    /**
     * 指定条件と一致する条件を取得.
     * @param value 一致する条件を設定します.
     * @return int 一致項番が返却されます.
     */
    public int eq( final T value ) {
        final HashElement<T>[] ary = hash[ value.hashCode() & mask ] ;
        final int len = ary.length ;
        for( int i = 0 ; i < len ; i ++ ) {
            if( value.equals( ary[ i ].value ) ) {
                return ary[ i ].no ;
            }
        }
        return -1 ;
    }
}
