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

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.HashMap;

import javax.script.Bindings;
import javax.script.ScriptContext;

import org.maachang.comet.MaachangDef;
import org.maachang.comet.httpd.HttpdErrorDef;
import org.maachang.comet.httpd.HttpdParams;
import org.maachang.comet.httpd.HttpdStateException;
import org.maachang.util.Digest;
import org.maachang.util.FileUtil;
import org.maachang.util.StringUtil;

/**
 * スクリプト定義.
 * 
 * @version 2007/08/24
 * @author masahito suzuki
 * @since MaachangComet 1.00
 */
public class ScriptDef {
    
    private ScriptDef() {
    }
    
    /**
     * スクリプト内認識名 : LOG.
     */
    public static final String SCRIPT_BY_LOG = "log" ;
    
    /**
     * スクリプト実行タイプ.
     */
    public static final String SCRIPT_MODE = "__$type$script" ;
    
    /**
     * スクリプト詳細.
     */
    public static final String SCRIPT_DETAIL = "__$detail$script" ;
    
    /**
     * 現在読み込んでいるスクリプト名.
     */
    public static final String CURRENT_SCRIPT = "_$current$name" ;
    
    /**
     * スクリプト実行タイプ : Comet.
     */
    public static final String MODE_COMET = "comet" ;
    
    /**
     * スクリプト実行タイプ : Comet以外のScript.
     */
    public static final String MODE_NOT_COMET = "script" ;
    
    /**
     * スクリプト実行タイプ : コンソール実行スクリプト.
     */
    public static final String MODE_CONSOLE = "console" ;
    
    /**
     * スクリプト内認識名 : MaachangComet管理テーブル.
     */
    public static final String MANAGER = "_$$maachang_comet_manager" ;
    
    /**
     * スクリプト内認識名 : MODEL.
     */
    public static final String SCRIPT_BY_MODEL = "_$script$base_model" ;
    
    /**
     * スクリプト内認識名 : ENGINE.
     */
    public static final String SCRIPT_BY_ENGINE = "_$script$engine" ;
    
    /**
     * スクリプト内認識名 : CONTEXT.
     */
    public static final String SCRIPT_BY_CTX = "_$script$context" ;
    
    /**
     * スクリプト内認識名 : SESSION.
     */
    public static final String SCRIPT_BY_SESSION = "_$script$session" ;
    
    /**
     * スクリプト内認識名 : HEADER.
     */
    public static final String SCRIPT_BY_HEADER = "_$script$header" ;
    
    /**
     * スクリプト内認識名 : PATH.
     */
    public static final String SCRIPT_BY_PATH = "_$script$path" ;
    
    /**
     * スクリプト内認識名 : GroupId.
     */
    public static final String SCRIPT_BY_GROUP_ID = "_$script$group_id" ;
    
    /**
     * スクリプト内認識名 : コメットパラメータ.
     */
    public static final String SCRIPT_BY_COMET_ARGS = "_$script$comet_args" ;
    
    /**
     * スクリプト内認識名 : HttpdResponse.
     */
    public static final String SCRIPT_BY_RESPONSE = "_$script$response" ;
    
    /**
     * スクリプト内認識名 : HttpdRequest.
     */
    public static final String SCRIPT_BY_REQUEST = "_$script$request" ;
    
    /**
     * スクリプト内認識名 : Queryパラメータ.
     */
    public static final String SCRIPT_BY_QUERY = "_$script$query" ;
    
    /**
     * スクリプト内認識名 : スタートアップパラメータ.
     */
    public static final String SCRIPT_STARTUP = "_$startup$params" ;
    
    /**
     * 実行スクリプト : JavaScript.
     */
    public static final String ENGINE_JAVASCRIPT = "js" ;
    
    /**
     * スクリプト拡張子.
     */
    public static final String SCRIPT_PLUS = ".ms" ;
    
    /**
     * スクリプト埋め込みHTMLファイル拡張子.
     */
    public static final String SCRIPT_HTML_PLUS = ".mhtml" ;
    
    /**
     * スクリプト名に対する実行処理 : Comet.
     */
    public static final String SCRIPT_BY_COMET = "Comet" ;
    
    /**
     * スクリプト名に対する実行処理 : JSONP-Comet.
     */
    public static final String SCRIPT_BY_JCOMET = "JComet" ;
    
    /**
     * スクリプト名に対する実行処理 : Cometトリガー名.
     */
    public static final String SCRIPT_BY_TRIGGER = "Trigger" ;
    
    /**
     * スクリプト名に対する実行処理 : Ajax.
     */
    public static final String SCRIPT_BY_AJAX = "Ajax" ;
    
    /**
     * スクリプト名に対する実行処理 : Controller.
     */
    public static final String SCRIPT_BY_CONTROLLER = "Controller" ;
    
    /**
     * スクリプト名に対する実行処理 : Jsonp.
     */
    public static final String SCRIPT_BY_JSONP = "Jsonp" ;
    
    /**
     * スクリプト名に対する実行処理 : RPC.
     */
    public static final String SCRIPT_BY_RPC = "Rpc" ;
    
    /**
     * スクリプト名に対する実行処理 : Inner.
     */
    public static final String SCRIPT_BY_INNER = "Inner" ;
    
    /**
     * スクリプト名に対する実行処理 : Filter.
     */
    public static final String SCRIPT_BY_FILTER = "Filter" ;
    
    /**
     * スクリプト名に対する実行タイプ : なし.
     */
    public static final int SCRIPT_TYPE_BY_NOT = -1 ;
    
    /**
     * スクリプト名に対する実行タイプ : デフォルトスクリプト.
     */
    public static final int SCRIPT_TYPE_BY_DEFAULT = 0 ;
    
    /**
     * スクリプト名に対する実行タイプ : Comet.
     */
    public static final int SCRIPT_TYPE_BY_COMET = 1 ;
    
    /**
     * スクリプト名に対する実行タイプ : Ajax.
     */
    public static final int SCRIPT_TYPE_BY_AJAX = 2 ;
    
    /**
     * スクリプト名に対する実行タイプ : Controller.
     */
    public static final int SCRIPT_TYPE_BY_CONTROLLER = 3 ;
    
    /**
     * スクリプト名に対する実行タイプ : Jsonp.
     */
    public static final int SCRIPT_TYPE_BY_JSONP = 4 ;
    
    /**
     * スクリプト名に対する実行タイプ : RPC.
     */
    public static final int SCRIPT_TYPE_BY_RPC = 5 ;
    
    /**
     * スクリプト名に対する実行タイプ : スクリプト用HTML.
     */
    public static final int SCRIPT_TYPE_BY_HTML = 10 ;
    
    /**
     * スクリプト名に対する実行タイプ : Model.
     */
    public static final int SCRIPT_TYPE_BY_MODEL = 20 ;
    
    /**
     * スクリプト名に対する実行タイプ : 外部ライブラリ.
     */
    public static final int SCRIPT_TYPE_BY_LIB = 30 ;
    
    /**
     * スクリプト名に対する実行タイプ : CometTrigger.
     */
    public static final int SCRIPT_TYPE_BY_COMET_TRIGGER = 31 ;
    
    /**
     * スクリプト名に対する実行タイプ : Inner.
     */
    public static final int SCRIPT_TYPE_BY_INNER = 32 ;
    
    /**
     * スクリプト名に対する実行タイプ : Filter.
     */
    public static final int SCRIPT_TYPE_BY_FILTER = 98 ;
    
    /**
     * スクリプト名に対する実行タイプ : その他.
     */
    public static final int SCRIPT_TYPE_BY_ETC = 99 ;
    
    /**
     * フィルタファイル名.
     */
    public static final String FILTER_NAME = "filter" ;
    
    /**
     * スクリプト実行結果 : redirect.
     */
    public static final String SCRIPT_RESULT_REDIRECT = "redirect" ;
    
    /**
     * スクリプト実行結果 : forward.
     */
    public static final String SCRIPT_RESULT_FORWARD = "forward" ;
    
    /**
     * スクリプト実行結果 : error.
     */
    public static final String SCRIPT_RESULT_ERROR = "error" ;
    
    /**
     * スクリプト実行時に追加する条件.
     */
    public static final String ADD_SCRIPT = 
        "scriptMemorys = Bindings() ;\n" +
        "var params = jmap("+SCRIPT_BY_QUERY+");\n" +
        "var sessions = sessionMap();\n" ;
    
    /**
     * 指定カレントディレクトリを整形.
     * <BR><BR>
     * 指定カレントディレクトリを整形します.
     * <BR>
     * @param directory 対象のカレントディレクトリを設定します.
     * @return String 整形されたディレクトリ名が返されます.
     * @exception Exception 例外.
     */
    public static final String trimCurrentDirectory( String directory )
        throws Exception {
        if( directory == null || ( directory = directory.trim() ).length() <= 0 ) {
            throw new IllegalArgumentException( "対象ディレクトリ名は不正です" ) ;
        }
        directory = FileUtil.getFullPath( directory ) ;
        directory = StringUtil.changeString( directory,"\\","/" ) ;
        if( directory.startsWith( "/" ) == false ) {
            directory = "/" + directory ;
        }
        if( directory.endsWith( "/" ) == false ) {
            directory += "/" ;
        }
        return directory ;
    }
    
    /**
     * スクリプト組み込みHTMLを含む、スクリプトファイルタイプを取得.
     * <BR><BR>
     * スクリプト組み込みHTMLを含む、スクリプトファイルタイプが返されます.
     * <BR>
     * @param name 対象のスクリプト名を設定します.
     * @return int スクリプトファイルタイプが返されます.
     */
    public static final int getScriptByAllTyep( String name ) {
        if( name.endsWith( ScriptDef.SCRIPT_HTML_PLUS ) == true ) {
            return ScriptDef.SCRIPT_TYPE_BY_HTML ;
        }
        if( name.endsWith( ScriptDef.SCRIPT_PLUS ) == false ) {
            name += ScriptDef.SCRIPT_PLUS ;
        }
        return ScriptDef.getScriptFileType( name ) ;
    }
    
    /**
     * スクリプトファイルタイプを取得.
     * <BR><BR>
     * スクリプトファイルタイプが返されます.
     * <BR>
     * @param name 対象のスクリプト名を設定します.
     * @return int スクリプトファイルタイプが返されます.
     */
    public static final int getScriptFileType( String name ) {
        if( name == null || ( name = name.trim() ).length() <= 0 ) {
            return ScriptDef.SCRIPT_TYPE_BY_NOT ;
        }
        String check = null ;
        if( name.endsWith( ScriptDef.SCRIPT_PLUS ) == false ) {
            return ScriptDef.SCRIPT_TYPE_BY_NOT ;
        }
        else {
            check = name.substring( 0,name.length()-ScriptDef.SCRIPT_PLUS.length() ) ;
        }
        if( check.endsWith( ScriptDef.SCRIPT_BY_COMET ) ) {
            return ScriptDef.SCRIPT_TYPE_BY_COMET ;
        }
        if( check.endsWith( ScriptDef.SCRIPT_BY_JCOMET ) ) {
            return ScriptDef.SCRIPT_TYPE_BY_COMET ;
        }
        if( check.endsWith( ScriptDef.SCRIPT_BY_AJAX ) ) {
            return ScriptDef.SCRIPT_TYPE_BY_AJAX ;
        }
        if( check.endsWith( ScriptDef.SCRIPT_BY_CONTROLLER ) ) {
            return ScriptDef.SCRIPT_TYPE_BY_CONTROLLER ;
        }
        if( check.endsWith( ScriptDef.SCRIPT_BY_JSONP ) ) {
            return ScriptDef.SCRIPT_TYPE_BY_JSONP ;
        }
        if( check.endsWith( ScriptDef.SCRIPT_BY_RPC ) ) {
            return ScriptDef.SCRIPT_TYPE_BY_RPC ;
        }
        if( check.endsWith( ScriptDef.SCRIPT_BY_TRIGGER ) ) {
            return ScriptDef.SCRIPT_TYPE_BY_COMET_TRIGGER ;
        }
        if( check.endsWith( ScriptDef.SCRIPT_BY_INNER ) ) {
            return ScriptDef.SCRIPT_TYPE_BY_INNER ;
        }
        if( check.endsWith( ScriptDef.SCRIPT_BY_FILTER ) ) {
            return ScriptDef.SCRIPT_TYPE_BY_FILTER ;
        }
        return ScriptDef.SCRIPT_TYPE_BY_NOT ;
    }
    
    /**
     * スクリプトタイプから、スクリプト名を取得.
     * <BR><BR>
     * スクリプトタイプから、スクリプト名を取得します.
     * <BR>
     * @param type 対象のタイプを設定します.
     * @return String スクリプトタイプが返されます.
     */
    public static final String getTypeByString( int type ) {
        switch( type ) {
            case SCRIPT_TYPE_BY_NOT: return "etc" ;
            case SCRIPT_TYPE_BY_COMET: return "comet" ;
            case SCRIPT_TYPE_BY_AJAX: return "ajax" ;
            case SCRIPT_TYPE_BY_CONTROLLER: return "controller" ;
            case SCRIPT_TYPE_BY_JSONP: return "jsonp" ;
            case SCRIPT_TYPE_BY_RPC: return "rpc" ;
            case SCRIPT_TYPE_BY_HTML: return "html" ;
            case SCRIPT_TYPE_BY_MODEL: return "model" ;
            case SCRIPT_TYPE_BY_LIB: return "lib" ;
            case SCRIPT_TYPE_BY_COMET_TRIGGER: return "trigger" ;
            case SCRIPT_TYPE_BY_INNER: return "inner" ;
            case SCRIPT_TYPE_BY_ETC: return "etc" ;
        }
        return "etc" ;
    }
    
    /**
     * 拡張子が、スクリプト条件であるかチェック.
     * <BR><BR>
     * 拡張子が、スクリプト条件であるかチェックします.
     * <BR>
     * @param name チェック対象名を設定します.
     * @return boolean [true]の場合、スクリプト拡張子です.
     */
    public static final boolean isScript( String name ) {
        if( name == null || ( name = name.trim() ).length() <= 0 ) {
            return false ;
        }
        name = name.toLowerCase() ;
        if( name.endsWith( SCRIPT_PLUS ) == true ||
            name.endsWith( SCRIPT_HTML_PLUS ) == true ) {
            return true ;
        }
        return false ;
    }
    
    /**
     * アプリケーション名を整形.
     * <BR><BR>
     * アプリケーション名を整形します.
     * <BR>
     * @param path 対象のパス名を設定します.
     * @param name アプリケーション名を設定します.
     * @return String 整形されたアプリケーション名が返されます.
     * @exception Exception 例外.
     */
    public static final String convertApplicationName( String path,String name )
        throws Exception {
        if( path == null || ( path = path.trim() ).length() <= 0 ||
            name == null || ( name = name.trim() ).length() <= 0 ) {
            throw new HttpdStateException( HttpdErrorDef.HTTP11_404,"引数は不正です" ) ;
        }
        if( name.startsWith( "/" ) ) {
            return name ;
        }
        else if( name.startsWith( "\\" ) ) {
            return StringUtil.changeString( name,"\\","/" ) ;
        }
        if( path.startsWith( "\\" ) || path.startsWith( "/" ) ) {
            path = path.substring( 1,path.length() ) ;
        }
        if( path.endsWith( "/" ) == false && path.endsWith( "\\" ) == false ) {
            path += "/" ;
        }
        if( name.startsWith( "\\" ) || name.startsWith( "/" ) ) {
            name = name.substring( 1,name.length() ) ;
        }
        if( path.startsWith( "\\" ) || path.startsWith( "/" ) ) {
            path = "." + path ;
        }
        String fullPath = FileUtil.getFullPath( "." ) ;
        String params = "" ;
        int p = name.indexOf( "?" ) ;
        if( p != -1 ) {
            params = name.substring( p ) ;
            name = name.substring( 0,p ) ;
        }
        String file = FileUtil.getFileName( name ) ;
        String target = FileUtil.getFullPath( path+name ) ;
        if( fullPath.length() > target.length() ) {
            throw new HttpdStateException( HttpdErrorDef.HTTP11_404,"指定名["+name+"]の条件は不正です" ) ;
        }
        target = target.substring( fullPath.length(),target.length() ) ;
        fullPath = null ;
        if( target.endsWith( file ) == false ) {
            throw new HttpdStateException( HttpdErrorDef.HTTP11_404,"指定名["+name+"]の条件は不正です" ) ;
        }
        return StringUtil.changeString( target,"\\","/" ) + params ;
    }
    
    /**
     * 指定パスのスクリプトを取得.
     * <BR><BR>
     * 指定パスのスクリプトを取得します.
     * <BR>
     * @param path 対象のスクリプトパスを設定します.
     * @return String スクリプト内容が返されます.
     * @exception Exception 例外.
     */
    public static String getScriptByResource( String path )
        throws Exception {
        return getScriptByResource( path,null,null ) ;
    }
    
    /**
     * 指定パスのスクリプトを取得.
     * <BR><BR>
     * 指定パスのスクリプトを取得します.
     * <BR>
     * @param path 対象のスクリプトパスを設定します.
     * @param src 置き換え元の文字列を設定します.
     * @param dest 置き換え先の文字列を設定します.
     * @return String スクリプト内容が返されます.
     * @exception Exception 例外.
     */
    public static String getScriptByResource( String path,String src,String dest )
        throws Exception {
        ClassLoader cl = Thread.currentThread().getContextClassLoader() ;
        BufferedReader br = null ;
        try {
            StringBuilder buf = new StringBuilder() ;
            br = new BufferedReader( new InputStreamReader(
                cl.getResourceAsStream( path ),"UTF8" ) ) ;
                //readResourceAsStream( path ),"UTF8" ) ) ;
            if( src != null && dest != null ) {
                for( ;; ) {
                    String s = br.readLine() ;
                    if( s == null ) {
                        break ;
                    }
                    s = StringUtil.changeString( s,src,dest ) ;
                    buf.append( s ) ;
                    buf.append( "\r\n" ) ;
                }
            }
            else {
                for( ;; ) {
                    String s = br.readLine() ;
                    if( s == null ) {
                        break ;
                    }
                    buf.append( s ) ;
                    buf.append( "\r\n" ) ;
                }
            }
            br.close() ;
            br = null ;
            return buf.toString() ;
        } catch( Exception e ) {
            throw e ;
        } finally {
            if( br != null ) {
                try {
                    br.close() ;
                } catch( Exception e ) {
                }
            }
        }
    }
    //private static final InputStream readResourceAsStream( String path )
    //    throws Exception {
    //    return ScriptDef.class.getResourceAsStream( path ) ;
    //}
    
    /**
     * 指定スクリプト一覧から管理コードを取得.
     * <BR><BR>
     * 指定スクリプト一覧から管理コードを取得します.
     * <BR>
     * @param scripts 対象のスクリプト一覧を設定します.
     * @param currentDirectory 対象のディレクトリ名を設定します.
     * @return String ディレクトリ管理コードが返されます.
     * @exception Exception 例外.
     */
    public static final String getScriptManagerCode( String[] scripts,String currentDirectory )
        throws Exception {
        if( scripts != null && scripts.length > 0 ) {
            StringBuilder buf = new StringBuilder() ;
            int len = scripts.length ;
            for( int i = 0 ; i < len ; i ++ ) {
                if( i != 0 ) {
                    buf.append( ":" ) ;
                }
                buf.append( scripts[ i ] ) ;
                long time = FileUtil.getLastTime( currentDirectory+scripts[ i ] ) ;
                buf.append( "-" ).append( time ) ;
            }
            byte[] bin = buf.toString().getBytes( "UTF8" ) ;
            buf = null ;
            return Digest.convert( "SHA1",bin ) ;
        }
        return null ;
    }
    
    /**
     * 指定ディレクトリ以下の有効スクリプト一覧を取得.
     * <BR><BR>
     * 指定ディレクトリ以下の有効スクリプト一覧を取得します.
     * <BR>
     * @param name 対象のディレクトリ名を設定します.
     * @return String[] 有効スクリプト名一覧が返されます.
     */
    public static final String[] getUseScript( String directory ) {
        String[] names = FileUtil.getFileList( directory ) ;
        if( names != null && names.length > 0 ) {
            int cnt = 0 ;
            int len = names.length ;
            for( int i = 0 ; i < len ; i ++ ) {
                if( names[ i ] != null && names[ i ].endsWith( ScriptDef.SCRIPT_PLUS ) ) {
                    cnt ++ ;
                }
            }
            if( cnt > 0 ) {
                String[] ret = new String[ cnt ] ;
                cnt = 0 ;
                for( int i = 0 ; i < len ; i ++ ) {
                    if( names[ i ] != null && names[ i ].endsWith( ScriptDef.SCRIPT_PLUS ) ) {
                        ret[ cnt ] = names[ i ].substring( 0,names[ i ].length()-ScriptDef.SCRIPT_PLUS.length() ) ;
                        cnt ++ ;
                    }
                }
                return ret ;
            }
        }
        return null ;
    }
    
    /**
     * ディレクトリ情報をScriptに設定.
     * <BR><BR>
     * ディレクトリ情報をScriptに設定します.
     * <BR>
     * @param ctx 設定対象のオブジェクトを設定します.
     * @exception 例外.
     */
    public static final void setDirectoryByBindings( ScriptContext ctx )
        throws Exception {
        if( ctx == null ) {
            return ;
        }
        setDirectoryByBindings( ctx.getBindings( ScriptContext.ENGINE_SCOPE ) ) ;
    }
    
    /**
     * ディレクトリ情報をScriptに設定.
     * <BR><BR>
     * ディレクトリ情報をScriptに設定します.
     * <BR>
     * @param bindings 設定対象のオブジェクトを設定します.
     * @exception 例外.
     */
    public static final void setDirectoryByBindings( Bindings bindings )
        throws Exception {
        if( bindings == null ) {
            return ;
        }
        String current = trimCurrentDirectory( "." ) ;
        bindings.put( "_$path$currentPath",current ) ;
        bindings.put( "_$path$scriptPath",current+MaachangDef.DIRECTORY_APPLICATION ) ;
        bindings.put( "_$path$modelPath",current+MaachangDef.DIRECTORY_MODEL ) ;
    }
    
    /**
     * リクエストパラメータを生成.
     * <BR><BR>
     * リクエストパラメータを生成します.
     * <BR>
     * @param params 対象のHTTPクエリを設定します.
     * @return HashMap<String,Object> 対象のHTTPクエリが返されます.
     */
    public static final HashMap<String,Object> getQuery( HttpdParams params ) {
        if( params == null ) {
            return new HashMap<String,Object>() ;
        }
        return params.getPrivateMap() ;
        /*HashMap<String,Object> ret = new HashMap<String,Object>() ;
        String[] keys = params.getKeys() ;
        if( keys != null && keys.length > 0 ) {
            int len = keys.length ;
            for( int i = 0 ; i < len ; i ++ ) {
                int lenJ = params.size( keys[ i ] ) ;
                if( lenJ == 1 ) {
                    Object o = null ;
                    if( params.getParamType( keys[ i ] ) == true ) {
                        o = params.getParamByBinary( keys[ i ] ) ;
                    }
                    else {
                        o = params.getParam( keys[ i ] ) ;
                    }
                    if( o == null ) {
                        o = "" ;
                    }
                    ret.put( keys[ i ],o ) ;
                }
                else {
                    ArrayList<Object> lst = new ArrayList<Object>() ;
                    for( int j = 0 ; j < lenJ ; j ++ ) {
                        Object o = null ;
                        if( params.getParamType( keys[ i ],j ) == true ) {
                            o = params.getParamByBinary( keys[ i ],j ) ;
                        }
                        else {
                            o = params.getParam( keys[ i ],j ) ;
                        }
                        if( o == null ) {
                            o = "" ;
                        }
                        lst.add( o ) ;
                    }
                    ret.put( keys[ i ],lst ) ;
                }
            }
        }
        return ret ;*/
    }
}
