package org.maachang.rimdb.index.numberFloat ;

import org.maachang.rimdb.RimDbException;
import org.maachang.rimdb.index.Index;
import org.maachang.rimdb.util.NAdd;

/**
 * Float(double)インデックス.
 * 
 * @version 2014/07/02
 * @author  masahito suzuki
 * @since   rimdb-1.00
 */
public final class NumberFloatIndex extends Index<Double> {
    
    /**
     * 完全一致検索.
     * @param a 対象の配列を設定します.
     * @param key 対象のキーを設定します.
     * @return int 項番が返却されます.
     *             [-1]が返却された場合、情報は存在しません.
     */
    public static final int search( final double[] a,final double key ) {
        int low = 0 ;
        int high = a.length -1 ;
        int mid,cmp ;
        double midVal ;
        long midBits,keyBits ;
        keyBits = Double.doubleToLongBits( key ) ;
        while (low <= high) {
            if (( midVal = a[ mid = (low + high) >>> 1 ] ) < key) {
                cmp = -1;   // Neither val is NaN, thisVal is smaller
            } else if (midVal > key) {
                cmp = 1;    // Neither val is NaN, thisVal is larger
            } else {
                midBits = Double.doubleToLongBits( midVal ) ;
                cmp = (midBits == keyBits ?  0 : // Values are equal
                       (midBits < keyBits ? -1 : // (-0.0, 0.0) or (!NaN, NaN)
                        1));                     // (0.0, -0.0) or (NaN, !NaN)
            }
            if (cmp < 0) {
                low = mid + 1;
            }
            else if (cmp > 0) {
                high = mid - 1;
            }
            else {
                return mid; // key found
            }
        }
        return -1 ;
    }
    
    /**
     * 大なり小なり検索.
     * @param big [true]の場合、第三引数が第二引数の検知条件より大きい条件(>=)を対象とします.
     * @param a 対象の配列を設定します.
     * @param key 対象のキーを設定します.
     * @return int 項番が返却されます.
     *             [-1]が返却された場合、情報は存在しません.
     */
    public static final int searchBS( final boolean big,final double[] a,final double key ) {
        int low = 0 ;
        int high = a.length -1 ;
        int mid,cmp ;
        double midVal ;
        long midBits,keyBits ;
        keyBits = Double.doubleToLongBits( key ) ;
        mid = 0 ;
        while (low <= high) {
            if (( midVal = a[ mid = (low + high) >>> 1 ] ) < key) {
                cmp = -1;   // Neither val is NaN, thisVal is smaller
            } else if (midVal > key) {
                cmp = 1;    // Neither val is NaN, thisVal is larger
            } else {
                midBits = Double.doubleToLongBits( midVal ) ;
                cmp = (midBits == keyBits ?  0 : // Values are equal
                       (midBits < keyBits ? -1 : // (-0.0, 0.0) or (!NaN, NaN)
                        1));                     // (0.0, -0.0) or (NaN, !NaN)
            }
            if (cmp < 0) {
                low = mid + 1;
            }
            else if (cmp > 0) {
                high = mid - 1;
            }
            else {
                return mid; // key found
            }
        }
        if( big ) {
            if( a[mid] > key ) {
                return mid ;
            }
            else if( a.length <= mid + 1 ) {
                return a.length-1 ;
            }
            return mid + 1 ;
        }
        if( a[mid] > key ) {
            if( mid <= 0 ) {
                return 0 ;
            }
            return mid -1 ;
        }
        return mid ;
    }
    
    /** index情報. **/
    protected final double[] index ;
    
    /**
     * コンストラクタ.
     * @param index 対象のインデックスを設定します.
     * @param line 対象の行情報を設定します.
     * @param max 全体長を設定します.
     */
    public NumberFloatIndex( double[] index,int[][] line,int max ) {
        super( line,max ) ;
        this.index = index ;
    }
    
    /**
     * インデックス情報を取得.
     * @return int[] インデックス情報が返却されます.
     */
    public double[] getIndex() {
        return index ;
    }
    
    /**
     * 一致検索.
     * @param key 対象のキーを設定します.
     * @return int インデックス番号が返却されます.
     */
    public final int eq( final Double key ) {
        return search( index,key ) ;
    }
    
    /**
     * 大なり検索.
     * @param eq 一致を許可する場合には[true]をセットします.
     * @param key 対象のキーを設定します.
     * @return int インデックス番号が返却されます.
     */
    public final int big( final boolean eq,final Double key ) {
        if( index[ index.length-1 ] < key ) {
            return -1 ;
        }
        else if( eq ) {
            return searchBS( true,index,key ) ;
        }
        else {
            int n = searchBS( true,index,key ) ;
            if( index[ n ] == key && (++n) >= maxLine ) {
                return -1 ;
            }
            return n ;
        }
    }
    
    /**
     * 小なり検索.
     * @param eq 一致を許可する場合には[true]をセットします.
     * @param key 対象のキーを設定します.
     * @return int インデックス番号が返却されます.
     */
    public final int small( final boolean eq,final Double key ) {
        if( index[ 0 ] > key ) {
            return -1;
        }
        else if( eq ) {
            return searchBS( false,index,key ) ;
        }
        else {
            int n = searchBS( false,index,key ) ;
            if( index[ n ] == key && (--n) < 0 ) {
                return -1 ;
            }
            return n ;
        }
    }
    
    /**
     * between検索.
     * @param out 格納行情報を設定します.
     * @param start 開始値を設定します.
     * @param end 終了値を設定します.
     * @return boolean [true]の場合、範囲取得に成功しました.
     */
    public final boolean between( final int[] out,Double start,Double end ) {
        if( start > end ) {
            Double n = start ;
            start = end ;
            end = n ;
        }
        out[ 0 ] = searchBS( true,index,start ) ;
        out[ 1 ] = searchBS( false,index,end ) ;
        
        return ( index[ out[ 0 ] ] >= start || index[ out[ 1 ] ] >= end ) &&
            ( index[ out[ 0 ] ] <= start || index[ out[ 1 ] ] <= end ) ;
    }
    
    /**
     * 部分一致検索.
     * @param result 一致行情報を格納するListオブジェクトを設定します.
     * @param key 対象のLike条件を設定します.
     */
    public final void like( final NAdd result,String key ) {
        throw new RimDbException( "FloatではLikeはサポートされていません" ) ;
    }
    
}

