package org.maachang.proxy.engine.error;

import org.maachang.proxy.engine.VersionDef;
import org.maachang.proxy.engine.mobile.MobileInfo;
import org.maachang.proxy.engine.net.RequestInfo;
import org.maachang.proxy.engine.net.ResponseInfo;
import org.maachang.proxy.engine.net.http.HttpdTimestamp;
import org.maachang.util.FileUtil;
import org.maachang.util.GZIPBinary;
import org.maachang.util.StringUtil;

/**
 * HTTPDエラーページ管理オブジェクト.
 * 
 * @version 2007/08/29
 * @author masahito suzuki
 * @since MaachangProxy 1.00
 */
public class ErrorPageManager {
    
    /**
     * デフォルトエラーページテンプレートファイル.
     */
    private static final String DEFAULT_TEMPLATE =
        "org/maachang/proxy/engine/error/default.error" ;
    
    /**
     * エラーページ名.
     */
    private static final String ERROR_PAGE = "error.html" ;
    
    /**
     * エラーディレクトリ名.
     */
    private static final String ERROR_DIR = "error/" ;
    
    /**
     * 携帯エラーページヘッダ名.
     */
    private static final String MOBILE_ERROR_HEADER = "m." ;
    
    /**
     * アプリケーションディレクトリ.
     */
    private String errorPageDir = null ;
    
    /**
     * デフォルトエラーページテンプレート.
     */
    private String defaultTemplate = null ;
    
    /**
     * コンストラクタ.
     */
    private ErrorPageManager() {
        
    }
    
    /**
     * デストラクタ.
     */
    protected void finalize() throws Exception {
        errorPageDir = null ;
        defaultTemplate = null ;
    }
    
    private static final ErrorPageManager SNGL = new ErrorPageManager() ;
    
    /**
     * オブジェクトを取得.
     * <BR>
     * @return ErrorPageManager オブジェクトが返されます.
     */
    public static final ErrorPageManager getInstance() {
        return SNGL ;
    }
    
    /**
     * 情報生成.
     * <BR><BR>
     * 条件を指定してオブジェクトを生成します.
     * <BR>
     * @param directory 対象のカレントディレクトリを設定します.
     * @exception Exception 例外.
     */
    public void create( String directory )
        throws Exception {
        if( directory == null || ( directory = directory.trim() ).length() <= 0 ) {
            throw new IllegalArgumentException( "カレントディレクトリは不正です" ) ;
        }
        directory = StringUtil.changeString( directory,"\\","/" ) ;
        if( directory.startsWith( "/" ) == false ) {
            directory = "/" + directory ;
        }
        if( directory.endsWith( "/" ) == false ) {
            directory += "/" ;
        }
        this.errorPageDir = directory + ERROR_DIR ;
        this.defaultTemplate = FileUtil.getScriptByResource( DEFAULT_TEMPLATE,null ) ;
    }
    
    /**
     * Proxy先のエラー情報に合った、表示エラーページが存在するかチェック.
     * @param res 対象のレスポンス情報を設定します.
     * @param req 対象のリクエスト情報を設定します.
     * @param info 対象の携帯情報オブジェクトを設定します.
     * @exception Exception 例外.
     */
    public void proxyErrorResponse( ResponseInfo res,RequestInfo req,MobileInfo info )
        throws Exception {
        int state = Integer.parseInt( res.getState() ) ;
        if( state < 400 ) {
            return ;
        }
        String template = getErrorTemplate( info,res.getState(),req.getUrl() ) ;
        if( template == null || template.length() <= 0 ) {
            return ;
        }
        String body = ErrorPageTemplate.createErrorPage(
            template,String.valueOf( state ),ErrorDef.convertErrorMessage( state ),null,req.getUrl() ) ;
        if( body != null ) {
            if( info.getMobileData() != null ) {
                res.putHeader( "Content-Type","text/html; charset=SHIFT_JIS" ) ;
                res.setBody( body.getBytes( "SHIFT_JIS" ) ) ;
                res.removeHeader( "Content-Encoding" ) ;
                res.setState( "200" ) ;
            }
            else {
                res.putHeader( "Content-Type","text/html; charset=UTF8" ) ;
                res.setBody( body.getBytes( "UTF8" ) ) ;
                if( res.getHeader( "Content-Encoding",0 ) != null &&
                    res.getHeader( "Content-Encoding",0 ).indexOf( "zip" ) >= 0 ) {
                    res.setBody( GZIPBinary.getInstance().convertBinaryByGZIP( res.getBody() ) ) ;
                }
            }
            res.putHeader( "Content-Length",String.valueOf( res.getBody().length ) ) ;
        }
    }
    
    /**
     * レスポンスオブジェクトに対して、エラー情報を付加.
     * @param info 対象の携帯情報オブジェクトを設定します.
     * @param host 対象のHost名を設定します.
     * @param code エラーコードを設定します.
     * @param message 対象のエラーメッセージを設定します.
     * @param detail 対象のエラー詳細を設定します.
     * @return ResponseInfo レスポンス情報が返されます.
     * @exception Exception 例外.
     */
    public ResponseInfo getErrorResponse( MobileInfo info,String host,int code,String message,String detail )
        throws Exception  {
        if( message == null || message.length() <= 0 ) {
            message = ErrorDef.convertErrorMessage( code ) ;
        }
        ResponseInfo ret = new ResponseInfo() ;
        ret.setState( String.valueOf( code ) ) ;
        ret.setStateMessage( message ) ;
        ret.setVersion( "1.1" ) ;
        ret.putHeader( "Server",VersionDef.getPrintServerName() ) ;
        ret.putHeader( "Date",HttpdTimestamp.getNowTimestamp() ) ;
        ret.putHeader( "Expires",HttpdTimestamp.getTimestamp( 0L ) );
        ret.putHeader( "Pragma","no-cache" ) ;
        ret.putHeader( "Last-Modified",HttpdTimestamp.getNowTimestamp() ) ;
        ret.putHeader( "Cache-Control","private" ) ;
        ret.addHeader( "Cache-Control","no-cache" ) ;
        ret.addHeader( "Cache-Control","no-store" ) ;
        ret.addHeader( "Cache-Control","max-age=0" ) ;
        if( info.getMobileData() != null ) {
            ret.putHeader( "Content-Type","text/html; charset=SHIFT_JIS" ) ;
        }
        else {
            ret.putHeader( "Content-Type","text/html; charset=UTF8" ) ;
        }
        String res = getErrorPage( info,host,code,message,detail ) ;
        byte[] body = null ;
        if( info.getMobileData() != null ) {
            body = res.getBytes( "SHIFT_JIS" ) ;
            ret.setState( "200" ) ;
        }
        else {
            body = res.getBytes( "UTF8" ) ;
        }
        res = null ;
        ret.putHeader( "Content-Length",String.valueOf( body.length ) ) ;
        ret.setBody( body ) ;
        return ret ;
    }
    
    /**
     * エラー情報を取得.
     * <BR><BR>
     * エラー情報を取得します.
     * <BR>
     * @param info 対象の携帯情報オブジェクトを設定します.
     * @param host 対象のホスト名を設定します.
     * @param code 対象のエラーコードを設定します.
     * @param message 対象のエラーメッセージを設定します.
     * @param detail 対象のエラー詳細を設定します.
     * @return String エラー情報が格納された内容が返されます.
     * @exception Exception 例外.
     */
    public String getErrorPage( MobileInfo info,String host,int code,String message,String detail )
        throws Exception {
        if( host == null || host.length() <= 0 ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        if( message == null || message.length() <= 0 ) {
            message = ErrorDef.convertErrorMessage( code ) ;
        }
        String template = getErrorTemplate( info,String.valueOf( code ),host ) ;
        if( template == null || template.length() <= 0 ) {
            template = defaultTemplate ;
        }
        return ErrorPageTemplate.createErrorPage(
            template,String.valueOf( code ),message,detail,host ) ;
    }
    
    /**
     * エラー情報を取得.
     * <BR><BR>
     * エラー情報を取得します.
     * <BR>
     * @param host 対象のHost名を設定します.
     * @param code 対象のエラーコードを設定します.
     * @param message 対象のエラーメッセージを設定します.
     * @param detail 対象のエラー詳細を設定します.
     * @return String エラー情報が格納された内容が返されます.
     */
    public String getDefaultErrorPage( String host,int code,String message,String detail ) {
        try {
            return ErrorPageTemplate.createErrorPage(
                defaultTemplate,String.valueOf( code ),message,detail,host ) ;
        } catch( Exception e ) {
        }
        return null ;
    }
    
    /**
     * エラーページを取得.
     */
    private String getErrorTemplate( MobileInfo info,String state,String url )
        throws Exception {
        String path = url ;
        int p = path.indexOf( "/" ) ;
        if( p >= 0 ) {
            path = path.substring( p+1 ) ;
        }
        else {
            path = "" ;
        }
        String codePage = null ;
        // 携帯からアクセスされている場合.
        if( info.getMobileData() != null ) {
            codePage = new StringBuilder().append( MOBILE_ERROR_HEADER ).
                append( state ).append( ".html" ).toString() ;
        }
        // PCからアクセスされている場合.
        else {
            codePage = new StringBuilder().append( state ).append( ".html" ).toString() ;
        }
        String errorPage = null ;
        // 携帯からアクセスされている場合.
        if( info.getMobileData() != null ) {
            errorPage = new StringBuilder().append( MOBILE_ERROR_HEADER ).
                append( ERROR_PAGE ).append( ".html" ).toString() ;
        }
        // PCからアクセスされている場合.
        else {
            errorPage = new StringBuilder().append( ERROR_PAGE ).append( ".html" ).toString() ;
        }
        // エラーページが存在するかチェック.
        for( ;; ) {
            String target = new StringBuilder().append( errorPageDir ).
                append( path ).toString() ;
            if( target.endsWith( "/" ) == false ) {
                target += "/" ;
            }
            if( FileUtil.isFileExists( target+codePage ) == true ) {
                return FileUtil.getFileByString( target+codePage,"UTF8" ) ;
            }
            if( FileUtil.isFileExists( target+ERROR_PAGE ) == true ) {
                return FileUtil.getFileByString( target+errorPage,"UTF8" ) ;
            }
            if( path.length() <= 0 ) {
                return null ;
            }
            p = path.lastIndexOf( "/" ) ;
            if( p != -1 ) {
                path = path.substring( 0,p ) ;
                if( ( p = path.lastIndexOf( "/" ) ) != -1 ) {
                    path = path.substring( 0,p ) ;
                }
                else {
                    path = "" ;
                }
            }
            else {
                path = "" ;
            }
        }
    }
}
