package org.maachang.mimdb.core.impl;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.maachang.mimdb.MimdbException;
import org.maachang.mimdb.core.MimdbSearchElement;
import org.maachang.mimdb.core.MimdbSearchType;
import org.maachang.mimdb.core.util.NNObjectKeyValue;
import org.maachang.mimdb.core.util.NumberList;
import org.maachang.mimdb.core.util.ObjectList;

/**
 * 数字(64bit)インデックスカラム.
 * 
 * @version 2013/10/10
 * @author masahito suzuki
 * @since MasterInMemDB 1.00
 */
public class MLongIndex extends AbstractNumberIndex<Long> {
    
    /** ワーク子要素. **/
    private static final class MLongColumnWorkChild {
        long value ;
        int lineNo ;
        boolean nullFlag ;
        public MLongColumnWorkChild( long v,int n,boolean nf ) {
            value = v ;
            lineNo = n ;
            nullFlag = nf ;
        }
    }
    
    /** インデックス子要素. **/
    private static final class MLongColumnIndexChild implements Comparable<MLongColumnIndexChild> {
        long value ;
        NumberList lineNo = new NumberList() ;
        public MLongColumnIndexChild( long v,int n ) {
            value = v ;
            lineNo.add( n ) ;
        }
        public int compareTo( MLongColumnIndexChild n ) {
            return (int)( value - n.value );
        }
        public int[] getLineNo() {
            int len = lineNo.size() ;
            int[] ret = new int[ len ] ;
            for( int i = 0 ; i < len ; i ++ ) {
                ret[ i ] = lineNo.get( i ) ;
            }
            return ret ;
        }
    }
    
    /**
     * ワークデータ.
     */
    protected ObjectList<MLongColumnWorkChild> work = null ;
    
    /**
     * ワーク初期サイズ.
     */
    protected int workLength = -1 ;
    
    /**
     * インデックスデータ.
     */
    protected long[] indexList = null ;
    
    /**
     * Hashインデックス利用フラグ.
     */
    protected boolean useHashFlag = false ;
    
    /**
     * コンストラクタ.
     */
    protected MLongIndex() {
    }
    
    /**
     * コンストラクタ.
     * @param id DbIdを設定します.
     * @param n カラム名を設定します.
     * @param h ハッシュインデックスを利用する場合は[true].
     */
    public MLongIndex( long id,String n,boolean h ) {
        this( id,n,0,h ) ;
    }
    
    /**
     * コンストラクタ.
     * @param id DbIdを設定します.
     * @param n カラム名を設定します.
     * @param size 初期サイズを設定.
     * @param h ハッシュインデックスを利用する場合は[true].
     */
    public MLongIndex( long id,String n,int size,boolean h ) {
        if( n == null || ( n = n.trim().toLowerCase() ).length() <= 0 ) {
            throw new MimdbException( "カラム名が設定されていません" ) ;
        }
        if( size <= 0 ) {
            size = AbstractNumberIndex.DEF_SIZE ;
        }
        dbId = id ;
        workLength = size ;
        name = n ;
        useHashFlag = h ;
    }
    
    /**
     * クリア.
     */
    public void clear() {
        super.clear() ;
        work = null ;
        indexList = null ;
    }
    
    /**
     * 情報セット.
     * @param value 対象の情報を設定します.
     * @param lineNo 行番号を設定します.
     */
    public void add( Long value,int lineNo ) {
        if( work == null ) {
            work = new ObjectList<MLongColumnWorkChild>( workLength ) ;
        }
        if( value == null ) {
            work.add( new MLongColumnWorkChild( value,-1,true ) ) ;
        }
        else {
            work.add( new MLongColumnWorkChild( value,lineNo,false ) ) ;
        }
    }
    
    /**
     * 情報セット.
     * @param value 対象の情報を設定します.
     * @param lineNo 行番号を設定します.
     * @exception Exception 例外.
     */
    public void add( Object value,int lineNo ) throws Exception {
        add( MimdbUtils.convertLong( value ),lineNo ) ;
    }
    
    /**
     * インデックスの生成.
     * @exception Exception 例外.
     */
    public void createIndex() throws Exception {
        if( work == null ) {
            clear() ;
            return ;
        }
        
        // 前のインデックスをクリア.
        indexList = null ;
        lineList = null ;
        nullLineList = null ;
        sortNoList = null ;
        allLength = -1 ;
        indexFlag = false ;
        
        // インデックス条件の作成.
        NumberList nullList = new NumberList( 32 ) ;
        MLongColumnWorkChild k ;
        MLongColumnIndexChild n ;
        int len = work.size() ;
        int _allLength = len ;
        
        // 同一Valueの条件を集計.
        NNObjectKeyValue<Long,MLongColumnIndexChild> map = new NNObjectKeyValue<Long,MLongColumnIndexChild>( len * 2 ) ;
        for( int i = 0 ; i < len ; i ++ ) {
            k = work.get( i ) ;
            // NULLデータの場合.
            if( k.nullFlag ) {
                nullList.add( k.lineNo ) ;
            }
            // 通常データの場合.
            else {
                if( ( n = map.get( k.value ) ) != null ) {
                    n.lineNo.add( k.lineNo ) ;
                }
                else {
                    map.put( k.value,new MLongColumnIndexChild( k.value,k.lineNo ) ) ;
                }
            }
        }
        k = null ;
        n = null ;
        
        // ワークデータの廃棄.
        work = null ;
        
        // 実データのインデックス条件が存在する場合.
        len = map.size() ;
        if( len > 0 ) {
            // 同一Value条件の設定.
            MLongColumnIndexChild[] lst = new MLongColumnIndexChild[ len ] ;
            NNObjectKeyValue<Long,MLongColumnIndexChild> it = map.reset() ;
            int cnt = 0 ;
            while( it.hasNext() ) {
                lst[ cnt ++ ] = map.get( it.next() ) ;
            }
            it = null ;
            map = null ;
            
            // ソート処理.
            Arrays.sort( lst ) ;
            
            // ソート結果の条件を設定.
            long[] _indexList = new long[ len ] ;
            int[][] _lineList = new int[ len ][] ;
            HashIndex<Long> _hashIndex = null ;
            
            // ソート項番.
            int[] _sortNoList = new int[ _allLength ] ;
            Arrays.fill( _sortNoList,-1 ) ;
            
            int j,lenJ ;
            int[] nlst ;
            
            for( int i = 0 ; i < len ; i ++ ) {
                n = lst[ i ] ;
                _indexList[ i ] = n.value ;
                nlst = n.getLineNo() ; ;
                _lineList[ i ] = nlst ;
                
                lenJ = nlst.length ;
                for( j = 0 ; j < lenJ ; j ++ ) {
                    _sortNoList[ nlst[ j ] ] = i ;
                }
            }
            lst = null ;
            
            // HashIndexを作成する場合.
            if( useHashFlag ) {
                _hashIndex = new HashIndex<Long>( _indexList ) ;
            }
            
            // インデックス条件のセット.
            indexList = _indexList ;
            lineList = _lineList ;
            hashIndex = _hashIndex ;
            sortNoList = _sortNoList ;
        }
        
        // NULLリストのインデックス条件が存在する場合.
        len = nullList.size() ;
        if( len > 0 ) {
            int[] _nullLineList = new int[ len ] ;
            for( int i = 0 ; i < len ; i ++ ) {
                _nullLineList[ i ] = nullList.get( i ) ;
            }
            nullList = null ;
            
            // インデックス条件のセット.
            nullLineList = _nullLineList ;
        }
        
        // インデックス情報をセット.
        allLength = _allLength ;
        indexFlag = true ;
    }
    
    /**
     * インデックス数を取得.
     * @return int インデックス数が返却されます.
     */
    public int getIndexSize() {
        return indexList.length ;
    }
    
    /**
     * カラムタイプを取得.
     * @return int カラムタイプが返却されます.
     */
    public int getType() {
        return COLUMN_LONG ;
    }
    
    /**
     * DBカラムタイプを取得.
     * @return int DBカラムタイプ(java.sql.Types)が返却されます.
     */
    public int getDBType() {
        return java.sql.Types.BIGINT ;
    }
    
    /**
     * NGramインデックスかチェック.
     * @return boolean [true]の場合、NGramインデックスです.
     */
    public boolean isNGram() {
        return false ;
    }
    
    /** 検索内容の整合性処理. **/
    protected MimdbSearchElement _checkSearchInfo( MimdbSearchElement info ) throws Exception {
        // IN,between検索の場合.
        if( info.getType() == MimdbSearchType.TYPE_IN ||
            info.getType() == MimdbSearchType.TYPE_BETWEEN ) {
            // リストで無い場合は、リストに変換する.
            Object o = info.getValue() ;
            if( !( o instanceof List ) ) {
                List<Long> lst = new ArrayList<Long>( 1 ) ;
                lst.add( MimdbUtils.convertLong( o ) ) ;
                info.setValue( lst ) ;
            }
            else {
                List n = (List)o ;
                int len = n.size() ;
                List<Long> lst = new ArrayList<Long>( len ) ;
                for( int i = 0 ; i < len ; i ++ ) {
                    lst.add( MimdbUtils.convertLong( n.get( i ) ) ) ;
                }
                info.setValue( lst ) ;
            }
            return info ;
        }
        // in以外.
        info.setValue( MimdbUtils.convertLong( info.getValue() ) ) ;
        return info ;
    }
    
    /** 根本検索処理. **/
    protected final int _numberBinarySearch( Object target ) {
        if( indexList == null ) {
            return -1 ;
        }
        // Hashインデックスで検索.
        if( useHashFlag ) {
            return hashIndex.eq( (Long)target ) ;
        }
        return MimdbUtils.searchLong( indexList,(Long)target ) ;
    }
    
    /** 大なり小なり検索処理. **/
    protected int _numberBinarySearchBM( boolean big,boolean eq,Object target ) {
        if( indexList == null ) {
            return -1 ;
        }
        long t = (Long)target ;
        int p = MimdbUtils.searchLongBS( big,indexList,t ) ;
        // 同一条件が許可されている場合.
        if( eq && indexList[ p ] == t ) {
            return p ;
        }
        // 大なり定義の場合.
        if( big ) {
            if( indexList.length <= p + 1 ) {
                return -1 ;
            }
            return p + 1 ;
        }
        // 小なり定義の場合.
        if( p <= 0 ) {
            return -1 ;
        }
        return p - 1 ;
    }
}
