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

import java.util.Map;

import javax.script.Bindings;
import javax.script.Compilable;
import javax.script.CompiledScript;
import javax.script.ScriptEngine;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.maachang.comet.MaachangDef;
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.SrcScriptList;
import org.maachang.comet.httpd.engine.script.js.JsDef;
import org.maachang.jsr.script.api.ApiManager;
import org.maachang.jsr.script.api.ExternalBindings;
import org.maachang.util.FileUtil;

/**
 * ライブラリAPIマネージャ.
 * 
 * @version 2008/08/11
 * @author masahito suzuki
 * @since MaachangComet 1.23
 */
class LibApiManager {
    
    /**
     * LOG.
     */
    private static final Log LOG = LogFactory.getLog( LibApiManager.class ) ;
    
    /**
     * スクリプトエンジン名.
     */
    protected static final String ENGINE_NAME = "js" ;
    
    /**
     * スクリプト実行名.
     */
    protected static final String SCRIPT_NAME = "[lib_api]" ;
    
    /**
     * 読み込み対象ディレクトリ.
     */
    private static final String READ_DIR = baseDir() ;
    
    /**
     * 再読み込み確認時間.
     */
    private static final long CHECK_TIME = 30000L ;
    
    /**
     * 前回再読み込み時間.
     */
    private long beforeTime = -1L ;
    
    /**
     * ライブラリ管理コード.
     */
    private String libCode = null ;
    
    /**
     * スクリプト内容.
     */
    private SrcScript srcScript = null ;
    
    /**
     * デフォルトパッケージ更新ID.
     */
    private int packageId = -1 ;
    
    /**
     * 再読み込みチェックフラグ.
     */
    private boolean reloadFlag = false ;
    
    /**
     * 同期オブジェクト.
     */
    private final Object sync = new Object() ;
    
    /**
     * コンストラクタ.
     */
    public LibApiManager() throws Exception {
        try {
            synchronized( sync ) {
                loadScript() ;
            }
        } catch( Exception e ) {
            LOG.error( "## [libApi] loadScriptError",e ) ;
            throw e ;
        }
    }
    
    /**
     * デストラクタ.
     */
    protected void finalize() throws Exception {
        this.clear() ;
    }
    
    /**
     * 基本ディレクトリを取得.
     */
    private static final String baseDir() {
        try {
            return FileUtil.getFullPath( MaachangDef.DIRECTORY_LIB ) + FileUtil.FILE_SPACE ;
        } catch( Exception e ) {
        }
        return null ;
    }
    
    /**
     * 情報クリア.
     * <BR><BR>
     * 情報をクリアします.
     */
    protected void clear() {
        synchronized( sync ) {
            beforeTime = -1L ;
            libCode = null ;
            srcScript = null ;
            packageId = -1 ;
            reloadFlag = false ;
        }
    }
    
    /**
     * 再読み込み処理.
     * @exception Exception 例外.
     */
    public void reload() throws Exception {
        synchronized( sync ) {
            loadScript() ;
        }
    }
    
    /**
     * スクリプトキャッシュを有効にする.
     * この処理は、スクリプト呼び出しの前に必ず実行する必要があります.
     * @exception Exception 例外.
     */
    public void useScript() throws Exception {
        boolean reloadCheck = false ;
        long time = System.currentTimeMillis() ;
        try {
            synchronized( sync ) {
                if( reloadFlag ) {
                    reloadCheck = true ;
                    reloadFlag = false ;
                    if( libCode == null || libCode.equals( createLibMonitor() ) == false ) {
                        loadScript() ;
                        return ;
                    }
                }
                if( this.packageId != -1 && this.packageId != JsDef.getDefaultPackageId() ) {
                    loadScript() ;
                }
                else if( beforeTime + CHECK_TIME <= time ) {
                    if( reloadCheck == false && ( libCode == null || libCode.equals( createLibMonitor() ) == false ) ) {
                        loadScript() ;
                    }
                    else {
                        beforeTime = System.currentTimeMillis() ;
                    }
                }
            }
        } catch( Exception e ) {
            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 ;
    }
    
    /**
     * リロードフラグを設定.
     * @param reloadFlag [true]の場合、再読み込みチェックを行います.
     */
    public void setReloadFlag( boolean reloadFlag ) {
        synchronized( sync ) {
            this.reloadFlag = reloadFlag ;
        }
    }
    
    /**
     * ディレクトリ名を取得.
     * @return String ディレクトリ名が返されます.
     */
    public String getDirectory() {
        return MaachangDef.DIRECTORY_LIB ;
    }
    
    /**
     * スクリプト内容を読み込む.
     */
    private void loadScript() throws Exception {
        int cnt = 0 ;
        String beforeManagerCode = this.libCode ;
        int beforePackageId = this.packageId ;
        long beforeBeforeTime = this.beforeTime ;
        //ScriptContext ctx = null ;
        try {
            // モデル内容を読み込み.
            String[] libs = ScriptDef.getUseScript( READ_DIR ) ;
            int startPos = 0 ;
            // 対象となるモデル内容が存在する場合.
            if( libs != null && libs.length > 0 ) {
                StringBuilder buf = new StringBuilder() ;
                // 更新された内容を一時保存.
                int[] outLine = new int[ 1 ] ;
                this.libCode = ScriptDef.getScriptManagerCode( libs,READ_DIR ) ;
                this.packageId = JsDef.pushDefaultPackage( outLine,buf ) ;
                this.beforeTime = System.currentTimeMillis() ;
                
                startPos = outLine[ 0 ] ;
                outLine = null ;
                
                // モデル内容を読み込む.
                int len = libs.length ;
                if( this.srcScript == null ) {
                    this.srcScript = new SrcScript() ;
                }
                else {
                    this.srcScript.clear() ;
                }
                for( int i = 0 ; i < len ; i ++ ) {
                    String s = FileUtil.getFileByString( READ_DIR+"/"+libs[i],"UTF8" ) ;
                    if( s != null ) {
                        if( LOG.isDebugEnabled() ) {
                            LOG.debug( ">read[lib] - " + libs[i] ) ;
                        }
                        if( cnt != 0 ) {
                            buf.append( "\n" ) ;
                            startPos ++ ;
                        }
                        // ファイル名のコメントを付加.
                        buf.append( "/** libName << " ).append( libs[ i ] ).append( " >> **/\n" ) ;
                        startPos ++ ;
                        // ソースリストを追加.
                        int targetSize = SrcScript.getScriptList( s ) ;
                        SrcScriptList srcLst = new SrcScriptList( libs[ i ],startPos,targetSize ) ;
                        this.srcScript.setSrcScriptList( srcLst ) ;
                        buf.append( s ) ;
                        startPos += targetSize ;
                        cnt ++ ;
                    }
                }
                String script = buf.toString() ;
                buf = null ;
                this.srcScript.create( script ) ;
                
                // 処理実行.
                Bindings bindings = ExternalBindings.getInstance() ;
                // dummyデータ設定.
                bindings.put( ScriptDef.SCRIPT_BY_MODEL,new DummyBaseModel() ) ;
                
                // エンジン実行.
                ScriptEngine engine = ApiManager.getInstance().getScriptEngine() ;
                //ctx = new SimpleScriptContext() ;
                //ctx.setBindings( bindings,ScriptContext.ENGINE_SCOPE ) ;
                //engine.setContext( ctx ) ;
                //engine.put( ScriptEngine.FILENAME,SCRIPT_NAME ) ;
                ApiManager.setScriptName( SCRIPT_NAME ) ;
                CompiledScript cs = ( ( Compilable )engine ).compile( script ) ;
                //cs.eval( ctx ) ;
                cs.eval( bindings ) ;
                cs = null ;
                
                // APIマネージャに登録.
                Map<String,Object> libManager = ApiManager.getLocal() ;
                bindings.clear() ;
                ApiManager.getInstance().setLibMap( libManager ) ;
                if( LOG.isDebugEnabled() ) {
                    LOG.debug( "** lib-cacheを読み込み" ) ;
                }
            }
            // 対象となるライブラリ内容が存在しない場合は、内容をクリアする.
            if( cnt <= 0 ) {
                this.libCode = null ;
                this.packageId = -1 ;
                ApiManager.getInstance().setLibMap( null ) ;
                this.beforeTime = System.currentTimeMillis() ;
            }
            this.reloadFlag = false ;
        } catch( Exception e ) {
            // 例外の場合は、ロールバック処理.
            this.libCode = beforeManagerCode ;
            this.packageId = beforePackageId ;
            this.beforeTime = beforeBeforeTime ;
            throw e ;
        } finally {
            ApiManager.removeLocal() ;
        }
    }
    
    /**
     * ライブラリ管理コードを生成.
     */
    private String createLibMonitor()
        throws Exception {
        if( LOG.isDebugEnabled() ) {
            LOG.debug( "## read LibCode" ) ;
        }
        String[] libs = ScriptDef.getUseScript( READ_DIR ) ;
        return ScriptDef.getScriptManagerCode( libs,READ_DIR ) ;
    }
}

