package org.maachang.comet.httpd.engine;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.OutputStream;

import org.maachang.comet.MaachangDef;
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.script.ScriptDef;
import org.maachang.util.FileUtil;
import org.maachang.util.StringUtil;

/**
 * Httpdパブリックディレクトリ管理.
 * 
 * @version 2007/08/29
 * @author masahito suzuki
 * @since MaachangComet 1.00
 */
class HttpdPublicManager implements HttpdExecutionManager {
    
    /**
     * ZIPを行わないデータサイズ.
     */
    private static final long NOT_GZIP_LENGTH = 1024 ;
    
    /**
     * データファイルサイズ長.
     */
    private static final long ONE_WRITE_BY_LENGTH = 8192 ;
    
    /**
     * publicディレクトリ.
     */
    private String publicDirectory = null ;
    
    /**
     * コンストラクタ.
     */
    private HttpdPublicManager() {
        
    }
    
    /**
     * コンストラクタ.
     * <BR><BR>
     * カレントディレクトリを指定してオブジェクトを生成します.
     * <BR>
     * @param currentDirectory 対象のカレントディレクトリを設定します.
     * @exception Exception 例外.
     */
    public HttpdPublicManager( String currentDirectory )
        throws Exception {
        this.publicDirectory = new StringBuilder().
            append( ScriptDef.trimCurrentDirectory( currentDirectory ) ).
            append( MaachangDef.DIRECTORY_PUBLIC ).toString() ;
    }
    
    /**
     * デストラクタ.
     */
    protected void finalize() throws Exception {
        publicDirectory = null ;
    }
    
    /**
     * 指定内容を取得.
     * <BR><BR>
     * 指定された内容を取得します.
     * <BR>
     * @param socket 対象のソケットチャネルを設定します.
     * @param request 対象のリクエストを設定します.
     * @return HttpdResponse 対象のレスポンスを設定します.
     * @exception Exception 例外.
     */
    public HttpdResponse get( HttpdRequest request )
        throws Exception {
        if( request == null ) {
            throw new HttpdStateException( HttpdErrorDef.HTTP11_400,
                "リクエストが不正です" ) ;
        }
        HttpdResponse ret = null ;
        try {
            String path = getUsePath( request.getUrlPath() ) ;
            pushUrl( request,path ) ;
            if( isJsOrCssOrIco( path ) ) {
                byte[] data = PublicCache.getInstance().getData( path ) ;
                if( data != null ) {
                    long time = PublicCache.getInstance().getLastTime( path ) ;
                    ret = HttpdResponseInstance.createResponse( request,request.getUrlPath(),
                        request.getKeepAliveTimeout(),request.getKeepAliveCount() ) ;
                    ret.setCookieSession( request ) ;
                    ret.getHeader().setHeader( HttpdDef.VALUE_LAST_UPDATE,
                        HttpdTimestamp.getTimestamp( time ) ) ;
                    ret.setHttpClose( false ) ;
                    if( path.toLowerCase().endsWith( ".js" ) ) {
                        String js = new String( data,"UTF8" ) ;
                        js = EnvJavascript.convert( request,js ) ;
                        data = js.getBytes( "UTF8" ) ;
                        js = null ;
                    }
                    // 長さなし.
                    //ret.getHeader().setHeader( HttpdDef.VALUE_CONTENT_LENGTH,
                    //    String.valueOf( data.length ) ) ;
                    if( data.length < NOT_GZIP_LENGTH ) {
                        ret.setGzip( false ) ;
                    }
                    OutputStream out = null ;
                    try {
                        out = ret.getOutput() ;
                        out.write( data ) ;
                        out.flush() ;
                        out.close() ;
                        out = null ;
                    } finally {
                        if( out != null ) {
                            try {
                                out.close() ;
                            } catch( Exception ee ) {
                            }
                        }
                    }
                }
            }
            else if( FileUtil.isFileExists( path ) == true ) {
                if( FileUtil.isRead( path ) == false ) {
                    throw new HttpdStateException( HttpdErrorDef.HTTP11_403,
                        "指定パス[" + path + "]は読み込み権限がありません" ) ;
                }
                ret = HttpdResponseInstance.createResponse( request,request.getUrlPath(),
                    request.getKeepAliveTimeout(),request.getKeepAliveCount() ) ;
                ret.setCookieSession( request ) ;
                ret.getHeader().setHeader( HttpdDef.VALUE_LAST_UPDATE,
                    HttpdTimestamp.getTimestamp( FileUtil.getLastTime( path ) ) ) ;
                long flen = FileUtil.getLength( path ) ;
                // 長さなし.
                //ret.getHeader().setHeader( HttpdDef.VALUE_CONTENT_LENGTH,
                //    String.valueOf( flen ) ) ;
                ret.setHttpClose( false ) ;
                if( flen < NOT_GZIP_LENGTH ) {
                    ret.setGzip( false ) ;
                }
                
                if( flen >= ONE_WRITE_BY_LENGTH ) {
                    BufferedInputStream bi = null ;
                    OutputStream out = null ;
                    try {
                        bi = new BufferedInputStream( new FileInputStream( path ) ) ;
                        out = ret.getOutput() ;
                        byte[] bin = new byte[ HttpdDef.BUFFER_SIZE ] ;
                        for( ;; ) {
                            int len = bi.read( bin ) ;
                            if( len <= 0 ) {
                                if( len <= -1 ) {
                                    out.flush() ;
                                    break ;
                                }
                            }
                            else {
                                out.write( bin,0,len ) ;
                            }
                        }
                        out.flush() ;
                        out.close() ;
                        out = null ;
                        bi.close() ;
                        bi = null ;
                    } finally {
                        if( bi != null ) {
                            try {
                                bi.close() ;
                            } catch( Exception ee ) {
                            }
                        }
                        if( out != null ) {
                            try {
                                out.close() ;
                            } catch( Exception ee ) {
                            }
                        }
                    }
                }
                else {
                    OutputStream out = null ;
                    try {
                        out = ret.getOutput() ;
                        out.write( FileUtil.getFile( path ) ) ;
                        out.flush() ;
                        out.close() ;
                        out = null ;
                    } finally {
                        if( out != null ) {
                            try {
                                out.close() ;
                            } catch( Exception e ) {
                            }
                        }
                        out = null ;
                    }
                }
            }
        } catch( Exception e ) {
            throw e ;
        } catch( Error e ) {
            throw e ;
        }
        return ret ;
    }
    
    /**
     * 指定パスのファイル日付を取得.
     * <BR><BR>
     * 指定パスのファイル日付を取得します.
     * <BR>
     * @parma path 対象のパスを設定します.
     * @return long ファイル日付が返されます.
     */
    public long getFileTime( String path ) {
        path = getUsePath( path ) ;
        if( isJsOrCssOrIco( path ) ) {
            try {
                return PublicCache.getInstance().getLastTime( path ) ;
            } catch( Exception e ) {
            }
        }
        else if( FileUtil.isFileExists( path ) == true &&
            FileUtil.isRead( path ) == true ) {
            return FileUtil.getLastTime( path ) ;
        }
        return -1L ;
    }
    
    /**
     * 指定パスの内容が存在するかチェック.
     * <BR><BR>
     * 指定パスの内容が存在するかチェックします.
     * <BR>
     * @param path 対象のパスを設定します.
     * @return boolean [true]の場合、存在します.
     */
    public boolean isPath( String path ) {
        path = getUsePath( path ) ;
        if( isJsOrCssOrIco( path ) ) {
            try {
                return ( PublicCache.getInstance().getLastTime( path ) > 0L ) ;
            } catch( Exception e ) {
            }
        }
        else if( FileUtil.isFileExists( path ) == 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 = publicDirectory + path ;
        if( FileUtil.isDirExists( path ) == true ) {
            return true ;
        }
        return false ;
    }
    
    /**
     * 指定パスの内容が読み込み可能かチェック.
     * <BR><BR>
     * 指定パスの内容が読み込み可能かチェックします.
     * <BR>
     * @param path 対象のパスを設定します.
     * @return boolean [true]の場合、読み込み可能です.
     */
    public boolean isRead( String path ) {
        path = getUsePath( path ) ;
        if( isJsOrCssOrIco( path ) ) {
            boolean ret = false ;
            try {
                ret = ( PublicCache.getInstance().getLastTime( path ) > 0L ) ;
            } catch( Exception e ) {
                ret = false ;
            }
            return ret ;
        }
        else if( FileUtil.isFileExists( path ) == true ) {
            return FileUtil.isRead( path ) ;
        }
        return false ;
    }
    
    /**
     * 有効パスを取得.
     */
    private final String getUsePath( String path ) {
        if( HttpdDef.ADDRESS_BAR_ICON.endsWith( path ) == true ) {
            path = new StringBuilder().
                append( publicDirectory+MaachangDef.DIRECTORY_IMAGE ).
                append( HttpdDef.ADDRESS_BAR_ICON.substring( 1,HttpdDef.ADDRESS_BAR_ICON.length() ) ).
                toString() ;
            if( FileUtil.isFileExists( path ) == false ) {
                path = new StringBuilder().
                    append( publicDirectory ).
                    append( HttpdDef.ADDRESS_BAR_ICON.substring( 1,HttpdDef.ADDRESS_BAR_ICON.length() ) ).
                    toString() ;
            }
        }
        else {
            if( path.startsWith( "/" ) == true ) {
                path = path.substring( 1,path.length() ) ;
            }
            path = publicDirectory + path ;
            if( path.endsWith( "/" ) == true ) {
                int len = HttpdDef.INDEX_HTML.length ;
                for( int i = 0 ; i < len ; i ++ ) {
                    String ph = path + HttpdDef.INDEX_HTML[ i ] ;
                    if( FileUtil.isFileExists( ph ) == true ) {
                        path = ph ;
                        break ;
                    }
                }
            }
        }
        return path ;
    }
    
    /**
     * 対象パスを整形してURLとして設定.
     */
    private final void pushUrl( HttpdRequest request,String path )
        throws Exception {
        String str = FileUtil.getFullPath( MaachangDef.DIRECTORY_PUBLIC ) ;
        path = FileUtil.getFullPath( path ) ;
        path = path.substring( str.length(),path.length() ) ;
        path = StringUtil.changeString( path,"\\","/" ) ;
        if( path.startsWith( "/" ) == false ) {
            path = "/" + path ;
        }
        request.setUrlPath( path ) ;
    }
    
    /**
     * 指定パスがJSファイルか、CSSファイルであるかチェック.
     */
    private static final boolean isJsOrCssOrIco( String path ) {
        path = path.toLowerCase() ;
        return ( path.endsWith( ".js" ) || path.endsWith( ".css" ) || path.endsWith( ".ico" ) ) ;
    }
}

