package org.maachang.mimdb.core.impl;

import java.util.Arrays;

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.NumberList;

/**
 * Bool用インデックスカラム.
 * 
 * @version 2013/10/13
 * @author masahito suzuki
 * @since MasterInMemDB 1.00
 */
public class MBoolIndex implements MimdbIndex {
    
    /** ワーク領域. **/
    private NumberList trueWork = null ;
    private NumberList falseWork = null ;
    private NumberList nullWork = null ;
    private int workLength = -1 ;
    
    /** データ領域. **/
    private int[] trueIndex = null ;
    private int[] falseIndex = null ;
    private int[] nullIndex = null ;
    
    /** ソート順. **/
    private int[] sortNoList = null ;
    
    /**
     * カラム名.
     */
    protected String name = null ;
    
    /**
     * DbId.
     */
    protected long dbId = -1L ;
    
    /**
     * 全データ長.
     */
    protected int allLength = -1 ;
    
    /**
     * インデックスフラグ.
     */
    protected boolean indexFlag = false ;
    
    /**
     * コンストラクタ.
     */
    private MBoolIndex() {
    }
    
    /**
     * コンストラクタ.
     * @param id DbIdを設定します.
     * @param n カラム名を設定します.
     */
    public MBoolIndex( int id,String n ) {
        this( id,n,0 ) ;
    }
    
    /**
     * コンストラクタ.
     * @param id DbIdを設定します.
     * @param n カラム名を設定します.
     * @param size 初期サイズを設定.
     */
    public MBoolIndex( long id,String n,int size ) {
        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 ;
    }
    
    /**
     * クリア.
     */
    public void clear() {
        trueWork = null ;
        falseWork = null ;
        nullWork = null ;
        trueIndex = null ;
        falseIndex = null ;
        nullIndex = null ;
        sortNoList = null ;
        allLength = -1 ;
        indexFlag = false ;
    }
    
    /**
     * DB更新IDを取得.
     * この情報が、結果データと一致しない場合は、その結果データは古くなっています.
     * @return int DB更新IDが返却されます.
     */
    public long getDbId() {
        return dbId ;
    }
    
    /**
     * 情報セット.
     * @param value 対象の情報を設定します.
     * @param lineNo 行番号を設定します.
     */
    public void add( Boolean value,int lineNo ) {
        if( value == null ) {
            if( nullWork == null ) {
                nullWork = new NumberList( workLength ) ;
            }
            nullWork.add( lineNo ) ;
        }
        if( value == true ) {
            if( trueWork == null ) {
                trueWork = new NumberList( workLength ) ;
            }
            trueWork.add( lineNo ) ;
        }
        else {
            if( falseWork == null ) {
                falseWork = new NumberList( workLength ) ;
            }
            falseWork.add( lineNo ) ;
        }
        if( allLength == -1 ) {
            allLength = 0 ;
        }
        allLength ++ ;
    }
    
    /**
     * 情報セット.
     * @param value 対象の情報を設定します.
     * @param lineNo 行番号を設定します.
     * @exception Exception 例外.
     */
    public void add( Object value,int lineNo )
        throws Exception {
        add( MimdbUtils.convertBool( value ),lineNo ) ;
    }
    
    /**
     * インデックスの生成.
     * @exception Exception 例外.
     */
    public void createIndex() throws Exception {
        if( allLength == -1 ) {
            clear() ;
            return ;
        }
        
        trueIndex = null ;
        falseIndex = null ;
        nullIndex = null ;
        sortNoList = null ;
        indexFlag = false ;
        
        int[] _sortNoList = new int[ allLength ] ;
        Arrays.fill( _sortNoList,-1 ) ;
        
        int[] nullIx = null ;
        if( nullWork != null ) {
            int len = nullWork.size() ;
            nullIx = new int[ len ] ;
            for( int i = 0 ; i < len ; i ++ ) {
                nullIx[ i ] = nullWork.get( i ) ;
            }
        }
        
        int[] trueIx = null ;
        if( trueWork != null ) {
            int len = trueWork.size() ;
            trueIx = new int[ len ] ;
            for( int i = 0 ; i < len ; i ++ ) {
                trueIx[ i ] = trueWork.get( i ) ;
                _sortNoList[ trueIx[ i ] ] = 1 ;
            }
        }
        
        int[] falseIx = null ;
        if( falseWork != null ) {
            int len = falseWork.size() ;
            falseIx = new int[ len ] ;
            for( int i = 0 ; i < len ; i ++ ) {
                falseIx[ i ] = falseWork.get( i ) ;
                _sortNoList[ falseIx[ i ] ] = 0 ;
            }
        }
        
        trueIndex = trueIx ;
        falseIndex = falseIx ;
        nullIndex = nullIx ;
        sortNoList = _sortNoList ;
        indexFlag = true ;
    }
    
    /** 検索内容の整合性処理. **/
    protected MimdbSearchElement _checkSearchInfo( MimdbSearchElement info ) throws Exception {
        info.setValue( MimdbUtils.convertBool( info.getValue() ) ) ;
        return info ;
    }
    
    /** 新規検索データの作成. **/
    protected final MimdbMiddleSearch _createMiddleSearch( MimdbMiddleSearch and,MimdbMiddleSearch or ) {
        
        // AND指定の場合.
        if( and != null ) {
            
            // AND属性で生成(オブジェクトを使いまわす).
            FlagsMiddleSearch ret = ( FlagsMiddleSearch )and ;
            ret.create( ret.dbId,ret.noList ) ;
            
            // ORも同時に指定されている場合.
            if( or != null ) {
                ret.or( or ) ;
            }
            
            return ret ;
        }
        // OR指定されている場合.
        else if( or != null ) {
            // AND属性の場合.
            if( or.isAnd() ) {
                // OR属性に変更.
                or.or( or ) ;
            }
            return or ;
        }
        // 何も条件が指定されていない場合.
        else {
            // 通常条件で作成.
            return new FlagsMiddleSearch( dbId,allLength ) ;
        }
    }
    
    /** [ON]検索結果追加処理. **/
    protected final MimdbMiddleSearch _addResultOnSearch( MimdbMiddleSearch and,MimdbMiddleSearch or,int[] list )
        throws Exception {
        
        // 検索条件を生成.
        MimdbMiddleSearch ret = _createMiddleSearch( and,or ) ;
        
        // 行情報の追加.
        _addLineNo( ret,true,list ) ;
        
        return ret ;
    }
    
    /** [OFF]検索結果追加処理. **/
    protected final MimdbMiddleSearch _addResultOffSearch( MimdbMiddleSearch and,MimdbMiddleSearch or,int[] list )
        throws Exception {
        
        // 検索条件を生成.
        MimdbMiddleSearch ret = _createMiddleSearch( and,or ) ;
        
        // 全フラグを立てる.
        ret.all() ;
        
        // 行情報の追加.
        _addLineNo( ret,false,list ) ;
        
        return ret ;
    }
    
    /** 行番号群をセット. **/
    protected final void _addLineNo( MimdbMiddleSearch ret,boolean onFlg,int[] n ) {
        
        // 指定番号をONに設定.
        if( onFlg ) {
            
            ret.addArray( n ) ;
        }
        // 指定番号をOFFに設定.
        else {
            
            ret.offArray( n ) ;
        }
    }
    
    /** 検索. **/
    protected final MimdbMiddleSearch _coreSearch( MimdbSearchElement info,MimdbMiddleSearch and,MimdbMiddleSearch or )
        throws Exception {
        if( info == null ) {
            throw new MimdbException( "検索条件が設定されていません" ) ;
        }
        if( !indexFlag ) {
            throw new MimdbException( "インデックス化されていません" ) ;
        }
        // 検索条件がNULLの場合.
        if( info.getValue() == null ) {
            // NULL一致.
            if( info.getType() == MimdbSearchType.TYPE_EQ ) {
                if( nullIndex == null ) {
                    return null ;
                }
                // NULL一致条件を返却.
                return _addResultOnSearch( and,or,nullIndex ) ;
            }
            // NULL不一致.
            if( trueIndex == null && falseIndex == null ) {
                return null ;
            }
            // NULL不一致条件を返却.
            return _addResultOffSearch( and,or,nullIndex ) ;
        }
        
        // 検索内容を整形.
        boolean value = MimdbUtils.convertBool( info.getValue() ) ;
        
        // 検索条件に応じた処理.
        switch( info.getType() ) {
            case MimdbSearchType.TYPE_EQ : // =
                // true条件.
                if( value ) {
                    if( trueIndex != null ) {
                        // true一致の条件を返却.
                        return _addResultOnSearch( and,or,trueIndex ) ;
                    }
                }
                // false条件.
                else {
                    if( falseIndex != null ) {
                        // false一致の条件を返却.
                        return _addResultOnSearch( and,or,falseIndex ) ;
                    }
                }
                return null ;
            case MimdbSearchType.TYPE_NEQ : // !=
                // true条件.
                if( value ) {
                    // true条件が存在する場合.
                    if( trueIndex != null ) {
                        // true以外の条件を返却.
                        return _addResultOffSearch( and,or,trueIndex ) ;
                    }
                }
                // false条件.
                else {
                    // false条件が存在する場合.
                    if( falseIndex != null ) {
                        // false以外の条件を返却.
                        return _addResultOffSearch( and,or,falseIndex ) ;
                    }
                }
                // 条件が存在しない場合は全条件を対象とする.
                MimdbMiddleSearch ret = _createMiddleSearch( and,or ) ;
                ret.all() ;
                return ret ;
        }
        throw new MimdbException( "数字検索に対して、不当な検索条件[" + info.getType() + "]です" ) ;
    }
    
    /**
     * インデックス化されているかチェック.
     * @return boolean [true]の場合、インデックス化されています.
     */
    public boolean isIndex() {
        return indexFlag ;
    }
    
    /**
     * 検索処理.
     * @param info 検索条件を設定します.
     * @return MimdbMiddleSearch 検索結果が返却されます.
     * @exception Exception 例外.
     */
    public MimdbMiddleSearch search( MimdbSearchElement info )
        throws Exception {
        return _coreSearch( _checkSearchInfo( info ),null,null ) ;
    }
    
    /**
     * AND検索処理.
     * @param info 検索条件を設定します.
     * @param and アンド検索条件元を設定します.
     * @return MimdbMiddleSearch 検索結果が返却されます.
     * @exception Exception 例外.
     */
    public MimdbMiddleSearch and( MimdbSearchElement info,MimdbMiddleSearch and )
        throws Exception {
        return _coreSearch( _checkSearchInfo( info ),and,null ) ;
    }
    
    /**
     * OR検索処理.
     * @param info 検索条件を設定します.
     * @param or オア検索条件元を設定します.
     * @return MimdbMiddleSearch 検索結果が返却されます.
     * @exception Exception 例外.
     */
    public MimdbMiddleSearch or( MimdbSearchElement info,MimdbMiddleSearch or )
        throws Exception {
        return _coreSearch( _checkSearchInfo( info ),null,or ) ;
    }
    
    /**
     * ソート順条件を取得.
     * @return int[] ソート順の条件が返却されます.
     */
    public int[] getSortNoList() {
        return sortNoList ;
    }
    
    /**
     * インデックス数を取得.
     * @return int インデックス数が返却されます.
     */
    public int getIndexSize() {
        return 2 ;
    }
    
    /**
     * カラム名を取得.
     * @return String カラム名が返却されます.
     */
    public String getName() {
        return name ;
    }
    
    /**
     * カラムタイプを取得.
     * @return int カラムタイプが返却されます.
     */
    public int getType() {
        return COLUMN_BOOL ;
    }
    
    /**
     * DBカラムタイプを取得.
     * @return int DBカラムタイプ(java.sql.Types)が返却されます.
     */
    public int getDBType() {
        return java.sql.Types.BOOLEAN ;
    }
    
    /**
     * NGramインデックスかチェック.
     * @return boolean [true]の場合、NGramインデックスです.
     */
    public boolean isNGram() {
        return false ;
    }
}
