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

import java.util.HashMap;

import javax.script.Bindings;
import javax.script.ScriptContext;
import javax.script.ScriptException;
import javax.script.SimpleScriptContext;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.maachang.comet.ServiceDef;
import org.maachang.comet.httpd.HttpdErrorDef;
import org.maachang.comet.httpd.HttpdParams;
import org.maachang.comet.httpd.HttpdRequest;
import org.maachang.comet.httpd.HttpdResponse;
import org.maachang.comet.httpd.HttpdStateException;
import org.maachang.comet.httpd.engine.HttpdDef;
import org.maachang.comet.httpd.engine.script.BaseModel;
import org.maachang.comet.httpd.engine.script.BaseModelImpl;
import org.maachang.comet.httpd.engine.script.BaseScriptException;
import org.maachang.comet.httpd.engine.script.EndScript;
import org.maachang.comet.httpd.engine.script.Script;
import org.maachang.comet.httpd.engine.script.ScriptDef;
import org.maachang.comet.httpd.engine.script.cache.CacheScriptManager;
import org.maachang.comet.httpd.engine.session.HttpdSession;
import org.maachang.manager.GlobalManager;
import org.maachang.util.FileUtil;

/**
 * リクエストに対する実行用ターゲットスクリプト.
 * 
 * @version 2007/08/24
 * @author masahito suzuki
 * @since MaachangComet 1.00
 */
public class TargetScript {
    
    /**
     * LOG.
     */
    private static final Log LOG = LogFactory.getLog( TargetScript.class ) ;
    
    /**
     * スクリプト実行処理.
     * @param path 対象のパス名を設定します.
     * @param request 対象のリクエスト情報を設定します.
     * @param params 対象のパラメータを設定します.
     * @return Object スクリプト処理結果が返されます.
     * @exception Exception 例外.
     */
    public static final Object executionScript( String path,HttpdRequest request,
        HashMap<String,Object> params ) throws Exception {
        if( request == null ) {
            throw new HttpdStateException( HttpdErrorDef.HTTP10_400,"不正なリクエストを受信しました" ) ;
        }
        long allTime = System.currentTimeMillis() ;
        ApplicationScriptFactory factory = getFactory() ;
        // 読み込み対象の拡張子が[.ms]及び、拡張子なし以外の場合.
        path = path.trim() ;
        String name = FileUtil.getFileName( path ) ;
        path = path.substring( 0,path.length()-name.length() ) ;
        if( name.indexOf( "." ) != -1 ) {
            if( name.endsWith( ScriptManager.SCRIPT_PLUS ) == true ) {
                name = name.substring( 0,name.length()-ScriptManager.SCRIPT_PLUS.length() ) ;
            }
        }
        // ターゲットのスクリプトエンジンを取得.
        Script script = factory.getApplication( path+name ) ;
        if( script == null ) {
            throw new HttpdStateException( HttpdErrorDef.HTTP11_500,
                "対象パス["+(path+name)+"]スクリプトの読み込みに失敗しました" ) ;
        }
        String acName = path + name ;
        
        long time = -1L ;
        // ターゲットURLが、ページキャッシュされている場合の処理.
        String cache = PageCache.getInstance().get( path ) ;
        if( cache != null ) {
            HttpdResponse res = ( HttpdResponse )params.get( ScriptDef.SCRIPT_BY_RESPONSE ) ;
            // レスポンスヘッダ周りを設定する.
            res.getHeader().setHeader( HttpdDef.VALUE_CONTENT_TYPE,HttpdDef.MHTML_MIME_TYPE ) ;
            res.getPrint().print( cache ) ;
            return "exit-script" ;
        }
        // キャッシュされていない場合などは、通常処理.
        Object ret = null ;
        ScriptContext context = null ;
        BaseModel baseModel = new BaseModelImpl() ;
        if( params == null ) {
            params = new HashMap<String,Object>() ;
        }
        params.put( ScriptDef.SCRIPT_MODE,ScriptDef.MODE_NOT_COMET ) ;
        try {
            if( LOG.isDebugEnabled() ) {
                /** スクリプト初期化処理用 デバッグログ */
                time = System.currentTimeMillis() ;
            }
            context = new SimpleScriptContext() ;
            // ライブラリスクリプトを読み込む.
            CacheScriptManager.getInstance().script( context ) ;
            // 各パラメータを取得.
            createContext( path,request,context,baseModel ) ;
            // デフォルトパラメータを取得.
            ScriptDef.setDirectoryByBindings( context ) ;
            
            if( LOG.isDebugEnabled() ) {
                /** スクリプト初期化処理用 デバッグログ */
                time = System.currentTimeMillis() - time ;
                LOG.debug( "## init(" + acName + ") = " + time + "msec" ) ;
                time = System.currentTimeMillis() ;
            }
            
            // ターゲットスクリプトを読み込む.
            ret = script.getScript().execution( context,params ) ;
            if( LOG.isDebugEnabled() ) {
                /** スクリプト実行処理用 デバッグログ */
                time = System.currentTimeMillis() - time ;
                LOG.debug( "## script(" + acName + ") = " + time + "msec" ) ;
                time = System.currentTimeMillis() ;
            }
            
            // コミット処理.
            if( baseModel.isCreate() == true ) {
                baseModel.commit() ;
            }
            if( LOG.isDebugEnabled() ) {
                /** スクリプトコミット処理用 デバッグログ */
                time = System.currentTimeMillis() - time ;
                LOG.debug( "## commit(" + acName + ") = " + time + "msec" ) ;
            }
            
        } catch( ScriptException sc ) {
            if( EndScript.isEndScript( sc ) == true ) {
                ret = EndScript.getEndByResult( context ) ;
                if( LOG.isDebugEnabled() ) {
                    /** スクリプト実行処理用 デバッグログ */
                    time = System.currentTimeMillis() - time ;
                    LOG.debug( "## script(e)(" + acName + ") = " + time + "msec" ) ;
                    time = System.currentTimeMillis() ;
                }
                
                // コミット処理.
                if( baseModel.isCreate() == true ) {
                    baseModel.commit() ;
                }
                if( LOG.isDebugEnabled() ) {
                    /** スクリプトコミット処理用 デバッグログ */
                    time = System.currentTimeMillis() - time ;
                    LOG.debug( "## commit(e)(" + acName + ") = " + time + "msec" ) ;
                }
            }
            else {
                if( baseModel.isCreate() == true ) {
                    try {
                        baseModel.rollback() ;
                    } catch( Exception ee ) {
                    }
                }
                throw new BaseScriptException( sc ) ;
            }
        } catch( Exception e ) {
            if( baseModel.isCreate() == true ) {
                try {
                    baseModel.rollback() ;
                } catch( Exception ee ) {
                }
            }
            throw e ;
        } finally {
            if( baseModel.isCreate() == true ) {
                try {
                    baseModel.getRecord().close() ;
                } catch( Exception ee ) {
                }
            }
            baseModel = null ;
            CacheScriptManager.getInstance().exitScript() ;
            if( LOG.isDebugEnabled() ) {
                LOG.debug( "... readScript:" + acName +
                    " - ["+( System.currentTimeMillis()-allTime )+"ms]" ) ;
            }
        }
        return ret ;
    }
    
    /**
     * コンテキストを生成.
     */
    private static void createContext( String path,HttpdRequest req,ScriptContext ctx,BaseModel baseModel )
        throws Exception {
        // 基本パラメータを定義.
        Bindings bindings = ctx.getBindings( ScriptContext.ENGINE_SCOPE ) ;
        bindings.put( ScriptDef.SCRIPT_BY_MODEL,baseModel ) ;
        bindings.put( ScriptDef.SCRIPT_BY_PATH,path ) ;
        bindings.put( ScriptDef.SCRIPT_BY_HEADER,req.getHeader() ) ;
        HttpdSession session = req.getSession() ;
        if( session != null ) {
            bindings.put( ScriptDef.SCRIPT_BY_SESSION,session ) ;
        }
        // リクエスト内容を設定.
        pushRequestParams( bindings,req.getQuery() ) ;
    }
    
    /**
     * WebAppScriptFactoryを取得.
     */
    private static final ApplicationScriptFactory getFactory() {
        return ( ApplicationScriptFactory )GlobalManager.getValue(
            ServiceDef.MANAGER_BY_WEB_APP_FACTORY ) ;
    }
    
    /**
     * リクエストパラメータを設定.
     */
    private static final void pushRequestParams( Bindings bindings,HttpdParams params ) {
        bindings.put( ScriptDef.SCRIPT_BY_QUERY,ScriptDef.getQuery( params ) ) ;
    }
}
