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;

/**
 * 浮動小数点インデックスカラム.
 *
 * @version 2013/10/10
 * @author masahito suzuki
 * @since MasterInMemDB 1.00
 */
public class MFloatIndex extends AbstractNumberIndex<Double> {

    /** ワーク子要素. **/
    private static final class MFloatColumnWorkChild {
        double value ;
        int lineNo ;
        boolean nullFlag ;
        public MFloatColumnWorkChild( double v,int n,boolean nf ) {
            value = v ;
            lineNo = n ;
            nullFlag = nf ;
        }
    }

    /** インデックス子要素. **/
    private static final class MFloatColumnIndexChild implements Comparable<MFloatColumnIndexChild> {
        double value ;
        NumberList lineNo = new NumberList() ;
        public MFloatColumnIndexChild( final double v,final int n ) {
            value = v ;
            lineNo.add( n ) ;
        }
        public int compareTo( final MFloatColumnIndexChild n ) {
            return (int)( value - n.value );
        }
        public int[] getLineNo() {
            final int len = lineNo.size() ;
            final int[] ret = new int[ len ] ;
            for( int i = 0 ; i < len ; i ++ ) {
                ret[ i ] = lineNo.get( i ) ;
            }
            return ret ;
        }
    }

    /**
     * ワークデータ.
     */
    private ObjectList<MFloatColumnWorkChild> work = null ;

    /**
     * ワーク初期サイズ.
     */
    private int workLength = -1 ;

    /**
     * インデックスデータ.
     */
    private double[] indexList = null ;

    /**
     * Hashインデックス利用フラグ.
     */
    private boolean useHashFlag = false ;

    /**
     * コンストラクタ.
     */
    private MFloatIndex() {
    }

    /**
     * コンストラクタ.
     * @param id DbIdを設定します.
     * @param n カラム名を設定します.
     * @param h ハッシュインデックスを利用する場合は[true].
     */
    public MFloatIndex( long id,String n,boolean h ) {
        this( id,n,0,h ) ;
    }

    /**
     * コンストラクタ.
     * @param id DbIdを設定します.
     * @param n カラム名を設定します.
     * @param size 初期サイズを設定.
     * @param h ハッシュインデックスを利用する場合は[true].
     */
    public MFloatIndex( 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( Double value,int lineNo ) {
        if( work == null ) {
            work = new ObjectList<MFloatColumnWorkChild>( workLength ) ;
        }
        if( value == null ) {
            work.add( new MFloatColumnWorkChild( value,-1,true ) ) ;
        }
        else {
            work.add( new MFloatColumnWorkChild( value,lineNo,false ) ) ;
        }
    }

    /**
     * 情報セット.
     * @param value 対象の情報を設定します.
     * @param lineNo 行番号を設定します.
     * @exception Exception 例外.
     */
    public void add( Object value,int lineNo ) throws Exception {
        add( MimdbUtils.convertDouble( 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 ) ;
        MFloatColumnWorkChild k ;
        MFloatColumnIndexChild n ;
        int len = work.size() ;
        int _allLength = len ;

        // 同一Valueの条件を集計.
        NNObjectKeyValue<Double,MFloatColumnIndexChild> map = new NNObjectKeyValue<Double,MFloatColumnIndexChild>( 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 MFloatColumnIndexChild( k.value,k.lineNo ) ) ;
                }
            }
        }
        k = null ;
        n = null ;

        // ワークデータの廃棄.
        work = null ;

        // 実データのインデックス条件が存在する場合.
        len = map.size() ;
        if( len > 0 ) {
            // 同一Value条件の設定.
            MFloatColumnIndexChild[] lst = new MFloatColumnIndexChild[ len ] ;
            NNObjectKeyValue<Double,MFloatColumnIndexChild> it = map.reset() ;
            int cnt = 0 ;
            while( it.hasNext() ) {
                lst[ cnt ++ ] = map.get( it.next() ) ;
            }
            it = null ;
            map = null ;

            // ソート処理.
            Arrays.sort( lst ) ;

            // ソート結果の条件を設定.
            double[] _indexList = new double[ len ] ;
            int[][] _lineList = new int[ len ][] ;
            HashIndex<Double> _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<Double>( _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_FLOAT ;
    }

    /**
     * DBカラムタイプを取得.
     * @return int DBカラムタイプ(java.sql.Types)が返却されます.
     */
    public int getDBType() {
        return java.sql.Types.FLOAT ;
    }

    /** 検索内容の整合性処理. **/
    protected final MimdbSearchElement _checkSearchInfo( final MimdbSearchElement info )
        throws Exception {
        // IN,between検索の場合.
        if( info.getType() == MimdbSearchType.TYPE_IN ||
            info.getType() == MimdbSearchType.TYPE_BETWEEN ) {
            // リストで無い場合は、リストに変換する.
            final Object o = info.getValue() ;
            if( !( o instanceof List ) ) {
                List<Double> lst = new ArrayList<Double>( 1 ) ;
                lst.add( MimdbUtils.convertDouble( o ) ) ;
                info.setValue( lst ) ;
            }
            else {
                final List n = (List)o ;
                final int len = n.size() ;
                final List<Double> lst = new ArrayList<Double>( len ) ;
                for( int i = 0 ; i < len ; i ++ ) {
                    lst.add( MimdbUtils.convertDouble( n.get( i ) ) ) ;
                }
                info.setValue( lst ) ;
            }
            return info ;
        }
        // in以外.
        info.setValue( MimdbUtils.convertDouble( info.getValue() ) ) ;
        return info ;
    }

    /** 根本検索処理. **/
    protected final int _numberBinarySearch( final Object target ) {
        if( indexList == null ) {
            return -1 ;
        }
        // Hashインデックスで検索.
        if( useHashFlag ) {
            return hashIndex.eq( (Double)target ) ;
        }
        return MimdbUtils.searchDouble( indexList,(Double)target ) ;
    }

    /**
     * NGramインデックスかチェック.
     * @return boolean [true]の場合、NGramインデックスです.
     */
    public boolean isNGram() {
        return false ;
    }

    /** 大なり小なり検索処理. **/
    protected int _numberBinarySearchBM( final boolean big,final boolean eq,final Object target ) {
        if( indexList == null ) {
            return -1 ;
        }
        final double t = (Double)target ;
        final int p = MimdbUtils.searchDoubleBS( big,indexList,t ) ;

        if( eq && indexList[ p ] == t ) {
            return p ;
        }
        // 指定位置の条件が同一の場合.
        if( t == indexList[ p ] ) {

            // 同一条件が許可されている場合.
            if( eq ) {
                return p ;
            }
            else {
                // 大なり定義の場合.
                if( big ) {
                    if( indexList.length <= p + 1 ) {
                        return -1 ;
                    }
                    return p + 1 ;
                }
                // 小なり定義の場合.
                if( p <= 0 ) {
                    return -1 ;
                }
                return p - 1 ;
            }
        }
        return p ;
    }

}
