package org.maachang.comet.httpd.engine;

import java.util.HashMap;

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.HttpdErrorDef;
import org.maachang.comet.httpd.HttpdExecutionManager;
import org.maachang.comet.httpd.HttpdRequest;
import org.maachang.comet.httpd.HttpdResponse;
import org.maachang.comet.httpd.HttpdStateException;
import org.maachang.comet.httpd.engine.comet.Comet;
import org.maachang.comet.httpd.engine.comet.CometManager;
import org.maachang.comet.httpd.engine.script.BaseModel;
import org.maachang.comet.httpd.engine.script.BaseModelImpl;
import org.maachang.comet.httpd.engine.script.ScriptDef;
import org.maachang.comet.httpd.engine.script.TargetScript;
import org.maachang.comet.httpd.engine.script.scripts.CometTriggerScript;
import org.maachang.comet.httpd.engine.script.scripts.TemplateScript;
import org.maachang.manager.GlobalManager;
import org.maachang.util.FileUtil;

/**
 * HTTPDスクリプト実行管理.
 * 
 * @version 2007/08/30
 * @author masahito suzuki
 * @since MaachangComet 1.00
 */
class HttpdScriptManager implements HttpdExecutionManager {
    
    /**
     * LOG.
     */
    private static final Log LOG = LogFactory.getLog( HttpdScriptManager.class ) ;
    
    /**
     * Applicationディレクトリ.
     */
    private String appDirectory = null ;
    
    /**
     * コンストラクタ.
     */
    private HttpdScriptManager() {
        
    }
    
    /**
     * コンストラクタ.
     * <BR><BR>
     * カレントディレクトリを指定してオブジェクトを生成します.
     * <BR>
     * @param currentDirectory 対象のカレントディレクトリを設定します.
     * @exception Exception 例外.
     */
    public HttpdScriptManager( String currentDirectory )
        throws Exception {
        this.appDirectory = new StringBuilder().
            append( ScriptDef.trimCurrentDirectory( currentDirectory ) ).
            append( MaachangDef.DIRECTORY_APPLICATION ).toString() ;
    }
    
    /**
     * デストラクタ.
     */
    protected void finalize() throws Exception {
        appDirectory = null ;
    }
    
    /**
     * 指定内容を取得.
     * <BR><BR>
     * 指定された内容を取得します.
     * <BR>
     * @param request 対象のリクエストを設定します.
     * @param timeout 対象のタイムアウト値を設定します.
     * @param count 対象のカウント値を設定します.
     * @return response 対象のレスポンスを設定します.
     * @exception Exception 例外.
     */
    public HttpdResponse get( HttpdRequest request )
        throws Exception {
        if( request == null ) {
            throw new HttpdStateException( HttpdErrorDef.HTTP11_400,
                "リクエストが不正です" ) ;
        }
        String path = trimPath( request.getUrlPath() ) ;
        if( path == null ) {
            throw new HttpdStateException( HttpdErrorDef.HTTP11_404,
                "指定パス[" + request.getUrlPath() + "]の内容は存在しません" ) ;
        }
        HttpdResponse ret = null ;
        try {
            // MTTML(Template)の場合.
            if( path.endsWith( ScriptDef.TEMPLATE_PLUS ) == true ) {
                TargetScript script = null ;
                ret = HttpdResponseInstance.createResponse( request,request.getUrlPath(),
                    request.getKeepAliveTimeout(),request.getKeepAliveCount() ) ;
                ret.setHttpCache( true ) ;
                ret.setHttpClose( false ) ;
                // フィルター処理を実行.
                if( executionFilter( ret,path,request ) == false ) {
                    // フィルタ実行で、レスポンス出力されていない場合は、
                    // 対象の処理を行う.
                    HashMap<String,Object> params = new HashMap<String,Object>() ;
                    ret.setCookieSession( request ) ;
                    ret.getHeader().addHeader( HttpdDef.VALUE_LAST_UPDATE,
                        HttpdTimestamp.getTimestamp( TemplateScript.getTime( path ) ) ) ;
                    params.put( ScriptDef.SCRIPT_BY_REQUEST,request ) ;
                    params.put( ScriptDef.SCRIPT_BY_RESPONSE,ret ) ;
                    script = new TargetScript( path,request ) ;
                    Object res = script.execution( params ) ;
                    resultError( path,res ) ;
                }
            }
            // それ以外の処理の場合.
            else {
                // 指定ファイルが存在するかチェック.
                String fullPath = appDirectory+path.substring( 1,path.length() ) ;
                if( FileUtil.isFileExists( fullPath+ScriptDef.SCRIPT_PLUS ) == false ) {
                    
                    // 先にコントローラ名として変換して再度チェック.
                    String controllerPath = path + ScriptDef.SCRIPT_BY_CONTROLLER ;
                    fullPath = appDirectory+controllerPath.substring( 1,controllerPath.length() ) ;
                    
                    // コントローラー情報が存在しない場合.
                    if( FileUtil.isFileExists( fullPath+ScriptDef.SCRIPT_PLUS ) == false ) {
                        // mhtml名を探す.
                        path = path + ScriptDef.SCRIPT_HTML_PLUS ;
                        fullPath = appDirectory+path.substring( 1,path.length() ) ;
                        // mhtmlファイルが存在しない場合.
                        if( FileUtil.isFileExists( fullPath ) == false ) {
                            throw new HttpdStateException( HttpdErrorDef.HTTP11_404,
                                "指定パス[" + request.getUrlPath() + "]の内容は存在しません" ) ;
                        }
                    }
                    // コントローラとして存在する場合は、コントローラとして処理する.
                    else {
                        path = controllerPath + ScriptDef.SCRIPT_PLUS ;
                        fullPath += ScriptDef.SCRIPT_PLUS ;
                    }
                }
                else {
                    path += ScriptDef.SCRIPT_PLUS ;
                    fullPath += ScriptDef.SCRIPT_PLUS ;
                }
                // 指定ファイルの読み込み権限が存在しない場合.
                if( FileUtil.isRead( fullPath ) == false ) {
                    throw new HttpdStateException( HttpdErrorDef.HTTP11_403,
                        "指定パス[" + request.getUrlPath() + "]は読み込み権限がありません" ) ;
                }
                long time = FileUtil.getLastTime( fullPath ) ;
                fullPath = null ;
                TargetScript script = null ;
                int type = ScriptDef.getScriptFileType( path ) ;
                switch( type ) {
                    // URL指定で実行可能なコメットオブジェクトタイプ.
                    case ScriptDef.SCRIPT_TYPE_BY_COMET : {
                        
                        // コメットトリガーが存在する場合は、その条件で処理.
                        String triggerPath = CometTriggerScript.getTriggerName( path ) ;
                        if( triggerPath != null ) {
                            // コメットトリガーを先に実行する.
                            ret = executionCometTriger( request,triggerPath,path ) ;
                        }
                        // コメットトリガーが実行されていない場合は、コメット登録.
                        if( ret == null ) {
                            // コメットの場合は、Body情報は使えない.
                            request.setBody( null ) ;
                            // コメットの場合は、コメットマネージャで処理.
                            CometManager man = ( CometManager )GlobalManager.getValue(
                                ServiceDef.MANAGER_BY_COMET ) ;
                            man.cometRequest( request ) ;
                        }
                    }
                    break ;
                    // URL指定で実行可能オブジェクトタイプ.
                    case ScriptDef.SCRIPT_TYPE_BY_AJAX :
                    case ScriptDef.SCRIPT_TYPE_BY_CONTROLLER :
                    case ScriptDef.SCRIPT_TYPE_BY_JSONP :
                    case ScriptDef.SCRIPT_TYPE_BY_RPC : {
                        
                        // コメット以外の場合は、スクリプト処理を実施.
                        ret = HttpdResponseInstance.createResponse( request,request.getUrlPath(),
                            request.getKeepAliveTimeout(),request.getKeepAliveCount() ) ;
                        ret.setHttpCache( true ) ;
                        ret.setHttpClose( false ) ;
                        
                        // filter実行を行い、OutputStream出力していない場合は、処理する.
                        if( executionFilter( ret,path,request ) == false ) {
                            if( type == ScriptDef.SCRIPT_TYPE_BY_AJAX ||
                                type == ScriptDef.SCRIPT_TYPE_BY_JSONP ||
                                type == ScriptDef.SCRIPT_TYPE_BY_RPC  ) {
                                // Ajax/Jsonp/AjaxRPCは、基本的にCacheをOFFにする.
                                ret.setHttpCache( true ) ;
                            }
                            HashMap<String,Object> params = new HashMap<String,Object>() ;
                            ret.setCookieSession( request ) ;
                            ret.getHeader().addHeader( HttpdDef.VALUE_LAST_UPDATE,
                                HttpdTimestamp.getTimestamp( time ) ) ;
                            params.put( ScriptDef.SCRIPT_BY_REQUEST,request ) ;
                            params.put( ScriptDef.SCRIPT_BY_RESPONSE,ret ) ;
                            script = new TargetScript( path,request ) ;
                            Object res = script.execution( params ) ;
                            resultError( path,res ) ;
                        }
                    }
                    break ;
                    // URL指定で実行不可能オブジェクトタイプ.
                    case ScriptDef.SCRIPT_TYPE_BY_COMET_TRIGGER :
                    case ScriptDef.SCRIPT_TYPE_BY_INNER :
                    case ScriptDef.SCRIPT_TYPE_BY_FILTER : {
                        // エラー403.
                        throw new HttpdStateException( HttpdErrorDef.HTTP11_403,
                            "指定パス[" + path + "]の内容はアクセスできません" ) ;
                    }
                    default : {
                        
                        // MHTMLファイルの場合.
                        if( path.endsWith( ScriptDef.SCRIPT_HTML_PLUS ) == true ) {
                            
                            // MHTMLファイルの実行.
                            ret = HttpdResponseInstance.createResponse( request,path,
                                request.getKeepAliveTimeout(),request.getKeepAliveCount() ) ;
                            ret.setHttpCache( true ) ;
                            ret.setHttpClose( false ) ;
                            
                            // filter実行を行い、OutputStream出力していない場合は、処理する.
                            if( executionFilter( ret,path,request ) == false ) {
                                HashMap<String,Object> params = new HashMap<String,Object>() ;
                                ret.setCookieSession( request ) ;
                                ret.getHeader().addHeader( HttpdDef.VALUE_LAST_UPDATE,
                                    HttpdTimestamp.getTimestamp( time ) ) ;
                                params.put( ScriptDef.SCRIPT_BY_REQUEST,request ) ;
                                params.put( ScriptDef.SCRIPT_BY_RESPONSE,ret ) ;
                                script = new TargetScript( path,request ) ;
                                Object res = script.execution( params ) ;
                                resultError( path,res ) ;
                            }
                        }
                        // その他では、404エラーとする.
                        else {
                            throw new HttpdStateException( HttpdErrorDef.HTTP11_404,
                                "指定パス[" + path + "]の内容は存在しません" ) ;
                        }
                    }
                    break ;
                }
            }
            return ret ;
        } catch( Exception e ) {
            throw e ;
        } catch( Error e ) {
            throw e ;
        }
    }
    
    /**
     * 指定パスのファイル日付を取得.
     * <BR><BR>
     * 指定パスのファイル日付を取得します.
     * <BR>
     * @parma path 対象のパスを設定します.
     * @return long ファイル日付が返されます.
     */
    public long getFileTime( String path ) {
        path = trimPath( path ) ;
        if( path == null ) {
            return -1L ;
        }
        long ret = -1L ;
        // MTTML(Template)名でチェック.
        if( path.endsWith( ScriptDef.TEMPLATE_PLUS ) == true ) {
            try {
                ret = TemplateScript.getTime( path ) ;
                if( ret != -1L ) {
                    return ret ;
                }
            } catch( Exception e ) {
            }
            return -1L ;
        }
        String fullPath = appDirectory+path.substring( 1,path.length() ) ;
        // 通常名でチェック.
        if( ( ret = FileUtil.getLastTime( fullPath+ScriptDef.SCRIPT_PLUS ) ) != -1L ) {
            return ret ;
        }
        // コントローラ名でチェック.
        if( ( ret = FileUtil.getLastTime( fullPath+ScriptDef.SCRIPT_BY_CONTROLLER+ScriptDef.SCRIPT_PLUS ) ) != -1L ) {
            return ret ;
        }
        // MHTML名でチェック.
        if( ( ret = FileUtil.getLastTime( fullPath+ScriptDef.SCRIPT_HTML_PLUS ) ) != -1L ) {
            return ret ;
        }
        return -1L ;
    }
    
    /**
     * 指定パスの内容が存在するかチェック.
     * <BR><BR>
     * 指定パスの内容が存在するかチェックします.
     * <BR>
     * @param path 対象のパスを設定します.
     * @return boolean [true]の場合、存在します.
     */
    public boolean isPath( String path ) {
        path = trimPath( path ) ;
        if( path == null ) {
            return false ;
        }
        // MTTML(Template)名でチェック.
        if( path.endsWith( ScriptDef.TEMPLATE_PLUS ) == true ) {
            try {
                if( TemplateScript.isTemplate( path ) == true ) {
                    return true ;
                }
            } catch( Exception e ) {
            }
            return false ;
        }
        String fullPath = appDirectory+path.substring( 1,path.length() ) ;
        // 通常名でチェック.
        if( FileUtil.isFileExists( fullPath+ScriptDef.SCRIPT_PLUS ) == true ) {
            return true ;
        }
        // コントローラ名でチェック.
        if( FileUtil.isFileExists( fullPath+ScriptDef.SCRIPT_BY_CONTROLLER+ScriptDef.SCRIPT_PLUS ) == true ) {
            return true ;
        }
        // MHTML名でチェック.
        if( FileUtil.isFileExists( fullPath+ScriptDef.SCRIPT_HTML_PLUS ) == true ) {
            return true ;
        }
        return false ;
    }
    
    /**
     * 指定パスのディレクトリが存在するかチェック.
     * <BR><BR>
     * 指定パスのディレクトリが存在するかチェックします.
     * <BR>
     * @param path 対象のパスを設定します.
     * @return boolean [true]の場合、ディレクトリとして存在します.
     */
    public boolean isDirectory( String path ) {
        if( path.startsWith( "/" ) == true ) {
            path = path.substring( 1,path.length() ) ;
        }
        path = appDirectory + path ;
        if( FileUtil.isDirExists( path ) == true ) {
            return true ;
        }
        return false ;
    }
    
    /**
     * 指定パスの内容が読み込み可能かチェック.
     * <BR><BR>
     * 指定パスの内容が読み込み可能かチェックします.
     * <BR>
     * @param path 対象のパスを設定します.
     * @return boolean [true]の場合、読み込み可能です.
     */
    public boolean isRead( String path ) {
        path = trimPath( path ) ;
        if( path == null ) {
            return false ;
        }
        // MTTML(Template)名でチェック.
        if( path.endsWith( ScriptDef.TEMPLATE_PLUS ) == true ) {
            try {
                if( TemplateScript.isTemplate( path ) == true ) {
                    return true ;
                }
            } catch( Exception e ) {
            }
            return false ;
        }
        String fullPath = appDirectory+path.substring( 1,path.length() ) ;
        // 通常名でチェック.
        if( FileUtil.isRead( fullPath+ScriptDef.SCRIPT_PLUS ) == true ) {
            return true ;
        }
        // コントローラ名でチェック.
        if( FileUtil.isRead( fullPath+ScriptDef.SCRIPT_BY_CONTROLLER+ScriptDef.SCRIPT_PLUS ) == true ) {
            return true ;
        }
        // MHTML名でチェック.
        if( FileUtil.isRead( fullPath+ScriptDef.SCRIPT_HTML_PLUS ) == true ) {
            return true ;
        }
        return false ;
    }
    
    /**
     * Path名を整形.
     */
    private static final String trimPath( String path ) {
        String lower = path.toLowerCase() ;
        if( path.endsWith( "/" ) == true ) {
            return path + "index" ;
        }
        //if( path.equals( "/" ) == true ) {
        //    return null ;
        //}
        if( lower.endsWith( ScriptDef.SCRIPT_PLUS ) == true ) {
            path = path.substring( 0,path.length()-ScriptDef.SCRIPT_PLUS.length() ) ;
        }
        else if( lower.endsWith( ScriptDef.SCRIPT_HTML_PLUS ) == true ) {
            path = path.substring( 0,path.length()-ScriptDef.SCRIPT_HTML_PLUS.length() ) ;
        }
        return path ;
    }
    
    /**
     * コメットトリガー正常終了条件.
     */
    private static final String SUCCESS_TRIGGER = "success" ;
    
    /**
     * コメットトリガー実行.
     */
    private HttpdResponse executionCometTriger( HttpdRequest request,String trigerPath,String path )
        throws Exception {
        HttpdResponse ret = null ;
        BaseModel baseModel = new BaseModelImpl() ;
        try {
            if( LOG.isDebugEnabled() ) {
                LOG.debug( "... execution Trigger[" + trigerPath + "]:" +
                    request.getQuery().getParam( "groupId" ) ) ;
            }
            Object res = Comet.oneSend( null,baseModel,request,
                trigerPath,request.getQuery().getParam( "groupId" ) ) ;
            if( baseModel.isCreate() ) {
                try {
                    baseModel.commit() ;
                } catch( Exception ee ) {
                }
            }
            if( res != null && res instanceof String && SUCCESS_TRIGGER.equals( res ) ) {
                if( LOG.isDebugEnabled() ) {
                    LOG.debug( "... exit[trigger] - comet:" +
                        request.getQuery().getParam( "groupId" ) ) ;
                }
                ret = HttpdResponseInstance.createResponse( request,request.getUrlPath(),
                    HttpdErrorDef.HTTP11_200,request.getKeepAliveTimeout(),
                    request.getKeepAliveCount() ) ;
                ret.setHttpCache( true ) ;
                ret.setHttpClose( false ) ;
                request.setBody( null ) ;
                Comet.oneSend( ret,baseModel,request,
                    path,request.getQuery().getParam( "groupId" ) ) ;
            }
            return ret ;
        } catch( Exception e ) {
            throw e ;
        } catch( Error e ) {
            throw e ;
        } finally {
            baseModel.clear() ;
        }
    }
    
    /**
     * Filter実行.
     */
    private final boolean executionFilter( HttpdResponse response,String path,HttpdRequest request )
        throws Exception {
        String filterPath = getExecutionFilterPath( path ) ;
        if( filterPath == null ) {
            return false ;
        }
        String fullPath = FileUtil.getFullPath(
            new StringBuilder().append( appDirectory ).append( filterPath ).toString() ) ;
        if( FileUtil.isFileExists( fullPath ) == true &&
            FileUtil.isRead( fullPath ) == true ) {
            HashMap<String,Object> params = new HashMap<String,Object>() ;
            response.setCookieSession( request ) ;
            response.getHeader().addHeader( HttpdDef.VALUE_LAST_UPDATE,
                HttpdTimestamp.getTimestamp( FileUtil.getLastTime( fullPath ) ) ) ;
            params.put( ScriptDef.SCRIPT_BY_REQUEST,request ) ;
            params.put( ScriptDef.SCRIPT_BY_RESPONSE,response ) ;
            TargetScript script = new TargetScript( filterPath,request ) ;
            Object res = script.execution( params ) ;
            if( response.isOutputStream() == true ||
                ScriptDef.SCRIPT_RESULT_REDIRECT.equals( res ) ||
                ScriptDef.SCRIPT_RESULT_FORWARD.equals( res ) ) {
                return true ;
            }
        }
        return false ;
    }
    
    /**
     * 実行可能なフィルタファイルパスを探す.
     */
    private final String getExecutionFilterPath( String path )
        throws Exception {
        for( ;; ) {
            path = path.substring( 0,path.length() - FileUtil.getFileName( path ).length() ) ;
            String filterPath = new StringBuilder().
                append( path ).
                append( ScriptDef.FILTER_NAME ).
                append( ScriptDef.SCRIPT_PLUS ).
                toString() ;
            String fullPath = FileUtil.getFullPath(
                new StringBuilder().append( appDirectory ).append( filterPath ).toString() ) ;
            if( FileUtil.isFileExists( fullPath ) == true && FileUtil.isRead( fullPath ) == true ) {
                return filterPath ;
            }
            if( path.length() <= 1 ) {
                return null ;
            }
            path = path.substring( 0,path.length() -1 ) ;
        }
    }
    
    /**
     * スクリプト戻り値でエラーが発生したかチェック.
     */
    private static final void resultError( String path,Object res )
        throws Exception {
        if( res != null && res instanceof String &&
            ( ( String )res ).startsWith( ScriptDef.SCRIPT_RESULT_ERROR ) ) {
            String s = ( String )res ;
            s = s.substring( ScriptDef.SCRIPT_RESULT_ERROR.length() ) ;
            int state = -1 ;
            try {
                state = Integer.parseInt( s ) ;
            } catch( Exception e ) {
                state = 500 ;
            }
            throw new HttpdStateException( state,
                "指定パス[" + path + "]の処理でエラーが発生しました" ) ;
        }
    }
    
}
