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

import org.maachang.comet.httpd.HttpdErrorDef;
import org.maachang.comet.httpd.HttpdStateException;
import org.maachang.comet.httpd.engine.script.ExecutionScript;
import org.maachang.comet.httpd.engine.script.Script;
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.comet.httpd.engine.script.taglib.AnalysisTagLib;
import org.maachang.comet.mdbm.SingleMDbm;
import org.maachang.dbm.MDbm;
import org.maachang.util.ConvertParam;

/**
 * テンプレート用スクリプト情報.
 * 
 * @version 2008/07/13
 * @author masahito suzuki
 * @since MaachangComet 1.1F
 */
public class TemplateScript implements Script {
    
    /**
     * MDBM名前ヘッダ.
     */
    protected static final String MDBM_HEADER = "@mc@template:" ;
    
    /**
     * MDBM更新ヘッダ.
     */
    protected static final String MDBM_UPDATE_HEADER = "@mc@template@update:" ;
    
    /**
     * 対象パス.
     */
    protected String path = null ;
    
    /**
     * カレントディレクトリ.
     */
    protected String currentDir = null ;
    
    /**
     * MDBMアクセス名.
     */
    protected byte[] baseName = null ;
    protected byte[] updateName = null ;
    
    /**
     * ファイル更新時間.
     */
    protected long updateTime = -1L ;
    
    /**
     * スクリプト内容.
     */
    private SrcScript srcScript = null ;
    
    /**
     * 実行スクリプトオブジェクト.
     */
    protected ExecutionScriptImpl executionScript = null ;
    
    /**
     * デフォルトパッケージ更新ID.
     */
    private int packageId = -1 ;
    
    /**
     * コンストラクタ.
     */
    private TemplateScript() {
    }
    
    /**
     * コンストラクタ.
     * <BR><BR>
     * 条件を指定してオブジェクトを生成します.
     * <BR>
     * @param path 対象のパスを設定します.
     * @exception Exception 例外.
     */
    public TemplateScript( String path )
        throws Exception {
        if( path == null || ( path = path.trim() ).length() <= 0 ) {
            throw new HttpdStateException( HttpdErrorDef.HTTP11_500,
                "mhtmlの指定パスは不正です" ) ;
        }
        byte[] baseName = getBaseKey( path ) ;
        MDbm mdbm = SingleMDbm.getInstance().getMDbm() ;
        if( mdbm.containsKey( baseName ) == false ) {
            throw new HttpdStateException( HttpdErrorDef.HTTP11_500,
                "対象templateファイル[" + path + "]は存在しません" ) ;
        }
        this.path = path ;
        this.baseName = baseName ;
        this.updateName = getUpdateKey( path ) ;
        this.currentDir = "./" ;
    }
    
    /**
     * 情報クリア.
     */
    protected void clear() {
        updateTime = -1L ;
        srcScript = null ;
        executionScript = null ;
    }
    
    /**
     * 基本スクリプトを取得.
     * <BR><BR>
     * 基本スクリプトを取得します.
     * <BR>
     * @return ExecutionScript 基本スクリプトが返されます.
     * @exception Exception 例外.
     */
    public synchronized ExecutionScript getScript() throws Exception {
        MDbm mdbm = SingleMDbm.getInstance().getMDbm() ;
        if( mdbm.containsKey( baseName ) == false ) {
            clear() ;
            throw new HttpdStateException( HttpdErrorDef.HTTP11_500,
                "対象mhtmlファイル[" + path + "]は存在しません" ) ;
        }
        else if( executionScript == null ||
            updateCheck() == true ||
            this.packageId != JsDef.getDefaultPackageId() ) {
            readScript() ;
        }
        return executionScript ;
    }
    
    /**
     * スクリプトパスを取得.
     * <BR><BR>
     * スクリプトパスを取得します.
     * <BR>
     * @return String スクリプトパスが返されます.
     */
    public synchronized String getPath() {
        return this.path ;
    }
    
    /**
     * スクリプトファイル名を取得.
     * <BR><BR>
     * スクリプトファイル名を取得します.
     * <BR>
     * @return String スクリプトファイル名が返されます.
     */
    public synchronized String getScriptName() {
        return this.path ;
    }
    
    /**
     * スクリプトタイプを取得.
     * <BR><BR>
     * スクリプトタイプを取得します.
     * <BR>
     * @return int スクリプトタイプが返されます.
     */
    public int getScriptType() {
        return ScriptManager.SCRIPT_TYPE_BY_HTML ;
    }
    
    /**
     * スクリプト内容を取得.
     * <BR><BR>
     * スクリプト内容を取得します.
     * <BR>
     * @return SrcScript スクリプト内容が返されます.
     */
    public synchronized SrcScript getSrcScript() {
        return srcScript ;
    }
    
    /**
     * スクリプトが利用可能かチェック.
     * <BR><BR>
     * スクリプトが利用可能かチェックします.
     * <BR>
     * @return boolean [true]の場合、利用可能です.
     */
    public synchronized boolean isScript() {
        return ( executionScript == null ) ? false : true ;
    }
    
    /**
     * スクリプト内容を読み込む.
     */
    protected void readScript()
        throws Exception {
        MDbm mdbm = SingleMDbm.getInstance().getMDbm() ;
        byte[] b = mdbm.get( baseName ) ;
        if( b == null ) {
            clear() ;
            return ;
        }
        String script = new String( b,CHARSET ) ;
        b = null ;
        script = ReadScriptPlus.directInclude( currentDir,script ) ;
        script = AnalysisTagLib.analysis( script ) ;
        script = TemplateScriptAnalysis.analysis( script ) ;
        // 出力内容は、まとめて返信するように変更.
        StringBuilder buf = new StringBuilder() ;
        buf.append( "var ___$_$_$rs$buf$_$ = new StrBuf() ;\n" ).
            append( "httpPrint = function( str ) { if( useString( str ) ) { " ).append( "___$_$_$rs$buf$_$.ad( str ); } } ;\n" ).
            append( script ).append( "\n" ).
            append( "httpPrintln( ___$_$_$rs$buf$_$.ts() ) ;\n ___$_$_$rs$buf$_$=null;\n" ) ;
        script = buf.toString() ;
        buf = null ;
        buf = new StringBuilder() ;
        this.packageId = JsDef.pushDefaultPackage( buf ) ;
        ReadScriptPlus.addMHTML( buf,script,path ) ;
        script = buf.toString() ;
        buf = null ;
        
        script = ReadScriptPlus.convert( false,script ) ;
        this.srcScript = new SrcScript( script ) ;
        this.executionScript = new ExecutionScriptImpl(
            ScriptDef.ENGINE_JAVASCRIPT,getScriptType(),
            this.getPath(),script ) ;
        script = null ;
        this.updateTime = getUpdate() ;
    }
    
    /**
     * 更新チェック.
     */
    private boolean updateCheck() throws Exception {
        long tm = getUpdate() ;
        if( tm != updateTime ) {
            return true ;
        }
        return false ;
    }
    
    /**
     * 更新情報を取得.
     */
    private long getUpdate() throws Exception {
        MDbm mdbm = SingleMDbm.getInstance().getMDbm() ;
        byte[] b = mdbm.get( updateName ) ;
        if( b == null || b.length != 8 ) {
            throw new IllegalArgumentException( "更新確認データは不正です" ) ;
        }
        return ConvertParam.convertLong( 0,b ) ;
    }
    
    /**
     * 指定パス上のテンプレート内容が存在するかチェック.
     * @param path 対象のパスを設定します.
     * @return boolean [true]の場合、存在します.
     * @exception Exception 例外.
     */
    public static final boolean isTemplate( String path ) throws Exception {
        MDbm mdbm = SingleMDbm.getInstance().getMDbm() ;
        return mdbm.containsKey( getBaseKey( path ) ) ;
    }
    
    /**
     * テンプレート更新時間を取得.
     * @param path 対象のパスを設定します.
     * @return long 更新時間が返されます.
     * @exception Exception 例外.
     */
    public static final long getTime( String path ) throws Exception {
        MDbm mdbm = SingleMDbm.getInstance().getMDbm() ;
        byte[] b = mdbm.get( getUpdateKey( path ) ) ;
        if( b == null || b.length != 8 ) {
            return -1 ;
        }
        return ConvertParam.convertLong( 0,b ) ;
    }
    
    /**
     * 基本キー名変換.
     * @param key 対象のキー名を設定します.
     * @return byte[] 変換されたバイナリが返されます.
     * @exception Exception 例外.
     */
    public static final byte[] getBaseKey( String key ) throws Exception {
        return new StringBuilder().append( MDBM_HEADER ).
            append( key ).toString().getBytes( CHARSET ) ;
    }
    
    /**
     * 更新キー名変換.
     * @param key 対象のキー名を設定します.
     * @return byte[] 変換されたバイナリが返されます.
     * @exception Exception 例外.
     */
    public static final byte[] getUpdateKey( String key ) throws Exception {
        return new StringBuilder().append( MDBM_UPDATE_HEADER ).
            append( key ).toString().getBytes( CHARSET ) ;
    }
}
