package org.maachang.comet.net ;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.HashMap;

import org.maachang.comet.ServiceDef;
import org.maachang.comet.httpd.HttpdHeaders;
import org.maachang.comet.httpd.HttpdParams;
import org.maachang.comet.httpd.HttpdRequest;
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.manager.GlobalManager;


/**
 * HTTPD基本リクエスト実装.
 *
 * @version 2007/08/20
 * @author  masahito suzuki
 * @since   MaachangComet 1.00
 */
public class HttpdRequestImpl implements HttpdRequest {
    
    /**
     * 処理タイプ.(GET/POST).
     */
    private String method = null ;
    
    /**
     * HTTPバージョン.
     */
    private String httpVersion = null ;
    
    /**
     * URL相対パス.
     */
    private String urlPath = null ;
    
    /**
     * パラメータ.
     */
    private HttpdParams query = null ;
    
    /**
     * ヘッダ.
     */
    private HttpdHeaders header = null ;
    
    /**
     * セッション.
     */
    private HttpdSession session = null ;
    
    /**
     * HTTPBody情報.
     */
    private byte[] httpBody = null ;
    
    /**
     * コネクション情報.
     */
    private ConnectionInfo conn = null ;
    
    /**
     * GZIP圧縮許可フラグ.
     */
    private boolean gzipFlag = false ;
    
    /**
     * Cookieフラグ.
     */
    private boolean cookieFlag = false ;
    
    /**
     * SSLフラグ.
     */
    private boolean sslFlag = false ;
    
    /**
     * コンストラクタ.
     */
    public HttpdRequestImpl() {
        query = new HttpdParams() ;
        header = new HttpdHeaders() ;
        sslFlag = false ;
    }
    
    /**
     * コンストラクタ.
     * <BR><BR>
     * 条件を設定してオブジェクトを生成します.
     * <BR>
     * @param method HTTPメソッドを設定します.
     * @param version HTTPバージョンを設定します.
     * @param url 接続URLを設定します.
     * @param header HTTPヘッダ情報を設定します.
     * @param query HTTPBody情報を設定します.
     * @exception Exception 例外.
     */
    public HttpdRequestImpl( String method,String version,String url,
        HttpdHeaders header,HttpdParams query )
        throws Exception {
        if( method == null || ( method = method.trim() ).length() <= 0 ||
            version == null || ( version = version.trim() ).length() <= 0 ||
            url == null || ( url = url.trim() ).length() <= 0 ||
            header == null || header.size() <= 0 ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        this.method = method ;
        this.httpVersion = version ;
        this.urlPath = url ;
        this.header = header ;
        this.query = ( query == null ) ? new HttpdParams() : query ;
        this.sslFlag = false ;
    }
    
    /**
     * デストラクタ.
     */
    protected void finalize() throws Exception {
        method = null ;
        httpVersion = null ;
        urlPath = null ;
        query = null ;
        header = null ;
        session = null ;
        httpBody = null ;
        conn = null ;
    }
    
    /**
     * HTTPメソッドを設定.
     * <BR><BR>
     * HTTPメソッドを設定します.
     * <BR>
     * @param method HTTPメソッドを設定します.
     */
    public void setMethod( String method ) {
        this.method = method ;
    }
    
    /**
     * HTTPメソッドを取得.
     * <BR><BR>
     * HTTPメソッドを取得します.
     * <BR>
     * @return String HTTPメソッドが返されます.
     */
    public String getMethod() {
        return method ;
    }
    
    /**
     * HTTPバージョンを設定.
     * <BR><BR>
     * HTTPバージョンを設定します.
     * <BR>
     * @param httpVersion HTTPバージョンが返されます.<BR>
     *                    [HTTP/0.9]の場合は、HTTPバージョン0.9です.<BR>
     *                    [HTTP/1.0]の場合は、HTTPバージョン1.0です.<BR>
     *                    [HTTP/1.1]の場合は、HTTPバージョン1.1です.
     */
    public void setHttpVersion( String httpVersion ) {
        this.httpVersion = httpVersion ;
    }
    
    /**
     * HTTPバージョンを取得.
     * <BR><BR>
     * HTTPバージョンを取得します.
     * <BR>
     * @return String HTTPバージョンが返されます.
     *                [HTTP/0.9]の場合は、HTTPバージョン0.9です.<BR>
     *                [HTTP/1.0]の場合は、HTTPバージョン1.0です.<BR>
     *                [HTTP/1.1]の場合は、HTTPバージョン1.1です.
     */
    public String getHttpVersion() {
        return httpVersion ;
    }
    
    /**
     * HTTPバージョン番号を取得.
     * <BR><BR>
     * HTTPバージョン番号を取得します.
     * <BR>
     * @return String HTTPバージョン番号が返されます.
     */
    public String getVersion() {
        return httpVersion.substring( "HTTP/".length() ) ;
    }
    
    /**
     * URL相対パスを設定.
     * <BR><BR>
     * URL相対パスを設定します.
     * <BR>
     * @param urlPath URL相対パスを設定します.
     */
    public void setUrlPath( String urlPath ) {
        this.urlPath = urlPath ;
    }
    
    /**
     * URL相対パスを取得.
     * <BR><BR>
     * URL相対パスを取得します.
     * <BR>
     * @return String URL相対パスが返されます.
     */
    public String getUrlPath() {
        return urlPath ;
    }
    
    /**
     * クエリオブジェクトを取得.
     * <BR><BR>
     * クエリオブジェクトを取得します.
     * <BR>
     * @return HttpdParams クエリオブジェクトが返されます.
     */
    public HttpdParams getQuery() {
        return query ;
    }
    
    /**
     * ヘッダオブジェクトを取得.
     * <BR><BR>
     * ヘッダオブジェクトを取得します.
     * <BR>
     * @return HttpdHeaders ヘッダオブジェクトが返されます.
     */
    public HttpdHeaders getHeader() {
        return header ;
    }
    
    /**
     * このリクエストに対するセッションオブジェクトを取得.
     * <BR><BR>
     * このリクエストに対するセッションオブジェクトが返されます.
     * <BR>
     * @return HttpdSession セッション情報が返されます.
     * @exception Exceptino 例外.
     */
    public HttpdSession getSession()
        throws Exception {
        if( session == null ) {
            HttpdSessionManager sessionManager = ( HttpdSessionManager )GlobalManager.getValue(
                ServiceDef.MANAGER_BY_SESSION ) ;
            String id = null ;
            // CookieのセッションIDで処理.
            String cookie = HttpdCookie.getCookie( this ) ;
            if( cookie != null ) {
                HashMap<String,String> ana = HttpdCookie.analysisCookie( cookie ) ;
                id = ana.get( HttpdSession.SESSION_NAME ) ;
                if( id != null && id.length() == HttpdSessionManager.SESSION_ID_LENGTH ) {
                    session = sessionManager.getSession( id ) ;
                }
                cookieFlag = true ;
            }
            // パラメータのセッションIDで処理.
            if( session == null ) {
                // セッション付与を行うファイルタイプの場合.
                if( isSessionId( urlPath ) ) {
                    id = query.getParam( HttpdDef.SESSION_PARAM ) ;
                    if( id != null &&
                        id.length() == HttpdSessionManager.SESSION_ID_LENGTH &&
                        sessionManager.getSession( id ) != null ) {
                        session = sessionManager.getSession( id ) ;
                    }
                    else {
                        session = sessionManager.getNewSession() ;
                    }
                    // クエリに新しく割り当てられたセッションパラメータを付加.
                    query.setParam( HttpdDef.SESSION_PARAM,session.getSessionId() ) ;
                }
                cookieFlag = false ;
            }
            
        }
        //LOG.info( "session:" + session.toString() ) ;
        return session ;
    }
    
    /**
     * HTTPBody情報を取得.
     * <BR><BR>
     * HTTPBody情報を取得します.
     * <BR>
     * @return InputStream HTTPBody情報が返されます.
     */
    public InputStream getBody() {
        if( httpBody == null ) {
            return null ;
        }
        return new ByteArrayInputStream( httpBody ) ;
    }
    
    /**
     * HTTPBody情報を取得.
     * <BR><BR>
     * HTTPBody情報を取得します.
     * <BR>
     * @return byte[] HTTPBody情報が返されます.
     */
    public byte[] getBodyByBinary() {
        if( httpBody == null ) {
            return null ;
        }
        return httpBody ;
    }
    
    /**
     * HTTPBody情報を設定.
     * <BR><BR>
     * HTTPBody情報を設定します.
     * <BR>
     * @param body 対象のHTTPBody情報を設定します.
     */
    public void setBody( byte[] body ) {
        if( body == null || body.length <= 0 ) {
            this.httpBody = null ;
        }
        else {
            this.httpBody = body ;
        }
    }
    
    /**
     * コネクション情報を設定.
     * <BR><BR>
     * コネクション情報を設定します.
     * <BR>
     * @param conn 対象のコネクション情報を設定します.
     */
    public void setConnectionInfo( ConnectionInfo conn ) {
        if( conn.getElement().getSslElement() != null ) {
            sslFlag = true ;
        }
        else {
            sslFlag = false ;
        }
        this.conn = conn ;
    }
    
    /**
     * コネクション情報を取得.
     * <BR><BR>
     * コネクション情報を取得します.
     * <BR>
     * @param ConnectionInfo 対象のコネクション情報が返されます.
     */
    public ConnectionInfo getConnectionInfo() {
        return this.conn ;
    }
    
    /**
     * KeepAliveタイムアウトを設定.
     * <BR><BR>
     * KeepAliveタイムアウトを設定します.
     * <BR>
     * @param timeout 対象のKeepAliveタイムアウト値を設定します.
     */
    public void setKeepAliveTimeout( int timeout ) {
        if( timeout > 0L ) {
            this.conn.setTimeout( timeout ) ;
        }
    }
    
    /**
     * KeepAliveタイムアウトを取得.
     * <BR><BR>
     * KeepAliveタイムアウトを取得します.
     * <BR>
     * @return long KeepAliveタイムアウト値が返されます.
     */
    public long getKeepAliveTimeout() {
        if( conn == null ) {
            return -1L ;
        }
        return conn.getTimeout() ;
    }
    
    /**
     * KeepAliveカウントを取得.
     * <BR><BR>
     * KeepAliveカウントを取得します.
     * <BR>
     * @return int KeepAliveカウント値が返されます.
     */
    public int getKeepAliveCount() {
        if( conn == null ) {
            return -1 ;
        }
        return this.conn.getCount() ;
    }
    
    /**
     * GZIP圧縮を許可するか設定.
     * <BR><BR>
     * MaachangCometがGZIP圧縮を許可している場合[true]を設定します.
     * <BR>
     * @param flag [true]の場合、GZIP圧縮を許可します.
     */
    public void setGzip( boolean flag ) {
        this.gzipFlag = flag ;
    }
    
    /**
     * GZIP圧縮を許可しているかチェック.
     * <BR><BR>
     * MaachangCometがGZIP圧縮を許可しているかチェックします.
     * <BR>
     * @return boolean [true]の場合、許可されています.
     */
    public boolean isGzip() {
        return gzipFlag ;
    }
    
    /**
     * Cookieが存在するかチェック.
     * <BR><BR>
     * Cookieが存在するかチェックします.
     * <BR>
     * @return boolean [true]の場合、Cookieが存在します.
     */
    public boolean isCookie() {
        return cookieFlag ;
    }
    
    /**
     * 対象プロトコルがSSLであるかチェック.
     * <BR><BR>
     * 対象プロトコルがSSLであるかチェックします.
     * <BR>
     * @return boolean [true]の場合、プロトコルはSSLです.
     */
    public boolean isSsl() {
        return sslFlag ;
    }
    
    /**
     * このリクエストが有効かチェック.
     * <BR><BR>
     * このリクエストが有効かチェックします.
     * <BR>
     * @return [true]が返された場合、有効です.
     */
    public boolean isUse() {
        return ( conn == null || conn.isUse() == false ) ? false : true ;
    }
    
    /**
     * IPアドレス、ポート番号を文字列として取得.
     * <BR><BR>
     * IPアドレス、ポート番号を文字列として取得します.
     * <BR>
     * @return String 文字列が返されます.
     */
    public String toIpPort() {
        return new StringBuilder().append( conn.getInetAddress().getHostAddress() ).
            append( "/" ).append( conn.getPort() ).toString() ;
    }
    
    /**
     * SessionId付与許可拡張子.
     */
    private static final String[] SESSION_LIST = {
        ".ms",".mhtml",".js",".html",".htm" } ;
    
    /**
     * SessionID付与許可拡張子かチェック.
     * @param path 対象のURLパスを設定します.
     * @return boolean [true]の場合、セッションIDを付与します.
     */
    public static final boolean isSessionId( String path ) {
        int x = path.lastIndexOf( "/" ) ;
        if( x >= 0 ) {
            path = path.substring( x ) ;
        }
        if( path.lastIndexOf( "." ) <= -1 ) {
            return true ;
        }
        int len = SESSION_LIST.length ;
        path = path.trim().toLowerCase() ;
        for( int i = 0 ; i < len ; i ++ ) {
            if( path.endsWith( SESSION_LIST[ i ] ) ) {
                return true ;
            }
        }
        return false ;
    }
}
