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

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import javax.script.Bindings;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.SimpleBindings;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.maachang.comet.ServiceDef;
import org.maachang.comet.httpd.engine.script.ScriptDef;
import org.maachang.comet.httpd.engine.script.SrcScript;
import org.maachang.comet.httpd.engine.script.dao.MasterCache;
import org.maachang.manager.GlobalManager;

/**
 * デフォルトスクリプトキャッシュ.
 * 
 * @version 2007/08/29
 * @author masahito suzuki
 * @since MaachangComet 1.00
 */
public class CacheDefaultScript {
    
    /**
     * LOG.
     */
    private static final Log LOG = LogFactory.getLog( CacheDefaultScript.class ) ;
    
    /**
     * デフォルトキャッシュ名.
     */
    private static final String CACHE_TABLE_NAME = "default" ;
    
    /**
     * スクリプトエンジン名.
     */
    protected static final String ENGINE_NAME = "js" ;
    
    /**
     * 読み込みデフォルトスクリプト一覧.
     */
    private static final String[] LIST = {
        "org/maachang/comet/httpd/engine/script/js/default.js",
        "org/maachang/comet/httpd/engine/script/js/log.js",
        "org/maachang/comet/httpd/engine/script/js/exception.js",
        "org/maachang/comet/httpd/engine/script/js/io.js",
        "org/maachang/comet/httpd/engine/script/js/html.js",
        "org/maachang/comet/httpd/engine/script/js/validate.js",
        "org/maachang/comet/httpd/engine/script/js/tag.js",
        "org/maachang/comet/httpd/engine/script/js/master_cache.js",
        "org/maachang/comet/httpd/engine/script/js/record.js",
        "org/maachang/comet/httpd/engine/script/js/base_model.js",
        "org/maachang/comet/httpd/engine/script/js/image.js",
        "org/maachang/comet/httpd/engine/script/js/mdbm.js",
        "org/maachang/comet/httpd/engine/script/js/http_client.js",
        "org/maachang/comet/httpd/engine/script/js/mail.js",
        "org/maachang/comet/httpd/engine/script/js/template.js",
        "org/maachang/comet/httpd/engine/script/js/rpc.js",
        "org/maachang/comet/httpd/engine/script/js/utils.js",
        "org/maachang/comet/httpd/engine/script/js/mttml.js",
        "org/maachang/comet/httpd/engine/script/js/startup.js"
    } ;
    
    /**
     * 読み込みデフォルトスクリプト名.
     */
    private static final String[] LIST_NAME = {
        "*base_script",
        "*log",
        "*exception",
        "*io",
        "*html",
        "*validate",
        "*tag",
        "*master_cache",
        "*record",
        "*base_model",
        "*image",
        "*mdbm",
        "*http_client",
        "*mail",
        "*template",
        "*rpc",
        "*utils",
        "*mttml",
        "*startup"
    } ;
    
    /**
     * スクリプト名接頭語.
     */
    private static final String HEAD = "*" ;
    
    /**
     * LOG.
     */
    //private static final Log LOG = LogFactory.getLog( CacheDefaultScript.class ) ;
    
    /**
     * スクリプトキャッシュラッパー.
     */
    private final CacheTableWrapper wrapper = new CacheTableWrapper() ;
    
    /**
     * ソーススクリプト.
     */
    private HashMap<String,SrcScript> srcScripts = null ;
    
    /**
     * 同期オブジェクト.
     */
    private final Object sync = new Object() ;
    
    /**
     * シングルトン.
     */
    //private static final CacheDefaultScript SNGL = new CacheDefaultScript() ;
    
    /**
     * コンストラクタ.
     */
    public CacheDefaultScript() {
        
    }
    
    /**
     * デストラクタ.
     */
    protected void finalize() throws Exception {
        this.clear() ;
    }
    
    /**
     * オブジェクトを取得.
     * <BR><BR>
     * オブジェクトを取得します.
     * <BR>
     * @return CacheDefaultScript オブジェクトを取得します.
     */
    //public static final CacheDefaultScript getInstance() {
    //    return SNGL ;
    //}
    
    /**
     * 情報クリア.
     * <BR><BR>
     * 情報をクリアします.
     */
    protected void clear() {
        synchronized( sync ) {
            wrapper.getCacheTable().clearAll() ;
            srcScripts = null ;
        }
    }
    
    /**
     * キャッシュラッパーを取得.
     * <BR><BR>
     * キャッシュラッパが返されます.
     * <BR>
     * @return CacheTableWrapper キャッシュラッパーが返されます.
     */
    public CacheTableWrapper getCacheTableWrapper() {
        return wrapper ;
    }
    
    /**
     * スクリプトキャッシュを取得.
     * <BR><BR>
     * スクリプトキャッシュを取得します.
     * <BR>
     * @return CacheTableWrapper スクリプト実行されたキャッシュラッパーが返されます.
     * @exception Exception 例外.
     */
    public CacheTableWrapper script() throws Exception {
        try {
            CacheTableWrapper ret = null ;
            synchronized( sync ) {
                if( wrapper.getCacheTable().isParent( CACHE_TABLE_NAME ) == false ) {
                    loadScripts() ;
                }
                ret = wrapper ;
            }
            return ret ;
        } catch( Exception e ) {
            this.clear() ;
            throw e ;
        }
    }
    
    /**
     * 指定パスのソーススクリプトを取得.
     * <BR><BR>
     * 指定パスのソーススクリプトを取得します.
     * <BR>
     * @param path 対象のパス名を設定します.
     * @return SrcScript 対象のソーススクリプトが返されます.
     */
    public SrcScript getSrcScript( String path ) {
        if( isCache( path ) == false ) {
            return null ;
        }
        synchronized( sync ) {
            if( srcScripts != null ) {
                return srcScripts.get( path ) ;
            }
        }
        return null ;
    }
    
    /**
     * 指定パスがこのキャッシュ内の条件であるかチェック.
     * <BR><BR>
     * 指定パスがこのキャッシュ内の条件であるかチェックします.
     * <BR>
     * @param path チェック対象のパスを設定します.
     * @return boolean [true]の場合はこのキャッシュ内の条件です.
     */
    public boolean isCache( String path ) {
        if( path == null || ( path = path.trim() ).length() <= 0 ||
            path.startsWith( HEAD ) == false ) {
            return false ;
        }
        return true ;
    }
    
    /**
     * スクリプトをロードして実行.
     */
    private void loadScripts() throws Exception {
        int len = LIST.length ;
        ScriptEngineManager manager = new ScriptEngineManager() ;
        ScriptEngine engine = manager.getEngineByName( ENGINE_NAME ) ;
        
        // 更新待機.
        CacheTable cacheTable = wrapper.getCacheTable() ;
        cacheTable.waitByStartUpdate() ;
        try {
            MasterCache masterCache = new MasterCache() ;
            Map<String,Object> shared = Collections.synchronizedMap( new HashMap<String,Object>() ) ;
            GlobalManager.getInstance().put( ServiceDef.MASTER_DB_CACHE_MANAGER,masterCache ) ;
            GlobalManager.getInstance().put( ServiceDef.SHARED_MEMORY_MANAGER,shared ) ;
            wrapper.setCacheAppend( true ) ;
            Bindings bindings = new SimpleBindings( wrapper ) ;
            bindings.put( ScriptDef.SCRIPT_BY_CTX,null ) ;
            bindings.put( ScriptDef.MANAGER,GlobalManager.getInstance() ) ;
            bindings.put( ScriptDef.SCRIPT_CACHE_DB,masterCache ) ;
            bindings.put( ScriptDef.SCRIPT_SHARED,shared ) ;
            this.srcScripts = new HashMap<String,SrcScript>() ;
            for( int i = 0 ; i < len ; i ++ ) {
                if( LOG.isDebugEnabled() ) {
                    LOG.debug( ">read[default] - " + LIST_NAME[ i ] ) ;
                }
                String script = ScriptDef.getScriptByResource( LIST[ i ] ) ;
                this.srcScripts.put( LIST_NAME[ i ],new SrcScript( script ) ) ;
                engine.put( ScriptEngine.FILENAME,LIST_NAME[ i ] ) ;
                engine.eval( script,bindings ) ;
            }
            // 登録.
            bindings = cacheTable.getBindings() ;
            cacheTable.putParent( CACHE_TABLE_NAME,bindings ) ;
        } finally {
            wrapper.setCacheAppend( false ) ;
            cacheTable.exitUpdate() ;
        }
        if( LOG.isDebugEnabled() ) {
            LOG.debug( "** default-cacheを読み込み" ) ;
        }
    }
    
}
