package org.maachang.mimdb.core;

import org.maachang.mimdb.MimdbException;
import org.maachang.mimdb.core.impl.EqualsNoList;
import org.maachang.mimdb.core.impl.MimdbUtils;
import org.maachang.mimdb.core.impl.WhereBlock;
import org.maachang.mimdb.core.util.ObjectList;


/**
 * クエリーコンパイル.
 * 
 * @version 2014/01/16
 * @author masahito suzuki
 * @since MasterInMemDB 1.02
 */
public final class QueryCompile {
    private QueryCompile() {}
    
    /**
     * コンパイル処理.
     * @param out コンパイル結果が返却されます.
     * @param info コンパイル元のクエリー情報を設定します.
     * @param manager 対象のテーブルマネージャを設定します.
     * @exception Exception 例外.
     */
    public static final void compile( final QueryCompileInfo out,final QueryInfo info,
        final TableManager manager )
        throws Exception {
        // 前回のコンパイル結果をクリア.
        out.clear() ;
        
        // 設定データチェック.
        checkCompile( info ) ;
        
        // テーブルオブジェクトの取得.
        BaseTable table = manager.get( info.name ) ;
        if( table == null ) {
            throw new MimdbException( "指定テーブル名[" + info.name + "]のテーブルオブジェクトは存在しません" ) ;
        }
        
        // 表示カラムチェック.
        if( !info.allColumn && !info.countFlag ) {
            setViewColumns( out,table,info.columns ) ;
        }
        else if( info.countFlag ) {
            out.columns = null ;
            out.countFlag = true ;
        }
        else if( info.allColumn ) {
            out.columns = null ;
            out.countFlag = false ;
        }
        
        // ソート条件を設定.
        if( info.sortList.size() > 0 ) {
            setSortColumns( out,table,info.sortList,info.descList ) ;
        }
        else {
            out.sort = null ;
        }
        
        // 判別条件を設定.
        if( info.where.size() > 0 ) {
            setWhere( out,table,info.where ) ;
        }
        else {
            out.block = null ;
            out.preparedParams = null ;
        }
        out.name = table.getName() ;
        out.dbId = table.getDbId() ;
    }
    
    /** コンパイルデータチェック. **/
    private static final void checkCompile( QueryInfo info ) throws Exception {
        if( info.name == null || info.name.length() <= 0 ) {
            throw new MimdbException( "テーブル名が設定されていません" ) ;
        }
        if( !info.allColumn && !info.countFlag && info.columns.size() <= 0 ) {
            throw new MimdbException( "表示条件が設定されていません" ) ;
        }
    }
    
    /** 表示カラム名の設定. **/
    private static final void setViewColumns( final QueryCompileInfo out,final BaseTable table,
        final ObjectList<String> columns )
        throws Exception {
        int p ;
        final int len = columns.size() ;
        final int[] columnArray = new int[ len ] ;
        String s ;
        for( int i = 0 ; i < len ; i ++ ) {
            s = columns.get( i ) ;
            if( s == null || s.length() <= 0 ) {
                throw new MimdbException( "カラム名がNULLで設定されています" ) ;
            }
            p = table.getColumnNameByNo( s ) ;
            if( p == -1 ) {
                throw new MimdbException( "テーブル[" + table.getName() + "]に、カラム名[" + s + "]は存在しません" ) ;
            }
            columnArray[ i ] = p ;
        }
        out.columns = new EqualsNoList( columnArray ) ;
        out.countFlag = false ;
    }
    
    /** ソートカラム名の設定. **/
    private static final void setSortColumns( final QueryCompileInfo out,final BaseTable table,
        final ObjectList<String> sortList,final ObjectList<Boolean> descList )
        throws Exception {
        int p ;
        final int len = sortList.size() ;
        final int[] sort = new int[ len ] ;
        final boolean[] desc = new boolean[ len ] ;
        String s ;
        for( int i = 0 ; i < len ; i ++ ) {
            s = sortList.get( i ) ;
            if( s == null || s.length() <= 0 ) {
                throw new MimdbException( "カラム名がNULLで設定されています" ) ;
            }
            p = table.getColumnNameByNo( s ) ;
            if( p == -1 ) {
                throw new MimdbException( "テーブル[" + table.getName() + "]に、カラム名[" + s + "]は存在しません" ) ;
            }
            sort[ i ] = p ;
            desc[ i ] = descList.get( i ) ;
        }
        out.sort = new SortElement() ;
        out.sort.sortNoList = sort ;
        out.sort.desc = desc ;
    }
    
    /** 判別情報の設定. **/
    private static final void setWhere( final QueryCompileInfo out,final BaseTable table,
        final ObjectList<MimdbSearchElement> list )
        throws Exception {
        int p ;
        int par = 0 ;
        int andor = -1 ;// 未定義の場合は-1.
        
        MimdbSearchElement n ;
        WhereBlock block = new WhereBlock() ;
        WhereBlock top = block ;
        ObjectList<MimdbSearchElement> params = new ObjectList<MimdbSearchElement>() ;
        
        int len = list.size() ;
        for( int i = 0 ; i < len ; i ++ ) {
            // 条件設定の場合.
            switch( ( n = list.get( i ) ).getType() ) {
                case MimdbSearchType.PAR_START :    // (
                    par ++ ;
                    block = block.add( new WhereBlock() ) ;
                    break ;
                case MimdbSearchType.PAR_END :      // )
                    if( ( block = block.getTop() ) == null ) {
                        throw new MimdbException( "括弧の構成が不正です" ) ;
                    }
                    par -- ;
                    break ;
                case MimdbSearchType.AND :          // and
                    if( andor != -1 ) {
                        throw new MimdbException( "and条件は不正です" ) ;
                    }
                    andor = 0 ; // andの場合は0.
                    block.add( n ) ;
                    break ;
                case MimdbSearchType.OR :           // or
                    if( andor != -1 ) {
                        throw new MimdbException( "or条件は不正です" ) ;
                    }
                    andor = 1 ; // orの場合は1.
                    block.add( n ) ;
                    break ;
                case MimdbSearchType.OFF_LIMIT :    // Offset or Limit
                    // Preparedモードの場合は、パラメータ設定.
                    if( n.isPrepared() ) {
                        params.add( n ) ;
                    }
                    // Preparedで無い場合は、オフセット、リミット値を直接セット.
                    else if( "offset".equals( n.getColumn() ) ) {
                        out.defOffset = MimdbUtils.convertInt( n.getValue() ) ;
                    }
                    else {
                        out.defLimit = MimdbUtils.convertInt( n.getValue() ) ;
                    }
                    break ;
                    
                default :                           // where判別条件.
                    andor = -1 ;
                    // カラム名をカラム項番に変換.
                    p = n.columnNameByNo( table ) ;
                    // 対象カラムがインデックス条件で無い場合.
                    if( !table.isIndex( p ) ) {
                        throw new MimdbException( "テーブル[" + table.getName() + "]のインデックスでないカラム[" +
                            n.getColumn() + "]は、where条件に定義できません" ) ;
                    }
                    // Preparedモードの場合は、パラメータ設定.
                    if( n.isPrepared() ) {
                        params.add( n ) ;
                    }
                    block.add( n ) ;
                    break ;
            }
        }
        if( par != 0 ) {
            throw new MimdbException( "括弧の整合性が取れていません" ) ;
        }
        else if( andor != -1 ) {
            throw new MimdbException( "And,Orの条件が不正です" ) ;
        }
        out.block = top ;               // Whereブロック.
        if( params.size() > 0 ) {
            len = params.size() ;
            MimdbSearchElement[] pms = new MimdbSearchElement[ len ] ;
            for( int i = 0 ; i < len ; i ++ ) {
                pms[ i ] = params.get( i ) ;
            }
            out.preparedParams = pms ; // Whereパラメータセット.
            out.preparedParamsSize = len ;
        }
        else {
            out.preparedParams = null ;// Whereパラメータなし.
            out.preparedParamsSize = 0 ;
        }
    }
}
