package org.maachang.mimdb.core ;

import java.io.OutputStream;

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

/**
 * コンパイル済みクエリー情報.
 * 
 * @version 2014/01/16
 * @author masahito suzuki
 * @since MasterInMemDB 1.02
 */
public final class QueryCompileInfo implements MimdbBase {
    
    /** 更新DbId. **/
    protected long dbId ;
    
    /** 処理対象テーブル名. **/
    protected String name ;
    
    /** プリコンパイル済みID. **/
    protected int preparedId ;
    
    /** 表示カラムNoリスト. **/
    protected EqualsNoList columns ;
    
    /** 行数表示. **/
    protected boolean countFlag ;
    
    /** ソート要素. **/
    protected SortElement sort ;
    
    /** 判別ブロック. **/
    protected WhereBlock block ;
    
    /** Preparedパラメータ. **/
    protected MimdbSearchElement[] preparedParams ;
    
    /** Preparedパラメータ数. **/
    protected int preparedParamsSize ;
    
    /** 基本オフセット定義値. **/
    protected int defOffset = -1 ;
    
    /** 基本リミット値. **/
    protected int defLimit = -1 ;
    
    /**
     * コンストラクタ.
     */
    public QueryCompileInfo() {}
    
    /** デストラクタ. **/
    protected void finalize() throws Exception {
        clear() ;
    }
    
    /**
     * 情報クリア.
     */
    public void clear() {
        dbId = -1L ;
        name = null ;
        preparedId = -1 ;
        columns = null ;
        countFlag = false ;
        sort = null ;
        block = null ;
        preparedParams = null ;
        preparedParamsSize = 0 ;
        defOffset = -1 ;
        defLimit = -1 ;
    }
    
    /**
     * DB更新IDの取得.
     * @return long DB更新IDが返却されます.
     */
    public long getDbId() {
        return dbId ;
    }
    
    /**
     * テーブル名を取得.
     * @return String テーブル名が返却されます.
     */
    public String getName() {
        return name ;
    }
    
    /**
     * プリコンパイル済みIDを設定.
     * @param id 対象のプリコンパイル済みIDを設定します.
     */
    public void setPreparedId( int id ) {
        preparedId = id ;
    }
    
    /**
     * プリコンパイル済みIDを取得.
     * @return int プリコンパイル済みIDが返却されます.
     */
    public int getPreparedId() {
        return preparedId ;
    }
    
    /**
     * SQL文に変換.
     * @param manager 対象のテーブル管理オブジェクトを設定します.
     * @return String SQL文に変換されます.
     * @exception Exception 例外.
     */
    public String getSql( TableManager manager ) throws Exception {
        if( name == null ) {
            throw new MimdbException( "テーブル名が設定されていません" ) ;
        }
        BaseTable table = manager.get( name ) ;
        if( table == null ) {
            throw new MimdbException( "テーブル名[" + name + "]は存在しません" ) ;
        }
        
        StringBuilder buf = new StringBuilder( 1024 ) ;
        
        buf.append( "SELECT " ) ;
        if( countFlag ) {
            buf.append( "COUNT(*) " ) ;
        }
        else {
            int len = columns.size() ;
            for( int i = 0 ; i < len ; i ++ ) {
                if( i != 0 ) {
                    buf.append( ", " ) ;
                }
                buf.append( table.getColumnName( columns.src( i ) ) ).append( " " ) ;
            }
        }
        buf.append( "FROM " ).append( name ).append( " " ) ;
        
        if( block != null ) {
            buf.append( "WHERE " ) ;
            buf.append( block.toString( false ) ) ;
        }
        
        if( sort != null ) {
            buf.append( "ORDER BY " ) ;
            int[] lst = sort.sortNoList ;
            boolean[] desc = sort.desc ;
            int len = lst.length ;
            
            for( int i = 0 ; i < len ; i ++ ) {
                buf.append( table.getColumnName( lst[ i ] ) ).append( " " ) ;
                if( desc[ i ] ) {
                    buf.append( "DESC " ) ;
                }
                else {
                    buf.append( "ASC " ) ;
                }
            }
        }
        
        return buf.append( ";" ).toString() ;
    }
    
    /**
     * バイナリ情報に変換.
     * @param out 対象のOutputStreamを設定します.
     * @exception Exception 例外.
     */
    public void getBinary( OutputStream out )
        throws Exception {
        
        // dbIdを設定.
        ObjectBinary.encode( out,dbId ) ;
        
        // テーブル名をセット.
        ObjectBinary.encode( out,name ) ;
        
        // 表示カラム名をセット.
        if( columns == null ) {
            ObjectBinary.encode( out,false ) ;
        }
        else {
            ObjectBinary.encode( out,true ) ;
            columns.getOutput( out ) ;
        }
        
        // カウント取得フラグをセット.
        ObjectBinary.encode( out,countFlag ) ;
        
        // ソート条件をセット.
        if( sort == null ) {
            ObjectBinary.encode( out,false ) ;
        }
        else {
            ObjectBinary.encode( out,true ) ;
            sort.getOutput( out ) ;
        }
        
        // Whereブロック条件をセット.
        if( block == null ) {
            ObjectBinary.encode( out,false ) ;
        }
        else {
            ObjectBinary.encode( out,true ) ;
            block.getOutput( out ) ;
        }
        
        // デフォルトオフセット、リミット値をセット.
        ObjectBinary.encode( out,defOffset ) ;
        ObjectBinary.encode( out,defLimit ) ;
    }
    
    /**
     * バイナリ情報から、オブジェクトに変換.
     * @param b 対象のバイナリオブジェクトを設定します.
     * @param off 対象のオフセット値を設定します.
     * @param len 対象の長さを設定します.
     * @return int オフセット値が返却されます.
     * @exception Exception 例外.
     */
    public int toObject( byte[] b,int off,int len )
        throws Exception {
        int[] p = new int[]{ off } ;
        clear() ;
        
        // DbIdを取得.
        dbId = (Long)ObjectBinary.decodeBinary( p,b,len ) ;
        
        // テーブル名を取得.
        name = (String)ObjectBinary.decodeBinary( p,b,len ) ;
        
        // 表示カラム名を取得.
        if( (Boolean)ObjectBinary.decodeBinary( p,b,len ) ) {
            int[] src = (int[])ObjectBinary.decodeBinary( p,b,len ) ;
            columns = new EqualsNoList( src ) ;
        }
        else {
            columns = null ;
        }
        
        // カウント取得フラグを取得.
        countFlag = (Boolean)ObjectBinary.decodeBinary( p,b,len ) ;
        
        // ソート条件を取得.
        if( (Boolean)ObjectBinary.decodeBinary( p,b,len ) ) {
            int[] sortListNo = (int[])ObjectBinary.decodeBinary( p,b,len ) ;
            boolean[] desc = (boolean[])ObjectBinary.decodeBinary( p,b,len ) ;
            sort = new SortElement() ;
            sort.sortNoList = sortListNo ;
            sort.desc = desc ;
        }
        else {
            sort = null ;
        }
        
        // where条件を取得.
        if( (Boolean)ObjectBinary.decodeBinary( p,b,len ) ) {
            block = new WhereBlock() ;
            ObjectList<MimdbSearchElement> list = new ObjectList<MimdbSearchElement>() ;
            block.toObject( p,list,b,len ) ;
            
            // Preparedパラメータ条件をセット.
            preparedParamsSize = list.size() ;
            preparedParams = new MimdbSearchElement[ preparedParamsSize ] ;
            for( int i = 0 ; i < preparedParamsSize ; i ++ ) {
                preparedParams[ i ] = list.get( i ) ;
            }
        }
        else {
            block = null ;
            preparedParamsSize = 0 ;
        }
        
        // デフォルトオフセット、リミット値をセット.
        defOffset = (Integer)ObjectBinary.decodeBinary( p,b,len ) ;
        defLimit = (Integer)ObjectBinary.decodeBinary( p,b,len ) ;
        
        // プリコンパイル済みIDをセット.
        preparedId = -1 ;
        
        return p[ 0 ] ;
    }
}
