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

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.MaachangDef;
import org.maachang.comet.ServiceDef;
import org.maachang.comet.httpd.engine.script.DummyBaseModel;
import org.maachang.comet.httpd.engine.script.ScriptDef;
import org.maachang.comet.httpd.engine.script.SrcScript;
import org.maachang.comet.httpd.engine.script.js.JsDef;
import org.maachang.manager.GlobalManager;
import org.maachang.util.FileUtil;


/**
 * モデルスクリプトキャッシュ.
 * 
 * @version 2007/11/04
 * @author masahito suzuki
 * @since MaachangComet 1.00
 */
public class CacheModelScript {
    
    /**
     * デフォルトキャッシュ名.
     */
    private static final String CACHE_TABLE_NAME = "model" ;
    
    /**
     * スクリプトエンジン名.
     */
    protected static final String ENGINE_NAME = "js" ;
    
    /**
     * スクリプト実行名.
     */
    protected static final String SCRIPT_NAME = "[models]" ;
    
    /**
     * カスタムモデルスクリプト.
     */
    protected static final String MODEL_CUSTOM = "Custom"+ScriptDef.SCRIPT_PLUS ;
    
    /**
     * 読み込み対象ディレクトリ.
     */
    private static final String READ_DIR = baseDir() ;
    
    /**
     * カレントディレクトリ.
     */
    private static final String CURRENT_DIR = currentDir() ;
    
    /**
     * 再読み込み確認時間.
     */
    private static final long CHECK_TIME = 30000L ;
    
    /**
     * LOG.
     */
    private static final Log LOG = LogFactory.getLog( CacheModelScript.class ) ;
    
    /**
     * 前回再読み込み時間.
     */
    private long beforeTime = -1L ;
    
    /**
     * モデル管理コード.
     */
    private String modelCode = null ;
    
    /**
     * スクリプト内容.
     */
    private SrcScript srcScript = null ;
    
    /**
     * デフォルトパッケージ更新ID.
     */
    private int packageId = -1 ;
    
    /**
     * 同期オブジェクト.
     */
    private final Object sync = new Object() ;
    
    /**
     * シングルトン.
     */
    //private static final CacheModelScript SNGL = new CacheModelScript() ;
    
    /**
     * コンストラクタ.
     */
    public CacheModelScript() {
        
    }
    
    /**
     * デストラクタ.
     */
    protected void finalize() throws Exception {
        this.clear() ;
    }
    
    /**
     * オブジェクトを取得.
     * <BR><BR>
     * オブジェクトを取得します.
     * <BR>
     * @return CacheModelScript オブジェクトを取得します.
     */
    //public static final CacheModelScript getInstance() {
    //    return SNGL ;
    //}
    
    /**
     * 情報クリア.
     * <BR><BR>
     * 情報をクリアします.
     */
    protected void clear() {
        synchronized( sync ) {
            beforeTime = -1L ;
            modelCode = null ;
            srcScript = null ;
            packageId = -1 ;
        }
    }
    
    /**
     * 再読み込み処理.
     * <BR><BR>
     * 再読み込み処理を行います.
     * <BR>
     * @param wrapper 取得対象のスクリプトWrapperを設定します.
     * @param CacheTableWrapper 対象のスクリプトWrapperが返されます.
     * @exception Exception 例外.
     */
    public CacheTableWrapper reload( CacheTableWrapper wrapper )
        throws Exception {
        if( wrapper == null ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        try {
            synchronized( sync ) {
                readScript( wrapper ) ;
            }
            return wrapper ;
        } catch( Exception e ) {
            this.clear() ;
            throw e ;
        }
    }
    
    /**
     * スクリプトキャッシュを取得.
     * <BR><BR>
     * スクリプトキャッシュを取得します.
     * <BR>
     * @param wrapper 取得対象のスクリプトWrapperを設定します.
     * @param CacheTableWrapper 対象のスクリプトWrapperが返されます.
     * @exception Exception 例外.
     */
    public CacheTableWrapper script( CacheTableWrapper wrapper ) throws Exception {
        if( wrapper == null ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        try {
            synchronized( sync ) {
                if( GlobalManager.getValue( ServiceDef.MANAGER_BY_DBMS_POOL ) != null ) {
                    if( this.packageId != -1 &&
                        this.packageId != JsDef.getDefaultPackageId() ) {
                        readScript( wrapper ) ;
                    }
                    else if( beforeTime + CHECK_TIME <= System.currentTimeMillis() ) {
                        if( wrapper.getCacheTable().isParent( CACHE_TABLE_NAME ) == false ) {
                            readScript( wrapper ) ;
                        }
                        else if( modelCode == null ) {
                            readScript( wrapper ) ;
                        }
                        else if( modelCode.equals( createModelMonitor() ) == false ) {
                            readScript( wrapper ) ;
                        }
                    }
                }
            }
            return wrapper ;
        } catch( Exception e ) {
            this.clear() ;
            throw e ;
        }
    }
    
    /**
     * スクリプト内容を取得.
     * <BR><BR>
     * スクリプト内容を取得します.
     * <BR>
     * @return SrcScript スクリプト内容が返されます.
     */
    public SrcScript getSrcScript() {
        SrcScript ret = null ;
        synchronized( sync ) {
            ret = srcScript ;
        }
        return ret ;
    }
    
    /**
     * 指定パスがこのキャッシュ内の条件であるかチェック.
     * <BR><BR>
     * 指定パスがこのキャッシュ内の条件であるかチェックします.
     * <BR>
     * @param path チェック対象のパスを設定します.
     * @return boolean [true]の場合はこのキャッシュ内の条件です.
     */
    public boolean isCache( String path ) {
        if( path == null || ( path = path.trim() ).length() <= 0 ||
            path.equals( SCRIPT_NAME ) == false ) {
            return false ;
        }
        return true ;
    }
    
    /**
     * Model管理コードを生成.
     */
    private String createModelMonitor()
        throws Exception {
        String[] models = ScriptDef.getUseScript( READ_DIR ) ;
        return ScriptDef.getScriptManagerCode( models,CURRENT_DIR ) ;
    }
    
    /**
     * Model内容を追加.
     */
    private void putModel( String model,StringBuilder buf )
        throws Exception {
        buf.append( "\n\n" ) ;
        buf.append( "var " ).append( model ).append( " = baseModel(\"" ).append( model ).append( "\");\n" ) ;
        String modelScript = FileUtil.getFileByString( READ_DIR+"/"+model+ScriptDef.SCRIPT_PLUS,"UTF8" ) ;
        if( modelScript != null && ( modelScript = modelScript.trim() ).length() > 0 ) {
            buf.append( modelScript ).append( "\n" ) ;
        }
        modelScript = null ;
        // カスタムモデルスクリプトが存在する場合、それも含めて取り込む.
        String custom = READ_DIR+"/"+model+MODEL_CUSTOM ;
        if( FileUtil.isFileExists( custom ) == true ) {
            modelScript = FileUtil.getFileByString( READ_DIR+"/"+model+ScriptDef.SCRIPT_PLUS,"UTF8" ) ;
            if( modelScript != null && ( modelScript = modelScript.trim() ).length() > 0 ) {
                buf.append( "\n" ).append( modelScript ).append( "\n" ) ;
            }
        }
    }
    
    /**
     * スクリプト内容を読み込む.
     */
    private void readScript( CacheTableWrapper wrapper )
        throws Exception {
        // モデル内容を読み込み.
        String[] models = ScriptDef.getUseScript( READ_DIR ) ;
        if( models != null && models.length > 0 ) {
            String managerCode = ScriptDef.getScriptManagerCode( models,CURRENT_DIR ) ;
            StringBuilder buf = new StringBuilder() ;
            this.packageId = JsDef.pushDefaultPackage( buf ) ;
            int len = models.length ;
            for( int i = 0 ; i < len ; i ++ ) {
                if( models[i].endsWith( MODEL_CUSTOM ) ) {
                    continue ;
                }
                if( LOG.isDebugEnabled() ) {
                    LOG.debug( ">read[model] - " + models[ i ] ) ;
                }
                putModel( models[ i ],buf ) ;
            }
            String script = buf.toString() ;
            buf = null ;
            this.srcScript = new SrcScript( script ) ;
            
            // ModelScript実行.
            ScriptEngineManager manager = new ScriptEngineManager() ;
            ScriptEngine engine = manager.getEngineByName( ENGINE_NAME ) ;
            // 更新待機.
            CacheTable cacheTable = wrapper.getCacheTable() ;
            cacheTable.waitByStartUpdate() ;
            try {
                // 処理実行.
                wrapper.setCacheAppend( true ) ;
                Bindings bindings = new SimpleBindings( wrapper ) ;
                bindings.put( ScriptDef.SCRIPT_BY_MODEL,new DummyBaseModel() ) ;
                engine.put( ScriptEngine.FILENAME,SCRIPT_NAME ) ;
                engine.eval( script,bindings ) ;
                // 登録.
                bindings = cacheTable.getBindings() ;
                cacheTable.putParent( CACHE_TABLE_NAME,bindings ) ;
            } finally {
                wrapper.setCacheAppend( false ) ;
                cacheTable.exitUpdate() ;
            }
            this.modelCode = managerCode ;
            if( LOG.isDebugEnabled() ) {
                LOG.debug( "** model-cacheを読み込み" ) ;
            }
        }
        else {
            this.modelCode = null ;
            this.packageId = -1 ;
        }
        this.beforeTime = System.currentTimeMillis() ;
    }
    
    /**
     * 基本ディレクトリを取得.
     */
    private static final String baseDir() {
        try {
            return FileUtil.getFullPath( MaachangDef.DIRECTORY_MODEL ) ;
        } catch( Exception e ) {
        }
        return null ;
    }
    
    /**
     * カレントディレクトリを取得.
     */
    private static final String currentDir() {
        try {
            String ret = FileUtil.getFullPath( "." ) ;
            if( ret.endsWith( "." ) ) {
                ret = ret.substring( 0,ret.length() - 1 ) ;
            }
            return ret ;
        } catch( Exception e ) {
        }
        return null ;
    }

}
