package org.maachang.mimdb.core.impl;

import java.util.List;

import org.maachang.mimdb.MimdbException;
import org.maachang.mimdb.core.MimdbIndex;
import org.maachang.mimdb.core.MimdbMiddleSearch;
import org.maachang.mimdb.core.MimdbSearchElement;
import org.maachang.mimdb.core.MimdbSearchType;
import org.maachang.mimdb.core.util.ObjectKeySet;

/**
 * 数値用インデックスカラム.
 * 
 * @version 2013/10/10
 * @author masahito suzuki
 * @since MasterInMemDB 1.00
 */
@SuppressWarnings("unchecked")
public abstract class AbstractNumberIndex<T> implements MimdbIndex {
    
    /**
     * データデフォルト値.
     */
    protected static final int DEF_SIZE = 64 ;
    
    /**
     * 空条件.
     */
    protected static final int[] NOT = new int[]{ -1 } ;
    
    /**
     * カラム名.
     */
    protected String name = null ;
    
    /**
     * DbId.
     */
    protected long dbId = -1L ;
    
    /**
     * 行番号.
     */
    protected int[][] lineList = null ;
    
    /**
     * Hashインデックス.
     */
    protected HashIndex<T> hashIndex = null ;
    
    
    /**
     * NULL行番号.
     */
    protected int[] nullLineList = null ;
    
    /**
     * ソート順管理.
     */
    protected int[] sortNoList = null ;
    
    /**
     * 全データ長.
     */
    protected int allLength = -1 ;
    
    /**
     * インデックスフラグ.
     */
    protected boolean indexFlag = false ;
    
    /**
     * クリア.
     */
    public void clear() {
        lineList = null ;
        hashIndex = null ;
        nullLineList = null ;
        sortNoList = null ;
        allLength = -1 ;
        indexFlag = false ;
    }
    
    /**
     * DB更新IDを取得.
     * この情報が、結果データと一致しない場合は、その結果データは古くなっています.
     * @return int DB更新IDが返却されます.
     */
    public long getDbId() {
        return dbId ;
    }
    
    /** 検索位置に対する有効行数の合計値を取得. **/
    protected final int _calcUseLineSize( final int[] posList ) {
        if( posList == null ) {
            return 0 ;
        }
        int[] n ;
        int ret = 0 ;
        final int len = posList.length ;
        for( int i = 0 ; i < len ; i ++ ) {
            
            // データが何らか存在する場合.
            if( posList[ i ] != -1 ) {
                // null.
                if( posList[ i ] == -9 ) {
                    n = nullLineList ;
                }
                // インデックスデータ.
                else {
                    n = lineList[ posList[ i ] ] ;
                }
                ret += n.length ;
            }
        }
        return ret ;
    }
    
    /** 新規検索データの作成. **/
    protected final MimdbMiddleSearch _createMiddleSearch( final MimdbMiddleSearch and,final MimdbMiddleSearch or ) {
        
        // AND指定の場合.
        if( and != null ) {
            
            // AND属性で生成(オブジェクトを使いまわす).
            final FlagsMiddleSearch ret = ( FlagsMiddleSearch )and ;
            ret.create( ret.dbId,ret.noList ) ;
            
            // ORも同時に指定されている場合.
            if( or != null ) {
                
                // OR属性に変換.
                ret.or( or ) ;
            }
            
            return ret ;
        }
        // OR指定されている場合.
        else if( or != null ) {
            
            // AND属性の場合.
            if( or.isAnd() ) {
                
                // OR属性に変更.
                or.or( or ) ;
            }
            // or条件をそのまま利用.
            return or ;
        }
        // 何も条件が指定されていない場合.
        else {
            
            // 通常条件で作成.
            return new FlagsMiddleSearch( dbId,allLength ) ;
        }
    }
    
    /** 検索に対する有効行番号群をセット. **/
    protected final void _addSearchLineNo( final MimdbMiddleSearch ret,final int pos ) {
        // データなし.
        if( pos == -1 ) {
            return ;
        }
        // null.
        else if( pos == -9 ) {
            ret.addArray( nullLineList ) ;
        }
        // 通常情報.
        else {
            ret.addArray( lineList[ pos ] ) ;
        }
    }
    
    /** 検索に対する有効番号群をクリア. **/
    protected final void _addSearchLineOffNo( final MimdbMiddleSearch ret,final int pos ) {
        // データなし.
        if( pos == -1 ) {
            return ;
        }
        // null.
        else if( pos == -9 ) {
            ret.offArray( nullLineList ) ;
        }
        // 通常情報.
        else {
            ret.offArray( lineList[ pos ] ) ;
        }
    }
    
    /** 基本検索. **/
    /** 検索結果はインデックス位置が返却される **/
    protected final int _searchNormal( final MimdbSearchElement target ) throws Exception {
        if( !indexFlag ) {
            throw new MimdbException( "インデックス化されていません" ) ;
        }
        if( lineList == null ) {
            return -1 ;
        }
        // 検索条件に応じた内容を取得.
        switch( target.getType() ) {
            case MimdbSearchType.TYPE_EQ :
            case MimdbSearchType.TYPE_NEQ :
                return _numberBinarySearch( target.getValue() ) ;
            case MimdbSearchType.TYPE_LBIG :
                return _numberBinarySearchBM( true,false,target.getValue() ) ;
            case MimdbSearchType.TYPE_LEQBIG :
                return _numberBinarySearchBM( true,true,target.getValue() ) ;
            case MimdbSearchType.TYPE_RBIG :
                return _numberBinarySearchBM( false,false,target.getValue() ) ;
            case MimdbSearchType.TYPE_REQBIG :
                return _numberBinarySearchBM( false,true,target.getValue() ) ;
        }
        throw new MimdbException( "数字検索に対して、不当な検索条件[" + target.getType() + "]です" ) ;
    }
    
    /** In検索. **/
    /** 検索結果はインデックス位置のリストが返却される. **/
    protected final int[] _searchIn( final MimdbSearchElement target ) throws Exception {
        Object o ;
        final List searchInfo = (List)target.getValue() ;
        int len = searchInfo.size() ;
        if( len <= 0 ) {
            return NOT ;
        }
        final int[] ret = new int[ len ] ;
        final ObjectKeySet<Object> dblChk = new ObjectKeySet<Object>( len << 1 ) ;
        for( int i = 0 ; i < len ; i ++ ) {
            // 同一条件チェック.
            o = searchInfo.get( i ) ;
            if( dblChk.contains( o ) ) {
                ret[ i ] = -1 ;
                continue ;
            }
            dblChk.add( o ) ;
            if( o == null ) {
                ret[ i ] = -9 ; // null.
            }
            else {
                ret[ i ] = _numberBinarySearch( o ) ;
            }
        }
        return ret ;
    }
    
    /** BETWEEN検索. **/
    /** 検索結果は、指定条件の[開始位置、終了位置]の条件が返却される. **/
    protected final int[] _searchBitween( final MimdbSearchElement target ) throws Exception {
        Object o ;
        final List searchInfo = (List)target.getValue() ;
        final int len = searchInfo.size() ;
        if( len <= 0 ) {
            return NOT ;
        }
        final int[] ret = new int[ len ] ;
        for( int i = 0 ; i < len ; i ++ ) {
            // 条件指定なし.
            if( ( o = searchInfo.get( i ) ) == null ) {
                ret[ i ] = -1 ;
            }
            // 偶数番(指定値より大きい条件).
            else if( (i & 1) == 0 ) {
                if( i + 1 < len && searchInfo.get( i+1 ) != null ) {
                    
                    // 開始位置、終了位置の数値が逆転している場合.
                    if( ((Comparable)o).compareTo( searchInfo.get( i+1 ) ) > 0 ) {
                        
                        // 開始、終了位置の条件を逆転させる.
                        ret[ i ] = _numberBinarySearchBM( true,true,searchInfo.get( i+1 ) ) ;
                        ret[ i+1 ] = _numberBinarySearchBM( false,true,o ) ;
                        i ++ ;
                        
                        continue ;
                    }
                }
                
                // 開始位置を取得.
                ret[ i ] = _numberBinarySearchBM( true,true,o ) ;
            }
            // 奇数番(指定値より小さい条件).
            else {
                
                // 終了位置を取得.
                ret[ i ] = _numberBinarySearchBM( false,true,o ) ;
            }
        }
        return ret ;
    }
    
    /** NULL検索. **/
    /** 検索結果はNULLの行番号群が返却される. **/
    protected final int[] _searchNull( final MimdbSearchElement target ) throws Exception {
        // 検索条件に応じた内容を取得.
        switch( target.getType() ) {
            case MimdbSearchType.TYPE_EQ :
            case MimdbSearchType.TYPE_NEQ :
            if( nullLineList == null ) {
                return null ;
            }
            return nullLineList ;
        }
        throw new MimdbException( "数字NULL検索に対して、不当な検索条件[" + target.getType() + "]です" ) ;
    }
    
    /** 検索. **/
    protected final MimdbMiddleSearch _coreSearch( final MimdbSearchElement info,final MimdbMiddleSearch and,final MimdbMiddleSearch or )
        throws Exception {
        if( info == null ) {
            throw new MimdbException( "検索条件が設定されていません" ) ;
        }
        if( !indexFlag ) {
            throw new MimdbException( "インデックス化されていません" ) ;
        }
        
        // 検索タイプを取得.
        final int type = info.getType() ;
        
        // 検索条件がNULLの場合.
        if( info.getValue() == null ) {
            
            // null条件で検索.
            final int[] nullPos = _searchNull( info ) ;
            
            // NULL一致.
            if( type == MimdbSearchType.TYPE_EQ ) {
                if( nullPos == null ) {
                    return null ;
                }
                // 検索条件の入れ物を作成.
                MimdbMiddleSearch ret = _createMiddleSearch( and,or ) ;
                
                // 行情報の追加.
                ret.addArray( nullPos ) ;
                return ret ;
            }
            // NULL不一致.
            else {
                return neqSearch( -1,and,or ) ;
            }
        }
        // 検索条件がInの場合.
        else if( type == MimdbSearchType.TYPE_IN ) {
            
            // inで検索.
            final int[] inPos = _searchIn( info ) ;
            
            // 有効な行数が存在しない場合は処理しない.
            if( _calcUseLineSize( inPos ) <= 0 ) {
                return null ;
            }
            
            // 検索条件の入れ物を作成.
            MimdbMiddleSearch ret = _createMiddleSearch( and,or ) ;
            
            // 検索結果追加.
            final int len = inPos.length ;
            for( int i = 0 ; i < len ; i ++ ) {
                _addSearchLineNo( ret,inPos[ i ] ) ;
            }
            return ret ;
        }
        // 検索条件がBitweenの場合.
        else if( type == MimdbSearchType.TYPE_BETWEEN ) {
            
            // 検索結果追加.
            return betweenSearch( _searchBitween( info ),and,or ) ;
        }
        
        // 通常検索の場合.
        final int pos = _searchNormal( info ) ;
        
        // notEquals以外で検索結果がゼロ件の場合.
        if( type != MimdbSearchType.TYPE_NEQ && pos == -1 ) {
            return null ;
        }
        
        // 検索条件に応じた処理.
        switch( type ) {
            case MimdbSearchType.TYPE_EQ :
                return eqSearch( pos,and,or ) ;
                
            case MimdbSearchType.TYPE_NEQ :
                return neqSearch( pos,and,or ) ;
                
            case MimdbSearchType.TYPE_LBIG :
            case MimdbSearchType.TYPE_LEQBIG :
                return lBigSearch( pos,and,or ) ;
                
            case MimdbSearchType.TYPE_RBIG :
            case MimdbSearchType.TYPE_REQBIG :
                return rBigSearch( pos,and,or ) ;
                
        }
        
        return null ;
    }
    
    /** 完全一致検索. **/
    protected final MimdbMiddleSearch eqSearch( final int pos,final MimdbMiddleSearch and,MimdbMiddleSearch or )
        throws Exception {
        
        // 検索条件の入れ物を作成.
        MimdbMiddleSearch ret = _createMiddleSearch( and,or ) ;
        
        // 指定位置の結果条件をセット.
        _addSearchLineNo( ret,pos ) ;
        return ret ;
    }
    
    /** 完全不一致検索. **/
    protected final MimdbMiddleSearch neqSearch( final int pos,MimdbMiddleSearch and,final MimdbMiddleSearch or )
        throws Exception {
        
        // 検索条件の入れ物を作成.
        final MimdbMiddleSearch ret = _createMiddleSearch( and,or ) ;
        
        // posが-1の場合は全部を対象とする.
        if( pos == -1 ) {
            ret.all() ;
        }
        // 条件が一致する場合は、指定条件以外のものを全てONにセット.
        else {
            ret.all() ;
            // 指定のものをOFFセット.
            _addSearchLineOffNo( ret,pos ) ;
        }
        return ret ;
    }
    
    /** 指定データより大きい(以上)の検索 **/
    protected final MimdbMiddleSearch lBigSearch( final int pos,MimdbMiddleSearch and,final MimdbMiddleSearch or )
        throws Exception {
        
        // 検索条件の入れ物を作成.
        final MimdbMiddleSearch ret = _createMiddleSearch( and,or ) ;
        
        int len = lineList.length ;
        for( int i = pos ; i < len ; i ++ ) {
            _addSearchLineNo( ret,i ) ;
        }
        return ret ;
    }
    
    /** 指定データより小さい(以下)の検索 **/
    protected final MimdbMiddleSearch rBigSearch( final int pos,MimdbMiddleSearch and,final MimdbMiddleSearch or )
        throws Exception {
        
        // 検索条件の入れ物を作成.
        final MimdbMiddleSearch ret = _createMiddleSearch( and,or ) ;
        
        for( int i = pos ; i >= 0 ; i -- ) {
            _addSearchLineNo( ret,i ) ;
        }
        return ret ;
    }
    
    /** Bitweenの範囲を検索. **/
    protected final MimdbMiddleSearch betweenSearch( final int[] list,final MimdbMiddleSearch and,final MimdbMiddleSearch or )
        throws Exception {
        int len ;
        
        // 検索条件の入れ物を作成.
        final MimdbMiddleSearch ret = _createMiddleSearch( and,or ) ;
        
        // Bitween条件が全件設定の場合.
        if( ( len = list.length ) == 1 && list[ 0 ] == -1 ) {
            ret.all() ;
            return ret ;
        }
        
        // 指定範囲条件をセット.
        int lineLen = lineList.length ;
        int s,e,j ;
        s = -1 ;
        e = -1 ;
        for( int i = 0 ; i < len ; i ++ ) {
            // 開始位置.
            if( ( i & 1 ) == 0 ) {
                s = list[ i ] ;
                
                // 終了位置が定義されていない場合.
                if( len == i + 1 ) {
                    
                    // 開始位置が定義されていない場合は、開始位置は0.
                    if( s == -1 ) {
                        s = 0 ;
                    }
                    
                    // 終了位置が定義されていない場合は、終了位置は、インデックスの最後.
                    e = lineLen ;
                    
                    // 行条件を追加.
                    for( j = s ; j <= e ; j ++ ) {
                        _addSearchLineNo( ret,j ) ;
                    }
                    
                }
            }
            // 終了位置
            else {
                e = list[ i ] ;
                
                // 開始位置が定義されていない場合は、開始位置は0.
                if( s == -1 ) {
                    s = 0 ;
                }
                // 終了位置が定義されていない場合は、終了位置は、インデックスの最後.
                if( e == -1 ) {
                    e = lineLen ;
                }
                
                // 行条件を追加.
                for( j = s ; j <= e ; j ++ ) {
                    _addSearchLineNo( ret,j ) ;
                }
            }
        }
        
        return ret ;
    }
    
    /**
     * インデックス化されているかチェック.
     * @return boolean [true]の場合、インデックス化されています.
     */
    public boolean isIndex() {
        return indexFlag ;
    }
    
    /**
     * 検索処理.
     * @param info 検索条件を設定します.
     * @return MimdbMiddleSearch 検索結果が返却されます.
     * @exception Exception 例外.
     */
    public MimdbMiddleSearch search( final MimdbSearchElement info )
        throws Exception {
        return _coreSearch( _checkSearchInfo( info ),null,null ) ;
    }
    
    /**
     * AND検索処理.
     * @param info 検索条件を設定します.
     * @param and アンド検索条件元を設定します.
     * @return MimdbMiddleSearch 検索結果が返却されます.
     * @exception Exception 例外.
     */
    public MimdbMiddleSearch and( final MimdbSearchElement info,final MimdbMiddleSearch and )
        throws Exception {
        return _coreSearch( _checkSearchInfo( info ),and,null ) ;
    }
    
    /**
     * OR検索処理.
     * @param info 検索条件を設定します.
     * @param or オア検索条件元を設定します.
     * @return MimdbMiddleSearch 検索結果が返却されます.
     * @exception Exception 例外.
     */
    public MimdbMiddleSearch or( final MimdbSearchElement info,final MimdbMiddleSearch or )
        throws Exception {
        return _coreSearch( _checkSearchInfo( info ),null,or ) ;
    }
    
    /**
     * ソート順条件を取得.
     * @return int[] ソート順の条件が返却されます.
     */
    public int[] getSortNoList() {
        return sortNoList ;
    }
    
    /**
     * カラム名を取得.
     * @return String カラム名が返却されます.
     */
    public String getName() {
        return name ;
    }
    
    /**
     * インデックスの生成.
     * @exception Exception 例外.
     */
    public abstract void createIndex() throws Exception ;
    
    /**
     * インデックス数を取得.
     * @return int インデックス数が返却されます.
     */
    public abstract int getIndexSize() ;
    
    /**
     * カラムタイプを取得.
     * @return int カラムタイプが返却されます.
     */
    public abstract int getType() ;
    
    /**
     * DBカラムタイプを取得.
     * @return int DBカラムタイプ(java.sql.Types)が返却されます.
     */
    public abstract int getDBType() ;
    
    /** equals検索処理. **/
    protected abstract int _numberBinarySearch( Object target ) ;
    
    /** 大なり小なり検索処理. **/
    protected abstract int _numberBinarySearchBM( boolean big,boolean eq,Object target ) ;
    
    /** 検索内容の整合性処理. **/
    protected abstract MimdbSearchElement _checkSearchInfo( MimdbSearchElement info ) throws Exception ;
}
