package org.maachang.rimdb.search ;

import org.maachang.rimdb.RimDbException;
import org.maachang.rimdb.Search;
import org.maachang.rimdb.SearchResult;
import org.maachang.rimdb.TableFactory;
import org.maachang.rimdb.index.Index;
import org.maachang.rimdb.index.MaskFlags;
import org.maachang.rimdb.index.Pointer;
import org.maachang.rimdb.index.ResultRadixSort;
import org.maachang.rimdb.index.ResultSort;
import org.maachang.rimdb.index.SearchPointer;
import org.maachang.rimdb.index.SortComparable;
import org.maachang.rimdb.index.position.PositionIndexPointer;
import org.maachang.rimdb.table.Indexs;
import org.maachang.rimdb.table.TableImpl;
import org.maachang.rimdb.util.NAdd;
import org.maachang.rimdb.util.NArray;

/**
 * インデックスを用いた検索処理.
 * 
 * @version 2014/07/11
 * @author  masahito suzuki
 * @since   rimdb-1.00
 */
@SuppressWarnings("unchecked")
public final class SearchImpl implements Search {
    protected SearchImpl() {}
    
    /** テーブル名. **/
    private String name ;
    
    /** Block情報. **/
    private Object[] blockList ;
    
    /** パラメータ設定用ポインター. **/
    private Pointer[] parameterList ;
    
    /** ソート順. **/
    private boolean[] sortDescList ;
    
    /** ソートカラム名. **/
    private String[] sortColumnList ;
    
    /** 条件ANDオンリー. **/
    private boolean andOnly ;
    
    /** 1件検索の場合. **/
    private Pointer oneSearchPointer ;
    
    /** 最終の空間インデックスポインタ. **/
    private PositionIndexPointer lastPositionPointer ;
    
    /** テーブルオブジェクト. **/
    private TableImpl nowTable ;
    
    /**
     * コンストラクタ.
     * @parma name 対象のテーブル名を設定します.
     * @param blockList 対象のBlock情報を設定します.
     * @param parameterList 対象のパラメータリストを設定します.
     * @param sortList 対象のソートリストを設定します.
     * @param andOnly [true]の場合、検索条件はANDのみです.
     * @param PositionIndexPointer 1件の検索条件の場合のみこの条件がセットされます.
     * @param lastPositionPointer 検索条件に定義された最終の空間インデックスがセットされます.
     */
    protected SearchImpl( String name,Object[] blockList,Pointer[] parameterList,
        Object[] sortList,boolean andOnly,Pointer oneSearchPointer,PositionIndexPointer lastPositionPointer ) {
        
        this.name = name ;
        this.blockList = blockList ;
        this.parameterList = parameterList ;
        this.andOnly = andOnly ;
        this.oneSearchPointer = oneSearchPointer ;
        this.lastPositionPointer = lastPositionPointer ;
        
        final int len = sortList.length ;
        if( len == 0 ) {
            this.sortDescList = new boolean[ 0 ] ;
            this.sortColumnList = new String[ 0 ] ;
        }
        else {
            Object[] o ;
            this.sortDescList = new boolean[ len ] ;
            this.sortColumnList = new String[ len ] ;
            for( int i = 0 ; i < len ; i ++ ) {
                o = (Object[])sortList[ i ] ;
                sortDescList[ i ] = (Boolean)o[ 0 ] ;
                if( o.length == 2 ) {
                    sortColumnList[ i ] = (String)o[ 1 ] ;
                }
                else {
                    sortColumnList[ i ] = null ;
                }
            }
        }
    }
    
    /**
     * 情報クリア.
     */
    public final void clear() {
        int len = parameterList.length ;
        for( int i = 0 ; i < len ; i ++ ) {
            parameterList[ i ].reset( true ) ;
        }
        nowTable = null ;
    }
    
    /**
     * テーブル名を取得.
     * @return String テーブル名が返却されます.
     */
    public final String getName() {
        return name ;
    }
    
    /**
     * パラメータ数を取得.
     * @return int パラメータ数が返却されます.
     */
    public final int getParameterSize() {
        return parameterList.length ;
    }
    
    /**
     * 情報設定.
     * @param no 対象の項番を設定します.
     * @param value 対象のパラメータを設定します.
     * @return Search オブジェクトが返却されます.
     */
    public final SearchImpl setParameter( final int no,final Object... value ) {
        if( no < 0 || no >= parameterList.length ) {
            throw new RimDbException( "指定番号は、パラメータの範囲外です:" + no ) ;
        }
        if( value.length == 1 ) {
            parameterList[ no ].parameter( value[ 0 ] ) ;
        }
        else {
            parameterList[ no ].parameter( value ) ;
        }
        return this ;
    }
    
    /**
     * 検索結果件数を取得.
     * この処理を呼び出すと、パラメータ定義の内容はクリアされます.
     * クリアされなく呼び出したい場合は[count(false)]で実行してください.
     * @return int 検索結果の件数が返却されます.
     */
    public final int count() {
        return count( true ) ;
    }
    
    /**
     * 検索結果件数を取得.
     * @param clearMode 処理結果に対して、パラメータ定義などを
     *                  クリアする場合は[true]を設定します.
     * @return int 検索結果の件数が返却されます.
     */
    public final int count( boolean clearMode ) {
        
        final TableImpl table = getTable() ;
        
        // テーブルの件数がゼロ件の場合はゼロを返却.
        if( table.length() == 0 ) {
            
            if( clearMode ) {
                clear() ;
            }
            return 0 ;
        }
        
        // 検索条件が存在しない場合.
        if( blockList.length == 0 ) {
            
            int ret ;
            
            // 検索条件が１件の場合.
            if( oneSearchPointer != null ) {
                
                ret = _searchOneCount( table,oneSearchPointer ) ;
            }
            // 検索条件が存在しない場合.
            else {
                
                ret = table.length() ;
            }
            
            if( clearMode ) {
                clear() ;
            }
            return ret ;
        }
        
        // 検索処理.
        final MaskFlags res = _searchBlock( table,blockList,andOnly ) ;
        if( clearMode ) {
            clear() ;
        }
        if( res == null ) {
            return 0 ;
        }
        return res.size() ;
    }
    
    /**
     * 検索処理.
     * @param offset 対象のオフセット値を設定します.
     * @param limit 対象のリミット値を設定します.
     * @return SearchResult 検索結果が返却されます.
     */
    public final SearchResult execute( final int offset,final int limit ) {
        return execute().setDefine( offset,limit ) ;
    }
    
    /**
     * 検索処理.
     * @return SearchResult 検索結果が返却されます.
     */
    public final SearchResultImpl execute() {
        
        final TableImpl table = getTable() ;
        
        // テーブルの件数がゼロ件の場合はゼロを返却.
        if( table.length() == 0 ) {
            
            clear() ;
            return new SearchResultImpl( table,null,0 ) ;
        }
        
        // 行情報を格納するリスト.
        NAdd list = null ;
        
        // 検索条件が存在しない場合.
        if( blockList.length == 0 ) {
            
            // 検索条件が１件の場合.
            if( oneSearchPointer != null ) {
                
                // １件の件数で検索処理.
                list = _searchOneLine( table,oneSearchPointer ) ;
            }
            // 検索条件が存在しない場合.
            else if( sortColumnList.length == 0 ) {
                
                // ソート条件が存在しない場合.
                clear() ;
                return new SearchResultImpl( table,null,-1 ) ;
            }
            else {
                
                // 全行情報を作成.
                list = new NArray( table.length() ) ;
                int len = table.length() ;
                for( int i = 0 ; i < len ; i ++ ) {
                    list.add( i ) ;
                }
            }
        }
        else {
            
            // 検索処理.
            final MaskFlags res = _searchBlock( table,blockList,andOnly ) ;
            
            // 検索結果がゼロ件の場合.
            if( res == null ) {
                clear() ;
                return new SearchResultImpl( table,null,0 ) ;
            }
            
            list = new NArray( res.size() ) ;
            res.get( list ) ;
        }
        
        // ソート条件が存在しない場合.
        // データ件数が少ない(1件未満)場合.
        if( sortColumnList.length == 0 || list.size() < 2 ) {
            clear() ;
            return new SearchResultImpl( table,list.toArray(),list.size() ) ;
        }
        
        // 行数をソート.
        int[] array = sortRow( list.toArray(),list.size(),table,sortDescList,sortColumnList,lastPositionPointer ) ;
        clear() ;
        return new SearchResultImpl( table,array,list.size() ) ;
    }
    
    /**
     * オブジェクトコピー.
     * @return Search コピーされたオブジェクトが返却されます.
     */
    public final SearchImpl copy() {
        
        // オブジェクトのパラメータをコピー.
        SearchImpl ret = new SearchImpl() ;
        ret.name = name ;
        ret.sortDescList = sortDescList ;
        ret.sortColumnList = sortColumnList ;
        ret.andOnly = andOnly ;
        
        // ブロックリスト、プリペアリストのコピー.
        final int len = blockList.length ;
        
        boolean flg ;
        int type,no,j,lenJ ;
        Object o ;
        Block b ;
        Pointer p ;
        Pointer[] pp ;
        SearchPointer[] np ;
        Object[] blist = new Object[ len ] ;
        Pointer[] pms = new Pointer[ parameterList.length ] ;
        
        ret.blockList = blist ;
        ret.parameterList = pms ;
        
        for( int i = 0 ; i < len ; i ++ ) {
            
            // 対象が番号の場合は、そのままセット.
            if( ( o = blockList[ i ] ) instanceof Integer ) {
                blist[ i ] = o ;
            }
            // 対象が番号以外の場合、ブロック定義をコピー.
            else {
                
                b = (Block)o ;
                switch( ( type = b.getType() ) ) {
                    
                    // 単一ポインタブロック.
                    case Block.AND :
                    case Block.OR :
                        
                        p = (Pointer)b.get() ;
                        
                        // 対象ポインタがパラメータ定義されてない場合.
                        if( ( no = p.getNo() ) != -1 ) {
                            p = p.copy( true ) ;
                            pms[ no ] = p ;
                        }
                        // 対象ポインタがパラメータ定義されている場合.
                        else {
                            p = p.copy( false ) ;
                        }
                        blist[ i ] = new PointerBlock( type,p ) ;
                        break ;
                        
                    // 複数ポインタブロック.
                    case Block.AND_LIST :
                    case Block.OR_LIST :
                        
                        pp = (Pointer[])b.get() ;
                        lenJ = pp.length ;
                        np = new SearchPointer[ lenJ ] ;
                        
                        for( j = 0 ; j < lenJ ; j ++ ) {
                            
                            // 対象ポインタがパラメータ定義されてない場合.
                            if( ( no = ( p = pp[ j ] ).getNo() ) != -1 ) {
                                p = p.copy( true ) ;
                                pms[ no ] = (SearchPointer)p ;
                            }
                            // 対象ポインタがパラメータ定義されている場合.
                            else {
                                p = p.copy( false ) ;
                            }
                            np[ j ] = (SearchPointer)p ;
                        }
                        blist[ i ] = new PointerList( type,np ) ;
                        break ;
                        
                    // 空間インデックス用ブロック.
                    case Block.AND_POSITION :
                    case Block.OR_POSITION :
                        
                        p = (Pointer)b.get() ;
                        
                        // 対象空間インデックスがソート対象のインデックスの場合.
                        if( p == lastPositionPointer ) {
                            flg = true ;
                        }
                        else {
                            flg = false ;
                        }
                        
                        // 対象ポインタがパラメータ定義されてない場合.
                        if( ( no = p.getNo() ) != -1 ) {
                            p = p.copy( true ) ;
                            pms[ no ] = p ;
                        }
                        // 対象ポインタがパラメータ定義されている場合.
                        else {
                            p = p.copy( false ) ;
                        }
                        
                        // ソート対象の場合は、ソート条件として、セット.
                        if( flg ) {
                            ret.lastPositionPointer = (PositionIndexPointer)p ;
                        }
                        blist[ i ] = new PositionBlock( type,p ) ;
                        break ;
                }
            }
        }
        
        // 単体検索条件の場合は、その条件をコピー.
        if( oneSearchPointer != null ) {
            
            if( ( no = oneSearchPointer.getNo() ) != -1 ) {
                ret.oneSearchPointer = oneSearchPointer.copy( true ) ;
                pms[ no ] = ret.oneSearchPointer ;
            }
            else {
                ret.oneSearchPointer = oneSearchPointer.copy( false ) ;
            }
            
            // 対象空間インデックスがソート対象のインデックスの場合.
            if( oneSearchPointer == lastPositionPointer ) {
                
                // ソート対象の場合は、ソート条件として、セット.
                ret.lastPositionPointer = (PositionIndexPointer)ret.oneSearchPointer ;
            }
            
        }
        
        return ret ;
    }
    
    /** テーブルオブジェクトを取得. **/
    protected final TableImpl getTable() {
        if( nowTable == null ) {
            nowTable = (TableImpl)TableFactory.getInstance().get( name ) ;
        }
        return nowTable ;
    }
    
    /** ブロック検索処理. **/
    private static final MaskFlags _searchBlock( final TableImpl table,final Object[] list, final boolean andOnly ) {
        
        int j,lenJ ;
        int type,code ;
        Object n ;
        Block b ;
        Index idx ;
        SearchPointer sp ;
        SearchPointer[] spList ;
        PositionIndexPointer pp ;
        MaskFlags mn = null ;
        MaskFlags mb = null ;
        
        final int rowLen = table.length() ;
        final int len = list.length ;
        final Indexs indexTable = table.getIndexs() ;
        
        code = Block.AND ;
        for( int i = 0 ; i < len ; i ++ ) {
            
            // 連結マージ条件が定義されている場合.
            if( ( n = list[ i ] ) instanceof Integer ) {
                code = (Integer)n ;
            }
            else {
                
                b = (Block)n ;
                switch( type = b.getType() ) {
                    
                    // １件のマージ処理.
                    case Block.AND :
                    case Block.OR :
                        
                        // 1件の処理の場合、区分に意味を持つ.
                        code = type ;
                        
                        // 1件データの場合は、ANDもORも基本処理は同じ.
                        mn = new MaskFlags( rowLen ) ;
                        sp = (SearchPointer)b.get() ;
                        idx = indexTable.getIndex( sp.getColumnName() ) ;
                        
                        // 検索処理.
                        sp.search( idx ) ;
                        
                        // ORマージ.
                        SearchBlock.or( mn,idx,sp ) ;
                        break ;
                        
                    // ANDブロックマージ.
                    case Block.AND_LIST :
                        
                        mn = new MaskFlags( rowLen ) ;
                        spList = (SearchPointer[])b.get() ;
                        lenJ = spList.length ;
                        
                        // 検索処理.
                        for( j = 0 ; j < lenJ ; j ++ ) {
                            spList[ j ].search( indexTable ) ;
                        }
                        
                        // Andマージ.
                        SearchBlock.andBlock( mn,indexTable,spList ) ;
                        break ;
                        
                    // ORブロックマージ.
                    case Block.OR_LIST :
                        
                        mn = new MaskFlags( rowLen ) ;
                        spList = (SearchPointer[])b.get() ;
                        lenJ = spList.length ;
                        
                        // 検索処理.
                        for( j = 0 ; j < lenJ ; j ++ ) {
                            spList[ j ].search( indexTable ) ;
                        }
                        
                        // ORマージ.
                        SearchBlock.orBlock( mn,indexTable,spList ) ;
                        break ;
                    
                    // 空間インデックスANDマージ.
                    case Block.AND_POSITION :
                        
                        // 空間インデックスでは、直接マージ処理を実行.
                        
                        mn = new MaskFlags( rowLen ) ;
                        pp = (PositionIndexPointer)b.get() ;
                        
                        // AND処理の場合は、内部でANDマージ.
                        if( mb != null ) {
                            pp.search( mn,mb,indexTable ) ;
                        }
                        // 前回条件が無い場合は、今回分のみ処理.
                        else {
                            pp.search( mn,indexTable ) ;
                        }
                        
                        // 対象処理がANDのみの場合で、
                        // 今回の検索結果がゼロ件の場合.
                        if( andOnly && mn.getListSize() == 0 ) {
                            return null ;
                        }
                        
                        mb = mn ;
                        mn = null ;
                        
                        break ;
                        
                    // 空間インデックスORマージ.
                    case Block.OR_POSITION :
                        
                        // 空間インデックスでは、直接マージ処理を実行.
                        
                        pp = (PositionIndexPointer)b.get() ;
                        
                        // OR処理の場合は、内部でORマージ.
                        if( mb == null ) {
                            mb = new MaskFlags( rowLen ) ;
                        }
                        pp.search( mb,indexTable ) ;
                        mn = null ;
                        
                        break ;
                        
                }
                
                // マージが必要な場合.
                if( mn != null ) {
                    
                    // 前回条件が存在しない場合.
                    if( mb == null ) {
                        mb = mn ;
                    }
                    // 前回条件とANDマージ.
                    else if( code == Block.AND ) {
                        
                        // 対象処理がANDのみの場合で、
                        // 今回の検索結果がゼロ件の場合.
                        if( andOnly && mn.getListSize() == 0 ) {
                            return null ;
                        }
                        
                        mb.and( mn ) ;
                    }
                    // 前回条件とORマージ.
                    else {
                        mb.or( mn ) ;
                    }
                    
                    mn = null ;
                    
                }
                
                // 連結マージ条件を初期化.
                code = Block.AND ;
                
            }
            
        }
        
        return mb ;
    }
    
    /** １件条件の行リスト検索処理. **/
    private static final NAdd _searchOneLine( final TableImpl table,final Pointer pointer ) {
        
        // 空間インデックス検索の場合.
        if( pointer.getType() == Pointer.TYPE_POSITION ) {
            MaskFlags f = new MaskFlags( table.length() ) ;
            PositionIndexPointer p = (PositionIndexPointer)pointer ;
            
            p.search( f,table.getIndexs() ) ;
            NAdd n = new NArray( f.size() ) ;
            f.get( n ) ;
            
            return n ;
        }
        
        // 通常検索の場合.
        SearchPointer p = (SearchPointer)pointer ;
        Index idx = table.getIndexs().getIndex( p.getColumnName() ) ;
        p.search( idx ) ;
        NAdd n = new NArray( p.getCount() ) ;
        p.get( n,idx ) ;
        
        return n ;
    }
    
    /** １件条件の件数検索処理. **/
    private static final int _searchOneCount( final TableImpl table,final Pointer pointer ) {
        
        // 空間インデックス検索の場合.
        if( pointer.getType() == Pointer.TYPE_POSITION ) {
            MaskFlags f = new MaskFlags( table.length() ) ;
            PositionIndexPointer p = (PositionIndexPointer)pointer ;
            
            p.search( f,table.getIndexs() ) ;
            return f.size() ;
        }
        
        // 通常検索の場合.
        SearchPointer p = (SearchPointer)pointer ;
        Index idx = table.getIndexs().getIndex( p.getColumnName() ) ;
        p.search( idx ) ;
        return p.getCount() ;
    }
    
    /** 行数をソート. **/
    private static final int[] sortRow( final int[] list,final int length,TableImpl table,
        final boolean[] sortDescList,final String[] sortColumnList,final PositionIndexPointer lastPositionPointer ) {
        
        final Indexs indexTable = table.getIndexs() ;
        final int len = sortDescList.length ;
        
        // ソート条件が１件で、通常インデックスの場合は
        // ResultRadixSortでソート処理.
        if( len == 1 && sortColumnList[ 0 ] != null ) {
            
            // １件のソート処理を実施.
            ResultRadixSort.sort(
                indexTable.getIndex( sortColumnList[ 0 ] ).getSortLines(),
                sortDescList[ 0 ],
                list,length,
                table.length() ) ;
            
            return list ;
        }
        
        String name ;
        
        // ソート条件が複数存在する場合は、ResultSortでソート処理.
        final SortComparable[] comparableList = new SortComparable[ len ] ;
        for( int i = 0 ; i < len ; i ++ ) {
            if( ( name = sortColumnList[ i ] ) != null ) {
                comparableList[ i ] = indexTable.getIndex( name ).getSortLines() ;
            }
            else {
                comparableList[ i ] = lastPositionPointer.getSort( indexTable ) ;
            }
        }
        
        ResultSort sortList = new ResultSort( list,length,sortDescList,comparableList ) ;
        sortList.sort() ;
        return list ;
    }
    
}
