package org.maachang.comet.httpd.engine.script.scripts;

import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;

import javax.script.Bindings;
import javax.script.Compilable;
import javax.script.CompiledScript;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.SimpleScriptContext;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.maachang.comet.httpd.engine.script.EndScript;
import org.maachang.comet.httpd.engine.script.ExecutionScript;
import org.maachang.comet.httpd.engine.script.ScriptDef;
import org.maachang.jsr.script.api.ApiManager;
import org.maachang.manager.GlobalManager;

/**
 * スクリプト実行処理実装.
 * 
 * @version 2007/08/24
 * @author masahito suzuki
 * @since MaachangComet 1.00
 */
class ExecutionScriptImpl implements ExecutionScript {
    /**
     * LOG用.
     */
    private static final Log LOG = LogFactory.getLog( ExecutionScript.class ) ;
    
    /**
     * MaachangComet管理テーブル.
     */
    private static final String MANAGER = ScriptDef.MANAGER ;
    
    /**
     * ログファイル出力用.
     */
    private static final String KEY_LOG = ScriptDef.SCRIPT_BY_LOG ;
    
    /**
     * エンジン処理用.
     */
    private static final String ENGINE = ScriptDef.SCRIPT_BY_ENGINE ;
    
    /**
     * コンテキスト処理用.
     */
    private static final String CTX = ScriptDef.SCRIPT_BY_CTX ;
    
    /**
     * スクリプト識別子.
     */
    private String command = null ;
    
    /**
     * コンパイル済みスクリプト.
     */
    private CompiledScript comp = null ;
    
    /**
     * スクリプトデータフラグ.
     */
    private int scriptDataType = -1 ;
    
    /**
     * スクリプト名.
     */
    private String scriptName = null ;
    
    /**
     * スクリプト内容.
     */
    private String scriptData = null ;
    
    /**
     * デフォルトパラメータ.
     */
    private HashMap<String,Object> defaultParams = null ;
    
    /**
     * コンストラクタ.
     */
    private ExecutionScriptImpl() {
        
    }
    
    /**
     * コンストラクタ.
     * <BR><BR>
     * 対象スクリプト文字列を渡して、オブジェクトを生成します.
     * <BR>
     * @param command 対象の実行スクリプト識別子を設定します.
     * @param scriptDataType 対象のスクリプトデータタイプを設定します.
     * @param name 対象のスクリプト名を設定します.
     * @param script 対象のスクリプトを設定します.
     * @exception Exception 例外.
     */
    public ExecutionScriptImpl( String command,int scriptDataType,String name,String script ) throws Exception {
        ScriptEngineManager manager = ApiManager.getInstance().getScriptEngineManager() ;
        ScriptEngine engine = manager.getEngineByName( command ) ;
        if( engine == null ) {
            throw new IOException( "指定スクリプト識別子[" + command + "]は存在しません" ) ;
        }
        else if( ( engine instanceof Compilable ) == false ) {
            throw new IOException( "指定スクリプト識別子[" + command + "]は、Compilableがサポートしていません" ) ;
        }
        //if( LOG.isDebugEnabled() ) {
        //    script = new StringBuilder().
        //        append( "if(isDebugEnabled()){" ).
        //        append( "debugLog('script:" ).append( name ).append( "');" ).
        //        append( "};" ).append( script ).
        //        toString() ;
        //}
        ApiManager.setScriptName( name ) ;
        Compilable compilable = (Compilable)engine ;
        CompiledScript comp = compilable.compile( script ) ;
        this.comp = comp ;
        this.command = command ;
        this.scriptName = name ;
        this.scriptDataType = scriptDataType ;
        if( LOG.isDebugEnabled() ) {
            this.scriptData = script ;
            LOG.debug( ">> createScript:" + this.scriptName ) ;
        }
    }
    
    /**
     * デストラクタ.
     */
    protected void finalize() throws Exception {
    }
    
    /**
     * スクリプト単体実行.
     * <BR><BR>
     * スクリプトを単体で実行します.<BR>
     * このメソッドは、主に[InnerScript]等、スクリプト内で実行する場合に
     * 呼び出されます.
     * <BR>
     * @param ctx 対象のスクリプトコンテキストを設定します.
     * @exception Exception 例外.
     */
    public Object eval( ScriptContext ctx ) throws Exception {
        if( ctx == null ) {
            throw new IllegalArgumentException( "ScriptContextが未設定です" ) ;
        }
        if( LOG.isDebugEnabled() ) {
            LOG.debug( ">> eval[" + scriptName + "]" ) ;
        }
        ApiManager.setScriptName( this.scriptName ) ;
        try {
            return comp.eval( ctx ) ;
        } catch( Exception e ) {
            if( EndScript.isEndScript( e ) ) {
                throw e ;
            }
            if( LOG.isDebugEnabled() ) {
                LOG.debug( "## SCRIPT ERROR:" + this.scriptName +
                    "\n<<<START>>>\n" + this.scriptData + "\n<<<END>>>",e ) ;
            }
            throw e ;
        }
    }
    
    /**
     * スクリプト実行.
     * <BR><BR>
     * 対象のスクリプトを実行します.
     * <BR>
     * @param params 対象のパラメータを設定します.
     * @return Object 処理結果が返されます.
     * @exception Exception 例外.
     */
    public Object execution( HashMap<String,Object> params ) throws Exception {
        return execution( null,params ) ;
    }
    
    /**
     * スクリプト実行.
     * <BR><BR>
     * 対象のスクリプトを実行します.
     * <BR>
     * @param ctx 対象のスクリプトコンテキストを設定します.
     * @param params 対象のパラメータを設定します.
     * @return Object 処理結果が返されます.
     * @exception Exception 例外.
     */
    public Object execution( ScriptContext ctx,HashMap<String,Object> params ) throws Exception {
        if( ctx == null ) {
            ctx = new SimpleScriptContext() ;
        }
        Bindings bindings = ctx.getBindings( ScriptContext.ENGINE_SCOPE ) ;
        putParams( params,bindings ) ;
        putObject( scriptDataType,command,scriptName,comp.getEngine(),defaultParams,ctx,bindings ) ;
        if( LOG.isDebugEnabled() ) {
            LOG.debug( ">> execution[" + scriptName + "]" ) ;
        }
        ApiManager.setScriptName( this.scriptName ) ;
        try {
            return comp.eval( ctx ) ;
        } catch( Exception e ) {
            if( EndScript.isEndScript( e ) ) {
                throw e ;
            }
            if( LOG.isDebugEnabled() ) {
                LOG.debug( "## SCRIPT ERROR:" + this.scriptName +
                    "\n<<<START>>>\n" + this.scriptData + "\n<<<END>>>",e ) ;
            }
            throw e ;
        }
    }
    
    /**
     * スクリプト識別子を取得.
     * <BR><BR>
     * スクリプト識別子を取得します.
     * <BR>
     * @return String スクリプト識別子が返されます.
     */
    public String getCommand() {
        return command ;
    }
    
    /**
     * スクリプトデータタイプを取得.
     * <BR><BR>
     * スクリプトデータタイプを取得します.
     * <BR>
     * @return スクリプトデータタイプが返されます.
     */
    public int getScriptDataType() {
        return scriptDataType ;
    }
    
    /**
     * スクリプト名を取得.
     * <BR><BR>
     * スクリプト名を取得します.
     * <BR>
     * @return String スクリプト名が返されます.
     */
    public String getScriptName() {
        return scriptName ;
    }
    
    /**
     * デフォルトパラメータを設定.
     * <BR><BR>
     * デフォルトパラメータを設定します.
     * <BR>
     * @param key 対象のキー名を設定します.
     * @param value 対象の要素を設定します.
     */
    public void putDefault( String key,Object value ) {
        if( key == null || value == null ) {
            return ;
        }
        if( defaultParams == null ) {
            defaultParams = new HashMap<String,Object>() ;
        }
        defaultParams.put( key,value ) ;
    }
    
    /**
     * 実行前に追加するオブジェクト.
     */
    private static final void putObject( int scriptDataType,String command,String name,ScriptEngine engine,
        HashMap<String,Object> defaultParams,ScriptContext ctx,Bindings bindings ) {
        bindings.put( ScriptDef.CURRENT_SCRIPT,name ) ;
        bindings.put( ScriptDef.SCRIPT_DETAIL,ScriptManager.getTypeByString( scriptDataType ) ) ;
        bindings.put( MANAGER,GlobalManager.getInstance() ) ;
        bindings.put( KEY_LOG,LOG ) ;
        bindings.put( CTX,ctx ) ;
        bindings.put( ENGINE,engine ) ;
        // defaultパラメータを設定.
        if( defaultParams != null ) {
            Iterator it = defaultParams.keySet().iterator() ;
            while( it.hasNext() ) {
                String key = ( String )it.next() ;
                bindings.put( key,defaultParams.get( key ) ) ;
            }
        }
    }
    
    /**
     * パラメータを追加.
     */
    private static final void putParams( HashMap<String,Object> params,Bindings bindings ) {
        if( params != null && params.size() > 0 ) {
            Iterator it = params.keySet().iterator() ;
            while( it.hasNext() ) {
                String key = ( String )it.next() ;
                bindings.put( key,params.get( key ) ) ;
            }
        }
    }
}
