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

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

import javax.script.Bindings;
import javax.script.Compilable;
import javax.script.CompiledScript;
import javax.script.Invocable;
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.ExecutionScript;
import org.maachang.comet.httpd.engine.script.ScriptDef;
import org.maachang.manager.GlobalManager;

/**
 * スクリプト実行処理実装.
 * 
 * @version 2007/08/24
 * @author masahito suzuki
 * @since MaachangComet 1.00
 */
class ExecutionScriptImpl implements ExecutionScript {
    /**
     * 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 ;
    
    /**
     * LOG用.
     */
    private static final Log LOG = LogFactory.getLog( ExecutionScript.class ) ;
    
    /**
     * スクリプト識別子.
     */
    private String command = null ;
    
    /**
     * コンパイル済みスクリプト.
     */
    private CompiledScript comp = null ;
    
    /**
     * スクリプトデータフラグ.
     */
    private int scriptDataType = -1 ;
    
    /**
     * スクリプト名.
     */
    private String scriptName = 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 = new ScriptEngineManager() ;
        ScriptEngine engine = manager.getEngineByName( command ) ;
        if( engine == null ) {
            throw new IOException( "指定スクリプト識別子[" + command + "]は存在しません" ) ;
        }
        if( ( engine instanceof Invocable ) == false ) {
            throw new IOException( "指定スクリプト識別子[" + command + "]は、Invocableがサポートしていません" ) ;
        }
        else if( ( engine instanceof Compilable ) == false ) {
            throw new IOException( "指定スクリプト識別子[" + command + "]は、Compilableがサポートしていません" ) ;
        }
        Compilable compilable = (Compilable)engine ;
        engine.put( ScriptEngine.FILENAME,name ) ;
        CompiledScript comp = compilable.compile( script ) ;
        this.comp = comp ;
        this.command = command ;
        this.scriptName = name ;
        this.scriptDataType = scriptDataType ;
    }
    
    /**
     * デストラクタ.
     */
    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が未設定です" ) ;
        }
        return comp.eval( ctx ) ;
    }
    
    /**
     * スクリプト実行.
     * <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 ) ;
        return comp.eval( ctx ) ;
    }
    
    /**
     * スクリプト識別子を取得.
     * <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,ScriptDef.getTypeByString( scriptDataType ) ) ;
        bindings.put( MANAGER,GlobalManager.getInstance() ) ;
        bindings.put( KEY_LOG,LOG ) ;
        bindings.put( CTX,ctx ) ;
        bindings.put( ENGINE,engine ) ;
        // defaultパラメータを設定.
        if( defaultParams != null ) {
            Object[] objs = defaultParams.keySet().toArray() ;
            if( objs != null && objs.length > 0 ) {
                int len = objs.length ;
                for( int i = 0 ; i < len ; i ++ ) {
                    String k = ( String )objs[ i ] ;
                    bindings.put( k,defaultParams.get( k ) ) ;
                }
            }
        }
    }
    
    /**
     * パラメータを追加.
     */
    private static final void putParams( HashMap<String,Object> params,Bindings bindings ) {
        int len;
        if( params != null && ( len = params.size() ) > 0 ) {
            Object[] objs = params.keySet().toArray() ;
            for( int i = 0 ; i < len ; i ++ ) {
                bindings.put( ( String )objs[ i ],params.get( ( String )objs[ i ] ) ) ;
            }
        }
    }
}
