package org.maachang.comet.net;

import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.zip.GZIPOutputStream;

import org.maachang.comet.ServiceDef;
import org.maachang.comet.conf.MimeConfig;
import org.maachang.comet.httpd.HttpdErrorDef;
import org.maachang.comet.httpd.HttpdHeaders;
import org.maachang.comet.httpd.HttpdRequest;
import org.maachang.comet.httpd.HttpdResponse;
import org.maachang.comet.httpd.engine.HttpdCookie;
import org.maachang.comet.httpd.engine.HttpdDef;
import org.maachang.comet.httpd.engine.session.HttpdSession;
import org.maachang.comet.httpd.engine.session.HttpdSessionManager;
import org.maachang.comet.net.nio.ConnectionInfo;
import org.maachang.conf.ConvIniParam;
import org.maachang.manager.GlobalManager;

/**
 * HTTPD基本レスポンス実装.
 * 
 * @version 2007/08/26
 * @author masahito suzuki
 * @since MaachangComet 1.00
 */
public class HttpdBaseResponse implements HttpdResponse {
    
    /**
     * HTTPコード.
     */
    protected int state = HttpdErrorDef.HTTP11_200 ;
    
    /**
     * 戻りHTTPバージョン.
     */
    protected String version = HttpdDef.VERSION_11 ;
    
    /**
     * コネクションオブジェクト.
     */
    private ConnectionInfo connectionInfo = null ;
    
    /**
     * 戻りヘッダ.
     */
    protected HttpdHeaders header = null ;
    
    /**
     * HTTPキャッシュ.
     */
    protected boolean cacheMode = false ;
    
    /**
     * gzip圧縮フラグ.
     */
    protected boolean gzipFlag = true ;
    
    /**
     * リクエストGZIP名.
     */
    protected String gzipName = null ;
    
    /**
     * リクエストURL.
     */
    private String requestURL = null ;
    
    /**
     * 出力用オブジェクト.
     */
    private NetWriteHttpd output = null ;
    
    /**
     * OutputStream.
     */
    private OutputStream outputStream = null ;
    
    /**
     * printWriter.
     */
    private PrintWriter printWriter = null ;
    
    /**
     * クローズフラグ.
     */
    private boolean closeFlag = true ;
    
    /**
     * リクエストKeepAlive.
     */
    private boolean requestKeepAlive = false ;
    
    /**
     * リクエストオブジェクト.
     */
    private HttpdRequest request = null ;
    
    /**
     * ローカルホストアクセス.
     */
    private boolean localhostFlag = false ;
    
    /**
     * コンストラクタ.
     */
    private HttpdBaseResponse() {
        
    }
    
    /**
     * コンストラクタ.
     * <BR><BR>
     * ステータスを設定してオブジェクトを生成します.
     * <BR>
     * @param request 対象のリクエストを設定します.
     * @param url 対象のリクエストURLを設定します.
     * @param timeout 対象のタイムアウト値を設定します.
     * @param count 対象のカウント値を設定します.
     * @exception Exception 例外.
     */
    public HttpdBaseResponse( HttpdRequest request,String url,long timeout,int count )
        throws Exception {
        this( request,url,HttpdErrorDef.HTTP10_200,timeout,count ) ;
    }
    
    /**
     * コンストラクタ.
     * <BR><BR>
     * ステータスを設定してオブジェクトを生成します.
     * <BR>
     * @param request 対象のリクエストを設定します.
     * @param url 対象のリクエストURLを設定します.
     * @param state 対象のHTTPコードを設定します.
     * @param timeout 対象のタイムアウト値を設定します.
     * @param count 対象のカウント値を設定します.
     * @exception Exception 例外.
     */
    public HttpdBaseResponse( HttpdRequest request,String url,int state,long timeout,int count )
        throws Exception {
        if( request == null || request.getConnectionInfo() == null ||
            request.getConnectionInfo().isUse() == false ) {
            throw new IOException( "引数は不正です" ) ;
        }
        this.requestURL = url ;
        this.state = state ;
        this.connectionInfo = request.getConnectionInfo() ;
        this.header = new HttpdHeadersImpl() ;
        // GZIP圧縮可能かチェック.
        if( request.getHeader().getHeader( HttpdDef.VALUE_ENCODE ) != null ) {
            HttpdHeaders requestHeader = request.getHeader() ;
            int len = requestHeader.size( HttpdDef.VALUE_ENCODE ) ;
            boolean chkFlag = false ;
            if( request.isGzip() == true ) {
                for( int i = 0 ; i < len ; i ++ ) {
                    String encode = requestHeader.getHeader( HttpdDef.VALUE_ENCODE,i ) ;
                    if( encode != null && encode.indexOf( "gzip" ) != -1 ) {
                        if( ( encode = encode.trim() ).length() <= 0 ) {
                            this.gzipName = null ;
                            chkFlag = false ;
                        }
                        else {
                            this.gzipName = encode ;
                            chkFlag = true ;
                        }
                        break ;
                    }
                }
            }
            if( chkFlag == false ) {
                this.gzipName = null ;
            }
            // 指定拡張子が、非圧縮対象の場合かチェック.
            else {
                MimeConfig mimeType = ( MimeConfig )GlobalManager.getValue( ServiceDef.MANAGER_BY_MIME_TYPE ) ;
                int p = url.lastIndexOf( "." ) ;
                String plus = null ;
                if( p != -1 ) {
                    plus = url.substring( p+1 ) ;
                }
                if( plus == null || ( plus = plus.trim() ).length() <= 0 ) {
                    plus = "" ;
                }
                String type = mimeType.get( MimeConfig.OPTION_GZIP+plus ) ;
                if( ConvIniParam.getBoolean( type ) == false ) {
                    this.gzipName = null ;
                }
            }
        }
        else {
            this.gzipName = null ;
        }
        // リクエストのHTTPバージョンを取得.
        this.version = request.getVersion() ;
        // リクエストのKeepAlive情報を取得.
        if( HttpdDef.VERSION_11.equals( this.version ) && request.getHeader().isKeepAlive() == true ) {
            if( timeout > 0L && count > 0 ) {
                this.request = request ;
                requestKeepAlive = true ;
            }
            else {
                this.request = null ;
                requestKeepAlive = false ;
            }
        }
        else {
            requestKeepAlive = false ;
        }
        // アクセス先がローカルホストの場合.
        if( "127.0.0.1".equals( this.connectionInfo.getInetAddress().getHostAddress() ) == true ) {
            this.localhostFlag = true ;
        }
        else {
            this.localhostFlag = false ;
        }
        setHttpClose( true ) ;
    }
    
    /**
     * デストラクタ.
     */
    protected void finalize() throws Exception {
        destroy() ;
    }
    
    /**
     * オブジェクトを破棄.
     * <BR><BR>
     * オブジェクトを破棄します.
     */
    public void destroy() {
        this.flush() ;
        if( destroyByNoOutputStream() == false ) {
            if( printWriter != null ) {
                try {
                    printWriter.close() ;
                } catch( Exception e ) {
                }
            }
            if( outputStream != null ) {
                try {
                    outputStream.close() ;
                } catch( Exception e ) {
                }
            }
            if( output != null ) {
                try {
                    output.close() ;
                } catch( Exception e ) {
                }
            }
        }
        header = null ;
        cacheMode = false ;
        gzipFlag = true ;
        gzipName = null ;
        connectionInfo = null ;
        requestURL = null ;
        output = null ;
        outputStream = null ;
        printWriter = null ;
        closeFlag = true ;
        requestKeepAlive = false ;
        request = null ;
    }
    
    /**
     * 出力完了処理.
     * <BR><BR>
     * 出力を完了する処理を実施します.
     */
    public void flush() {
        if( printWriter != null ) {
            try {
                printWriter.flush() ;
            } catch( Exception e ) {
            }
        }
        if( outputStream != null ) {
            try {
                outputStream.flush() ;
            } catch( Exception e ) {
            }
            if( outputStream instanceof GZIPOutputStream ) {
                try {
                    ( ( GZIPOutputStream )outputStream ).finish() ;
                } catch( Exception e ) {
                }
            }
        }
    }
    
    /**
     * HTTPコードを設定.
     * <BR><BR>
     * HTTPコードを設定します.
     * <BR>
     * @param state 対象のHTTPコードを設定します.
     */
    public void setState( int state ) {
        this.state = state ;
    }
    
    /**
     * HTTPコードを取得.
     * <BR><BR>
     * HTTPコードを取得します.
     * <BR>
     * @return int HTTPコードが返されます.
     */
    public int getState() {
        return state ;
    }
    
    /**
     * HTTPバージョン情報を設定.
     * <BR><BR>
     * HTTPバージョン情報を設定します.
     * <BR>
     * @param version 対象のHTTPバージョンを設定します.
     */
    public void setVersion( String version ) {
        this.version = version ;
    }
    
    /**
     * HTTPバージョン情報を取得.
     * <BR><BR>
     * HTTPバージョン情報を取得します.
     * <BR>
     * @return String 対象のHTTPバージョンが返されます.
     */
    public String getVersion() {
        return version ;
    }
    
    /**
     * GZIP圧縮条件を設定.
     * <BR><BR>
     * GZIP圧縮条件を設定します.
     * <BR>
     * @param flag GZIP圧縮条件を設定します.
     */
    public void setGzip( boolean flag ) {
        gzipFlag = flag ;
    }
    
    /**
     * GZIP圧縮条件を取得.
     * <BR><BR>
     * GZIP圧縮条件を取得します.
     * <BR>
     * @return boolean GZIP圧縮条件が返されます.
     */
    public boolean getGzip() {
        if( gzipName == null ) {
            return false ;
        }
        return gzipFlag ;
    }
    
    /**
     * HTTPキャッシュ条件を設定.
     * <BR><BR>
     * HTTPキャッシュ条件を設定します.
     * <BR>
     * @param cache HTTPキャッシュ条件を設定します.
     */
    public void setHttpCache( boolean cache ) {
        this.cacheMode = cache ;
    }
    
    /**
     * HTTPキャッシュ条件を取得.
     * <BR><BR>
     * HTTPキャッシュ条件を取得します.
     * <BR>
     * @return boolean HTTPキャッシュ条件が返されます.
     */
    public boolean getHttpCache() {
        return cacheMode ;
    }
    
    /**
     * リクエストに設定されているSessionCookie条件を設定.
     * <BR><BR>
     * リクエストに設定されているSessionCookie条件を設定します.
     * <BR>
     * @param request 対象のリクエストを設定します.
     * @exception Exception 例外.
     */
    public void setCookieSession( HttpdRequest request )
        throws Exception {
        if( request == null ) {
            return ;
        }
        if( request.isCookie() == true ||
            HttpdRequestImpl.isSessionId( request.getUrlPath() ) == true ) {
            HttpdSession session = request.getSession() ;
            if( session != null ) {
                int len = 0 ;
                if( ( len = header.size( HttpdCookie.SET_COOKIE ) ) > 0 ) {
                    for( int i = len-1 ; i >= 0 ; i -- ) {
                        String value = header.getHeader( HttpdCookie.SET_COOKIE,i ) ;
                        if( value != null ) {
                            if( value.indexOf( HttpdSession.SESSION_NAME+"=" ) != -1 ) {
                                header.removeHeader( HttpdCookie.SET_COOKIE,i ) ;
                            }
                        }
                    }
                }
                HttpdSessionManager sessionManager = ( HttpdSessionManager )GlobalManager.getValue(
                    ServiceDef.MANAGER_BY_SESSION ) ;
                long time = System.currentTimeMillis() + sessionManager.getDeleteTime() ;
                String value = HttpdCookie.createCookie(
                    HttpdSession.SESSION_NAME,
                    session.getSessionId(),
                    "/",time ) ;
                header.addHeader( HttpdCookie.SET_COOKIE,value ) ;
            }
        }
    }
    
    /**
     * ヘッダオブジェクトを取得.
     * <BR><BR>
     * ヘッダオブジェクトを取得します.
     * <BR>
     * @return HttpdHeaders ヘッダオブジェクトが返されます.
     */
    public HttpdHeaders getHeader() {
        return header ;
    }
    
    /**
     * ソケットクローズフラグを設定.
     * <BR><BR>
     * 送信結果と同時に、ソケットをクローズする場合は、[true]を設定します.
     * <BR>
     * @param mode [true]の場合、このオブジェクトが破棄されると同時に、コネクションが切断されます.
     */
    public void setDestroyByClose( boolean mode ) {
        try {
            this.connectionInfo.setCloseFlag( mode ) ;
        } catch( Exception e ) {
        }
    }
    
    /**
     * リクエストKeepAliveモードを取得.
     * <BR><BR>
     * リクエストKeepAliveモードを取得します.
     * <BR>
     * @return boolean [true]の場合リクエストのKeepAliveは有効です.
     */
    public boolean isRequestKeepAlive() {
        return requestKeepAlive ;
    }
    
    /**
     * HTTPクローズ条件を設定.
     * <BR><BR>
     * HTTPクローズ条件を設定します.
     * <BR>
     * @param closeFlag HTTPクローズ条件を設定します.
     */
    public void setHttpClose( boolean closeFlag ) {
        // リクエストのKeepAliveモードが無効.
        // SSLの場合.
        // ローカルホストアクセスの場合.
        //if( this.requestKeepAlive == false || isSSL == true || localhostFlag == true ) {
        //    // 強制的にCloseとする.
        //    this.closeFlag = true ;
        //}
        //else {
        //    this.closeFlag = closeFlag ;
        //}
        
        // 何も条件は見ない.
        this.closeFlag = closeFlag ;
    }
    
    /**
     * HTTPクローズ条件を取得.
     * <BR><BR>
     * HTTPクローズ条件を取得します.
     * <BR>
     * @return boolean HTTPクローズ条件が返されます.
     */
    public boolean getHttpClose() {
        return closeFlag ;
    }
    
    /**
     * 戻り情報を設定するオブジェクトを取得.
     * <BR><BR>
     * 戻り情報を設定するオブジェクトを取得します.
     * <BR>
     * @return OutputStream 戻り情報を設定するオブジェクトが返されます.
     * @exception Exception 例外
     */
    public OutputStream getOutput()
        throws Exception {
        if( printWriter != null ) {
            return null ;
        }
        if( outputStream == null ) {
            boolean keepFlag = ( this.closeFlag == false ) ;
            boolean chkFlag = false ;
            if( output == null ) {
                output = new NetWriteHttpd( connectionInfo,requestURL,state,version,keepFlag ) ;
                // クローズ条件を設定.
                if( closeFlag == true ) {
                    // close.
                    output.closeOn() ;
                    this.setDestroyByClose( true ) ;
                }
                else {
                    if( requestKeepAlive == true ) {
                        // keepAlive.
                        output.closeOff() ;
                        header.addHeader( "Keep-Alive","timeout="+( request.getKeepAliveTimeout() ) ) ;
                        header.addHeader( "Keep-Alive","max="+request.getKeepAliveCount() ) ;
                        this.setDestroyByClose( false ) ;
                    }
                    else {
                        output.closeOn() ;
                        this.setDestroyByClose( true ) ;
                    }
                }
                // 基本ヘッダ群を設定.
                output.baseHeader() ;
                if( header != null ) {
                    output.pushHeaders( header ) ;
                }
                // 非キャッシュ状態の場合.
                if( cacheMode == true ) {
                    output.cacheOff() ;
                }
                // GZIP圧縮が有効の場合.
                if( HttpdDef.VERSION_11.equals( version ) == true && getGzip() == true ) {
                    output.pushHeader( "Content-Encoding",gzipName ) ;
                    output.setGzip( true ) ;
                }
            }
            // 出力用OutputStreamを生成.
            OutputStream os = output.getOutputStream() ;
            // チャングフラグを取得.
            chkFlag = output.isChunked() ;
            // GIZPの場合は、強制的にチャングにする.
            if( getGzip() == true ) {
                outputStream = new GZIPOutputStream( new ChunkedOutputStream( os ) ) ;
            }
            // GZIP以外の場合.
            else {
                if( chkFlag == true ) {
                    outputStream = new ChunkedOutputStream( os ) ;
                }
                else {
                    outputStream = new BufferedOutputStream( os ) ;
                }
            }
        }
        return outputStream ;
    }
    
    /**
     * 戻り情報を設定するPrintWriterを取得.
     * <BR><BR>
     * 戻り情報を設定するPrintWriterを取得します.
     * <BR>
     * @return PrintWriter 戻り情報を設定するオブジェクトが返されます.
     * @exception Exception 例外
     */
    public PrintWriter getPrint()
        throws Exception {
        if( printWriter == null ) {
            OutputStream os = getOutput() ;
            PrintWriter p = new PrintWriter(
                new OutputStreamWriter( os,HttpdDef.DEF_CHARSET ) ) ;
            printWriter = p ; 
        }
        return printWriter ;
    }
    
    /**
     * ローカルホストアクセスか取得.
     * <BR><BR>
     * ローカルホストから、アクセスされているか取得します.
     * <BR>
     * @return boolean [true]の場合、ローカルホストアクセスです.
     */
    public boolean isLocalHost() {
        return localhostFlag ;
    }
    
    /**
     * OutputStreamが展開されているかチェック.
     * <BR><BR>
     * OutputStreamが展開されているかチェックします.
     * <BR>
     * @return boolean [true]の場合、展開されています.
     */
    public boolean isOutputStream() {
        return ( output != null ) ? true : false ;
    }
    
    /**
     * destroyメソッドで、OutputStreamをオープンしていない場合.
     */
    private boolean destroyByNoOutputStream() {
        if( isOutputStream() == false ) {
            try {
                boolean keepFlag = ( this.closeFlag == false ) ;
                output = new NetWriteHttpd( connectionInfo,requestURL,state,version,keepFlag ) ;
                // クローズ条件を設定.
                if( closeFlag == true ) {
                    // close.
                    output.closeOn() ;
                    this.setDestroyByClose( true ) ;
                }
                else {
                    if( requestKeepAlive == true ) {
                        // keepAlive.
                        output.closeOff() ;
                        header.addHeader( "Keep-Alive","timeout="+request.getKeepAliveTimeout() ) ;
                        header.addHeader( "Keep-Alive","max="+request.getKeepAliveCount() ) ;
                        this.setDestroyByClose( false ) ;
                    }
                    else {
                        output.closeOn() ;
                        this.setDestroyByClose( true ) ;
                    }
                }
                // コンテンツ長を0バイトに設定.
                header.setHeader( HttpdDef.VALUE_CONTENT_LENGTH,"0" ) ;
                
                // 基本ヘッダ群を設定.
                output.baseHeader() ;
                if( header != null ) {
                    output.pushHeaders( header ) ;
                }
                // 非キャッシュ状態の場合.
                if( cacheMode == true ) {
                    output.cacheOff() ;
                }
                OutputStream out = output.getOutputStream() ;
                out.flush() ;
                out.close() ;
                output.close() ;
            } catch( Exception e ) {
            }
            return true ;
        }
        return false ;
    }
}
