package org.maachang.mimdb.core ;

import java.lang.ref.SoftReference;

import org.maachang.mimdb.MimdbException;

/**
 * Remote用プリコンパイル済みステートメント.
 *
 * @version 2014/01/16
 * @author masahito suzuki
 * @since MasterInMemDB 1.02
 */
final class RemotePreparedStatement implements MimdbPreparedStatement {

    /** 元のSQL文. **/
    protected String sql = null ;

    /** リモートコネクションオブジェクト. **/
    protected RemoteConnection connection = null ;

    /** コンパイルオブジェクト. **/
    protected QueryCompileInfo compile = null ;

    /** 実行用Preparedパラメータ. **/
    protected Object[] executionParams = null ;

    /** 表示オフセット. **/
    protected int viewOffset = -1 ;

    /** 表示リミット. **/
    protected int viewLimit = -1 ;

    /** フェッチサイズ. **/
    protected int fetchSize = RemoteConnection.DEF_FETCH_SIZE ;

    /** データ受け取り条件. **/
    protected int[] out = new int[ 3 ] ;

    /** 結果オブジェクト. **/
    protected RemoteResultImpl result = null ;

    /** メタオブジェクト. **/
    protected final RemoteMetaDataImpl meta = new RemoteMetaDataImpl() ;



    /** コンストラクタ. **/
    private RemotePreparedStatement() {}

    /** コンストラクタ. **/
    protected RemotePreparedStatement( RemoteConnection conn,String sql ) {
        open( conn,sql ) ;
    }

    /** ファイナライズ. **/
    protected void finalize() throws Exception {
        close() ;
    }

    /** 情報生成. **/
    protected void open( RemoteConnection conn,String sql ) {
        this.sql = sql ;
        this.connection = conn ;

        // プリコンパイル済み情報を取得.
        QueryCompileInfo cmp ;
        SoftReference<QueryCompileInfo> s = conn.preparedList.get( sql ) ;

        // プリコンパイル済み情報が存在しない場合は、新規作成.
        if( s == null || ( cmp = s.get() ) == null ) {
            try {
                cmp = RemoteConnection.compile( conn,sql ) ;
            } catch( RuntimeException r ) {
                throw r ;
            } catch( Exception e ) {
                throw new MimdbException( e ) ;
            }
            // プリコンパイル済み情報を登録.
            conn.preparedList.put( sql,new SoftReference<QueryCompileInfo>( cmp ) ) ;
        }
        compile = cmp ;
        meta.create( conn,cmp,false ) ;
    }

    /**
     * オブジェクトクローズ.
     */
    public void close() {
        compile = null ;
        sql = null ;
        connection = null ;
        meta.clear() ;
    }

    /**
     * オブジェクトがクローズしているか取得.
     * @return boolean [true]の場合、クローズしています.
     */
    public boolean isClose() {
        return connection == null || connection.isClose() ;
    }

    /** チェック処理. **/
    protected final void check() {
        if( isClose() ) {
            throw new MimdbException( "オブジェクトは既にクローズしています" ) ;
        }
    }

    /**
     * DB更新IDの取得.
     * @return long DB更新IDが返却されます.
     */
    public long getDbId() {
        check() ;
        return compile.dbId ;
    }

    /**
     * テーブル名を取得.
     * @return String テーブル名が返却されます.
     */
    public String getName() {
        check() ;
        return compile.name ;
    }

    /**
     * テーブルオブジェクトを取得.
     * @return BaseTable テーブルオブジェクトが返却されます.
     */
    public BaseTable getTable() {
        check() ;
        try {
            return connection.client.getTable( compile.name ) ;
        } catch( RuntimeException r ) {
            throw r ;
        } catch( Exception e ) {
            throw new MimdbException( e ) ;
        }
    }

    /**
     * preparedパラメータ数を取得.
     * @return int パラメータ数が返却されます.
     */
    public int length() {
        check() ;
        return compile.preparedParamsSize ;
    }

    /**
     * preparedパラメータをクリア.
     * @return MimdbPreparedStatement このオブジェクトが返却されます.
     */
    public MimdbPreparedStatement clearParams() {
        check() ;
        executionParams = null ;
        return this ;
    }

    /**
     * preparedパラメータを設定.
     * @param index 対象のパラメータ項番を設定します.
     * @param value 対象の設定情報を設定します.
     * @return MimdbPreparedStatement このオブジェクトが返却されます.
     */
    public MimdbPreparedStatement setParams( int index,Object value ) {
        check() ;
        if( index < 0 || index >= compile.preparedParamsSize ) {
            return this ;
        }
        if( executionParams == null ) {
            executionParams = new Object[ compile.preparedParamsSize ] ;
        }

        executionParams[ index ] = value ;
        return this ;
    }

    /**
     * 指定パラメータ項番のカラム名を取得.
     * @param index 対象のパラメータ項番を設定します.
     * @return String カラム名が返却されます.
     */
    public String getColumnName( int index ) {
        check() ;
        if( compile.preparedParams == null ||
            index < 0 || index >= compile.preparedParamsSize ) {
            return null ;
        }
        return compile.preparedParams[ index ].getColumn() ;
    }

    /**
     * 指定パラメータ項番の型タイプを取得.
     * @param index 対象のパラメータ項番を設定します.
     * @return int 型タイプが返却されます.
     */
    public int getType( int index ) {
        check() ;
        if( compile.preparedParams == null ||
            index < 0 || index >= compile.preparedParamsSize ) {
            return -1 ;
        }
        try {
            RemoteConnection.checkTable( connection,compile.name ) ;
        } catch( RuntimeException r ) {
            throw r ;
        } catch( Exception e ) {
            throw new MimdbException( e ) ;
        }
        return RemoteTableManager.getInstance().get( compile.name ).
            getColumnType( compile.preparedParams[ index ].getColumn() ) ;
    }

    /**
     * 指定パラメータがリスト形式での設定が可能かチェック.
     * @param index 対象のパラメータ項番を設定します.
     * @return boolean [true]の場合、リスト形式での設定が可能です.
     */
    public boolean isListParam( int index ) {
        check() ;
        if( compile.preparedParams == null ||
            index < 0 || index >= compile.preparedParamsSize ) {
            return false ;
        }
        int type = compile.preparedParams[ index ].getType() ;
        return type == MimdbSearchType.TYPE_IN ||
            type == MimdbSearchType.TYPE_BETWEEN ;
    }

    /**
     * 表示オフセット値を設定.
     * この条件により、表示位置を確定できます.
     * @param off 表示オフセット値を設定します.
     *            [-1]が設定された場合、表示幅は確定されません.
     * @return MimdbPreparedStatement このオブジェクトが返却されます.
     */
    public MimdbPreparedStatement setOffset( int off ) {
        check() ;
        viewOffset = off ;
        return this ;
    }

    /**
     * 表示リミット値を設定.
     * この条件により、表示位置を確定できます.
     * @param limit 表示リミット値を設定します.
     *            [-1]が設定された場合、表示幅は確定されません.
     * @return MimdbPreparedStatement このオブジェクトが返却されます.
     */
    public MimdbPreparedStatement setLimit( int limit ) {
        check() ;
        viewLimit = limit ;
        return this ;
    }

    /** オフセット、リミット値をクリア. **/
    protected void clearOffLimit() {
        viewOffset = -1 ;
        viewLimit = -1 ;
    }

    /**
     * フェッチサイズを設定.
     * ※この値は、サーバーモードでの接続のみ有効となります.
     * @param size フェッチサイズを設定します.
     * @return MimdbPreparedStatement このオブジェクトが返却されます.
     */
    public MimdbPreparedStatement setFetchSize( int size ) {
        check() ;
        fetchSize = size ;
        return this ;
    }

    /**
     * フェッチサイズを取得.
     * ※この値は、サーバーモードでの接続のみ有効となります.
     * @return int フェッチサイズが返却されます.
     */
    public int getFetchSize() {
        check() ;
        return fetchSize ;
    }

    /**
     * クエリー実行.
     * @return MimdbResult 実行結果が返却されます.
     * @exception Exception 例外.
     */
    public MimdbResult executeQuery()
        throws Exception {
        check() ;

        // オフセット、リミット値設定.
        int off,lmt ;
        off  = compile.defOffset ;
        lmt  = compile.defLimit ;
        if( viewOffset >= 0 ) {
            off = viewOffset ;
            viewOffset = -1 ;
        }
        if( viewLimit >= 0 ) {
            lmt = viewLimit ;
            viewLimit = -1 ;
        }

        // 取得パラメータ初期化.
        out[ 0 ] = -1 ;
        out[ 1 ] = -1 ;
        out[ 2 ] = -1 ;

        // パラメータチェック.
        if( compile.preparedParamsSize > 0 && executionParams == null ) {
            throw new MimdbException( "実行パラメータが定義されていません" ) ;
        }

        // プリコンパイル済み実行.
        connection.client.prepared(
            out,compile.getPreparedId(),compile,off,lmt,executionParams ) ;

        // 処理結果を返却.
        return getResult(
            connection,compile,meta,fetchSize,out[0],out[1],out[2] ) ;
    }

    /** RemoteResultImplを生成. **/
    private final RemoteResultImpl getResult( RemoteConnection conn,QueryCompileInfo cmp,
        RemoteMetaDataImpl mt,int fetch,int resId,int len,int max ) {

        if( connection.result.isClearData() ) {
            connection.result.create( conn,cmp,mt,fetch,resId,len,max ) ;
            return connection.result ;
        }
        else if( result == null ) {
            result = new RemoteResultImpl( conn,cmp,mt,fetch,resId,len,max ) ;
            return result ;
        }
        else if( result.isClearData() ) {
            result.create( conn,cmp,mt,fetch,resId,len,max ) ;
            return result ;
        }
        return new RemoteResultImpl( conn,cmp,mt,fetch,resId,len,max ) ;
    }
}
