package org.maachang.rimdb.search ;

import org.maachang.rimdb.RimDbException;
import org.maachang.rimdb.index.Pointer;
import org.maachang.rimdb.index.position.PositionIndexPointer;
import org.maachang.rimdb.util.OList;

/**
 * 検索条件の作成処理.
 * 
 * @version 2014/07/11
 * @author  masahito suzuki
 * @since   rimdb-1.00
 */
public final class CreateSearch {
    
    /** パラメータなしのオブジェクト. **/
    private static final class NoParams {}
    private static final NoParams NO_PARAMS = new NoParams() ;
    
    /** 検索条件. **/
    private static final int AND = Block.AND ;
    private static final int OR = Block.OR ;
    private static final int BEGIN = 100 ;
    private static final int END = 101 ;
    
    /** テーブル名. **/
    private String name ;
    
    /** 検索条件. **/
    private OList<Object> list ;
    
    /** 検索条件パラメータ. **/
    private OList<Object> params ;
    
    /** ソート条件. **/
    private OList<Object> sort ;
    
    /**
     * コンストラクタ.
     */
    public CreateSearch() {
        
    }
    
    /**
     * コンストラクタ.
     * @param name 対象のテーブル名を設定します.
     */
    public CreateSearch( String name ) {
        create( name ) ;
    }
    
    /**
     * 検索条件を新規作成.
     * @param name 対象のテーブル名を設定します.
     */
    public void create( final String name ) {
        this.name = name ;
    }
    
    /**
     * 情報クリア.
     */
    public final void clear() {
        if( list != null ) {
            list.clear() ;
        }
        list = null ;
        if( params != null ) {
            params.clear() ;
        }
        params = null ;
        if( sort != null ) {
            sort.clear() ;
        }
        sort = null ;
    }
    
    /**
     * テーブル名を取得.
     * @return String テーブル名が返却されます.
     */
    public final String getName() {
        return name ;
    }
    
    /**
     * ANDセット.
     */
    public final void and() {
        _set( AND ) ;
    }
    
    /**
     * ORセット.
     */
    public final void or() {
        _set( OR ) ;
    }
    
    /**
     * 区切り開始セット.
     */
    public final void begin() {
        _set( BEGIN ) ;
    }
    
    /**
     * 区切り終了セット.
     */
    public final void end() {
        _set( END ) ;
    }
    
    /** コードセット. **/
    private final void _set( final int n ) {
        if( list == null ) {
            list = new OList<Object>() ;
        }
        list.add( n ) ;
    }
    
    /**
     * 検索ポインタを設定.
     * @param pointer 対象のポインタを設定します.
     */
    public final void pointer( final Pointer pointer ) {
        _pointer( pointer,NO_PARAMS ) ;
    }
    
    /**
     * 検索ポインタを設定.
     * @param pointer 対象のポインタを設定します.
     * @param params 対象のパラメータ情報を設定します.
     */
    public final void pointer( final Pointer pointer,final Object... params ) {
        if( params.length > 1 ) {
            _pointer( pointer,params ) ;
        }
        else {
            _pointer( pointer,params[ 0 ] ) ;
        }
    }
    
    /** 検索ポインタを設定. **/
    protected final void _pointer( final Pointer pointer,final Object param ) {
        if( pointer == null ) {
            throw new RimDbException( "[" + name + "]検索ポインタが設定されていません" ) ;
        }
        if( list == null ) {
            list = new OList<Object>() ;
        }
        list.add( pointer ) ;
        
        if( params == null ) {
            params = new OList<Object>() ;
        }
        params.add( param ) ;
    }
    
    /**
     * 検索空間インデックス条件を設定.
     * @param p 空間インデックスポインタを設定します.
     */
    public final void position( final Pointer p ) {
        position( p,NO_PARAMS ) ;
    }
    
    /**
     * 検索空間インデックス条件を設定.
     * @param p 空間インデックスポインタを設定します.
     * @param x 空間インデックス検索X軸を設定します.
     * @param y 空間インデックス検索Y軸を設定します.
     * @param half 空間インデックス検索半径を設定します.
     */
    public final void position( final Pointer p,final int x,final int y,final int half ) {
        position( p,new int[]{ x,y,half } ) ;
    }
    
    /**
     * 検索空間インデックス条件を設定.
     * @param p 空間インデックスポインタを設定します.
     * @param o 対象のパラメータを設定します.
     */
    protected final void position( final Pointer p,final Object o ) {
        if( p == null ) {
            throw new RimDbException( "[" + name + "]検索ポインタが設定されていません" ) ;
        }
        if( list == null ) {
            list = new OList<Object>() ;
        }
        list.add( p ) ;
        
        if( params == null ) {
            params = new OList<Object>() ;
        }
        params.add( o ) ;
    }
    
    /**
     * ソート条件をセット.
     * @param boolean [true]の場合、降順で検索します.
     * @param name ソート条件のカラム名を設定します.
     */
    public final void sort( final boolean desc,final String name ) {
        if( sort == null ) {
            sort = new OList<Object>() ;
        }
        sort.add( new Object[]{ desc,name } ) ;
    }
    
    /**
     * 空間インデックスのソート条件をセット.
     * @param boolean [true]の場合、降順で検索します.
     */
    public final void sort( final boolean desc ) {
        if( sort == null ) {
            sort = new OList<Object>() ;
        }
        sort.add( new Object[]{ desc } ) ;
    }
    
    /**
     * 検索オブジェクトを作成.
     * @return Search 検索オブジェクトが返却されます.
     */
    public final SearchImpl create() {
        SearchImpl ret ;
        final Object[] sort = sortList() ;
        
        if( list == null ) {
            
            // 空の検索結果をセット.
            ret = new SearchImpl( name,new Object[0],new Pointer[0],
                sort,true,null,null ) ;
        }
        else {
            
            int len = list.size() ;
            
            // 検索条件が１件の場合.
            if( len == 1 ) {
                
                // １件の情報が検索条件の場合.
                if( list.get( 0 ) instanceof Pointer ) {
                    
                    Pointer p = (Pointer)list.get( 0 ) ;
                    Pointer[] ps ;
                    Object pms = params.get( 0 ) ;
                    if( pms == NO_PARAMS ) {
                        p.setNo( 0 ) ;
                        ps = new Pointer[]{ p } ;
                    }
                    else {
                        p.parameter( pms ) ;
                        ps = new Pointer[0] ;
                    }
                    
                    // 1件の検索処理条件を設定.
                    if( p.getType() == Pointer.TYPE_POSITION ) {
                        
                        // 空間インデックスが存在する場合.
                        ret = new SearchImpl( name,new Object[ 0 ],ps,sort,true,
                            p,(PositionIndexPointer)p ) ;
                    }
                    else {
                        
                        // 空間インデックスが存在しない場合.
                        ret = new SearchImpl( name,new Object[ 0 ],ps,sort,true,
                            p,null ) ;
                    }
                }
                // １件の情報が検索条件以外の場合.
                else {
                    ret = new SearchImpl( name,new Object[0],new Pointer[0],
                        sort,true,null,null ) ;
                }
            }
            // 検索条件が複数の場合.
            else {
                
                final OList<Pointer> prepared = new OList<Pointer>() ;
                final OList<Object> out = new OList<Object>() ;
                
                final int[] cnt = new int[]{ 0,0,-1 } ;
                // cnt[ 0 ] : [in]パラメータカウンタ.
                // cnt[ 1 ] : [out]or定義数.
                // cnt[ 2 ] : [out]空間インデックスポインタ最終位置.
                
                int res = toBlock( out,0,len,prepared,cnt ) ;
                if( len != res ) {
                    throw new RimDbException( "[" + name + "]不明なEOFを検出しました" ) ;
                }
                len = prepared.size() ;
                Pointer[] ps = new Pointer[ len ] ;
                System.arraycopy( prepared.toArray(),0,ps,0,len ) ;
                
                // 空間インデックスが存在する場合.
                if( cnt[ 2 ] != -1 ) {
                    
                    // ソート対象となる空間インデックスポインタに対して、番号１をセット.
                    PositionIndexPointer pip = (PositionIndexPointer)list.get( cnt[ 2 ] ) ;
                    ret = new SearchImpl( name,out.getArray(),ps,sort,
                        cnt[1] == 0,null,pip ) ;
                }
                // 空間インデックスが存在しない場合.
                else {
                    
                    ret = new SearchImpl( name,out.getArray(),ps,sort,
                        cnt[1] == 0,null,null ) ;
                }
                
            }
        }
        
        // 情報を削除して返却.
        clear() ;
        return ret ;
    }
    
    /** ブロック単位での処理. **/
    private final int toBlock( final OList<Object> out,int i,final int len,OList<Pointer> prepared,int[] cnt ) {
        OList<Pointer> and = new OList<Pointer>() ;
        OList<Pointer> or = new OList<Pointer>() ;
        
        Object n ;
        Pointer p ;
        int code ;
        
        // 次の条件が存在して、次の条件がInteger定義でOR定義の場合は、OR属性ではじめる.
        if( i + 1 < len &&
            ( n = list.get( i + 1 ) ) instanceof Integer &&
            (Integer)n == OR ) {
            code = OR ;
        }
        else {
            code = AND ;
        }
        
        for( ; i < len ; i ++ ) {
            
            // 対象が数値の場合は、判別条件および、括弧系.
            if( ( n = list.get( i ) ) instanceof Integer ) {
                switch( (Integer)n  ) {
                    case AND :
                        code = AND ;
                        break ;
                        
                    case OR :
                        code = OR ;
                        break ;
                        
                    case BEGIN :
                        i = toBlock( out,i+1,len,prepared,cnt ) ;
                        if( i != 0 ) {
                            if( code == OR ) {
                                ++ cnt[ 1 ] ;
                            }
                            out.add( code ) ;
                        }
                        break ;
                    case END :
                        createBlock( out,and,or ) ;
                        return i ;
                }
            }
            else {
                
                p = (Pointer)n ;
                
                // パラメータが存在しない場合は、PrepareStatement.
                if( ( n = params.get( cnt[0] ) ) == NO_PARAMS ) {
                    p.setNo( cnt[0] ) ;
                    prepared.add( p ) ;
                }
                // パラメータが存在する場合は、固定.
                else {
                    p.parameter( n ) ;
                }
                // パラメータカウンタUP.
                ++ cnt[0] ;
                
                // AND定義.
                if( code == AND ) {
                    and.add( p ) ;
                }
                // OR定義.
                else {
                    
                    // ORカウンタを１インクリメント.
                    cnt[ 1 ] ++ ;
                    or.add( p ) ;
                }
                
                // 空間インデックスポインタの場合は項番を保存.
                if( p.getType() == Pointer.TYPE_POSITION ) {
                    cnt[ 2 ] = i ;
                }
                
                // 条件をANDに戻す.
                code = AND ;
            }
            
        }
        
        // 最後の条件をセット.
        createBlock( out,and,or ) ;
        return i ;
    }
    
    /** Block作成. **/
    private static final void createBlock( final OList<Object> out,final OList<Pointer> and,final OList<Pointer> or ) {
        int i,len ;
        Pointer p ;
        if( and.size() > 0 ) {
            len = and.size() ;
            if( len == 1 ) {
                if( ( p = and.get( 0 ) ).getType() == Pointer.TYPE_POSITION ) {
                    out.add( new PositionBlock( Block.AND_POSITION,p ) ) ;
                }
                else {
                    out.add( new PointerBlock( Block.AND,p ) ) ;
                }
            }
            else {
                for( i = 0 ; i < len ; i ++ ) {
                    if( ( p = and.get( i ) ).getType() == Pointer.TYPE_POSITION ) {
                        out.add( new PositionBlock( Block.AND_POSITION,p ) ) ;
                        and.remove( i ) ;
                        i -- ;
                        len -- ;
                    }
                }
                if( len > 0 ) {
                    if( len == 1 ) {
                        out.add( new PointerBlock( Block.AND,and.get( 0 ) ) ) ;
                    }
                    else {
                        out.add( new PointerList( Block.AND_LIST,and ) ) ;
                    }
                }
            }
        }
        if( or.size() > 0 ) {
            len = or.size() ;
            if( len == 1 ) {
                if( ( p = or.get( 0 ) ).getType() == Pointer.TYPE_POSITION ) {
                    out.add( new PositionBlock( Block.OR_POSITION,p ) ) ;
                }
                else {
                    out.add( new PointerBlock( Block.OR,p ) ) ;
                }
            }
            else {
                for( i = 0 ; i < len ; i ++ ) {
                    if( ( p = or.get( i ) ).getType() == Pointer.TYPE_POSITION ) {
                        out.add( new PositionBlock( Block.OR_POSITION,p ) ) ;
                        or.remove( i ) ;
                        i -- ;
                        len -- ;
                    }
                }
                if( len > 0 ) {
                    if( len == 1 ) {
                        out.add( new PointerBlock( Block.OR,or.get( 0 ) ) ) ;
                    }
                    else {
                        out.add( new PointerList( Block.OR_LIST,or ) ) ;
                    }
                }
            }
        }
    }
    
    /** ソート条件を作成. **/
    private final Object[] sortList() {
        if( sort == null || sort.size() == 0 ) {
            return new Object[ 0 ] ;
        }
        
        // 情報内に、空間インデックスソートが存在するかチェック.
        Object[] ret = sort.getArray() ;
        boolean positionFlag = false ;
        int len = ret.length ;
        for( int i = 0 ; i < len ; i ++ ) {
            if( ((Object[])ret[ i ]).length == 1 ) {
                positionFlag = true ;
                break ;
            }
        }
        
        // 空間インデックスソートが存在する場合、
        // 検索条件に空間インデックスが含まれているかチェック.
        if( positionFlag ) {
            
            // 空間インデックスの検索が含まれていない場合は、
            // 空間インデックスソートは行えない.
            len = list.size() ;
            for( int i = 0 ; i < len ; i ++ ) {
                if( list.get( i ) instanceof PositionIndexPointer ) {
                    return ret ;
                }
            }
            throw new RimDbException( "[" + name +
                "]空間インデックスソートに対して、検索条件に空間インデックス検索が" +
                "存在しない場合は、処理できません" ) ;
        }
        return ret ;
    }
}

