package org.maachang.comet.net ;

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

import javax.net.ssl.SSLSocket;

import org.maachang.comet.conf.ServiceDef;
import org.maachang.comet.httpd.HttpConnectionInfo;
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.manager.GlobalManager;


/**
 * HTTPD基本リクエスト実装.
 *
 * @version 2007/08/20
 * @author  masahito suzuki
 * @since   MaachangComet 1.00
 */
public class HttpdBaseRequestImpl 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 HttpConnectionInfo conn = null ;
    
    /**
     * GZIP圧縮許可フラグ.
     */
    private boolean gzipFlag = false ;
    
    /**
     * Cookieフラグ.
     */
    private boolean cookieFlag = false ;
    
    /**
     * SSLフラグ.
     */
    private boolean sslFlag = false ;
    
    /**
     * コンストラクタ.
     */
    public HttpdBaseRequestImpl() {
        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 HttpdBaseRequestImpl( 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( ( id = query.getParam( HttpdDef.SESSION_PARAM ) ) != null &&
                    id.length() == HttpdSessionManager.SESSION_ID_LENGTH &&
                    sessionManager.getSession( id ) != null ) {
                    session = sessionManager.getSession( id ) ;
                }
                else {
                    session = sessionManager.getNewSession() ;
                }
                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( HttpConnectionInfo conn ) {
        if( conn.getSocket().socket() instanceof SSLSocket ) {
            sslFlag = true ;
        }
        else {
            sslFlag = false ;
        }
        this.conn = conn ;
    }
    
    /**
     * コネクション情報を取得.
     * <BR><BR>
     * コネクション情報を取得します.
     * <BR>
     * @param HttpConnectionInfo 対象のコネクション情報が返されます.
     */
    public HttpConnectionInfo getConnectionInfo() {
        return this.conn ;
    }
    
    /**
     * KeepAliveタイムアウトを設定.
     * <BR><BR>
     * KeepAliveタイムアウトを設定します.
     * <BR>
     * @param timeout 対象のKeepAliveタイムアウト値を設定します.
     */
    public void setKeepAliveTimeout( long timeout ) {
        if( timeout <= -1L ) {
            this.conn.setTimeout( -1L ) ;
        }
        else {
            long tm = header.getKeepAlive() ;
            if( tm > 0L ) {
                tm = tm * 1000L ;
                if( tm <= timeout ) {
                    timeout = tm ;
                }
            }
            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>
     * @param count KeepAliveカウント値を設定します.
     */
    public void setKeepAliveCount( int count ) {
        this.conn.setCount( count ) ;
    }
    
    /**
     * 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() {
        Socket soc = conn.getSocket().socket() ;
        return new StringBuilder().append( soc.getInetAddress().getHostAddress() ).
            append( "/" ).append( soc.getPort() ).toString() ;
    }
    
    /**
     * 文字列に変換します.
     */
    public String toString() {
        StringBuilder buf = new StringBuilder() ;
        if( conn != null && conn.getSocket() != null && conn.getSocket().socket() != null ) {
            Socket soc = conn.getSocket().socket() ;
            buf.append( " ip:[" ).append( soc.getInetAddress().getHostAddress() ).
                append( "] port:" ).append( soc.getPort() ) ;
        }
        buf.append( " method:" ).
            append( method ).
            append( " httpVersion:" ).
            append( httpVersion ).
            append( " urlPath:" ).
            append( urlPath ).
            append( " keepAliveTimeout:" ).
            append( getKeepAliveTimeout() ).
            append( " keepAliveCount:" ).
            append( getKeepAliveCount() ).
            append( " header:{" ).
            append( header ).
            append( " }" ).
            append( " query:{" ).
            append( query ).
            append( " }" ) ;
        return buf.toString() ;
    }
}
