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

import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

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.comet.httpd.engine.script.scripts.ScriptManager;
import org.maachang.jsr.script.api.ApiManager;
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" ;
    
    /**
     * スクリプト内認識名 : キャッシュDBパラメータ.
     */
    public static final String SCRIPT_CACHE_DB = "_$cachedb$params" ;
    
    /**
     * スクリプト内認識名 : 共有メモリ.
     */
    public static final String SCRIPT_SHARED = "_$shared$params" ;
    
    /**
     * 最終読み込みＭＨＴＭＬファイル名.
     */
    public static final String SCRIPT_LAST_MHTML = "_$script$lastMHTML" ;
    
    /**
     * 最終読み込みコントローラ名.
     */
    public static final String SCRIPT_LAST_CONTROLLER = "_$script$controllerName" ;
    
    /**
     * 実行スクリプト : JavaScript.
     */
    public static final String ENGINE_JAVASCRIPT = ApiManager.ENGINE_NAME ;
    
    /**
     * スクリプト実行結果 : 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 EXIT_FOODER =
        "exit() ;\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 ;
    }
    
    /**
     * アプリケーション名を整形.
     * <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( ":" ) ;
                }
                if( scripts[ i ].startsWith( "/" ) || scripts[ i ].startsWith( "\\" ) ) {
                    scripts[ i ] = scripts[ i ].substring( 1 ) ;
                }
                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 dir 対象のディレクトリ名を設定します.
     * @return String[] 有効スクリプト名一覧が返されます.
     * @exception Exception 例外.
     */
    public static final String[] getUseScript( String dir )
        throws Exception {
        ArrayList<String> lst = new ArrayList<String>() ;
        list( lst,dir,dir ) ;
        if( lst.size() > 0 ) {
            int len = lst.size() ;
            String[] ret = new String[ len ] ;
            for( int i = 0 ; i < len ; i ++ ) {
                ret[ i ] = lst.get( i ) ;
            }
            lst = null ;
            Arrays.sort( ret ) ;
            return ret ;
        }
        return null ;
    }
    
    /**
     * 指定ディレクトリ以下のディレクトリ名を取得.
     */
    private static final void list( ArrayList<String> out,String base,String dir )
        throws Exception {
        File fp = new File( dir ) ;
        String[] names = fp.list() ;
        fp = null ;
        if( names != null && names.length > 0 ) {
            int len = names.length ;
            for( int i = 0 ; i < len ; i ++ ) {
                if( names[ i ] == null || ( names[ i ] = names[ i ].trim() ).length() <= 0 ) {
                    continue ;
                }
                String name = new StringBuilder().append( dir ).
                    append( FileUtil.FILE_SPACE ).append( names[ i ] ).toString() ;
                if( FileUtil.isDirExists( name ) ) {
                    list( out,base,name ) ;
                }
                else if( name.toLowerCase().endsWith( ScriptManager.SCRIPT_PLUS ) ) {
                    name = name.substring( base.length(),name.length() ) ;
                    if( name.indexOf( "\\" ) != -1 ) {
                        name = StringUtil.changeString( name,"\\","/" ) ;
                    }
                    if( name.startsWith( "/" ) ) {
                        name.substring( 1,name.length() ) ;
                    }
                    out.add( name ) ;
                }
            }
        }
    }
    
    /**
     * ディレクトリ情報を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 Map<String,Object> getQuery( HttpdParams params ) {
        if( params == null ) {
            return new HashMap<String,Object>() ;
        }
        return params.getPrivateMap() ;
    }
}
