package org.maachang.comet.httpd.engine.script.service.client;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.methods.ByteArrayRequestEntity;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.params.HttpMethodParams;
import org.maachang.util.URLEncode;

/**
 * HttpClientAccess.
 * 
 * @version 2007/10/18
 * @author masahito suzuki
 * @since MaachangComet 1.00
 */
public class HttpClientAccess {
    private HttpClientAccess() { }
    
    /**
     * リダイレクトリトライ回数.
     */
    private static final int REDIRECT_RETRY_COUNT = 99 ;
    
    /**
     * デフォルトタイムアウト.
     */
    private static final Integer DEFAULT_TIMEOUT = new Integer( 30000 ) ;
    
    /**
     * リクエストコンテンツ長.
     */
    private static final String CONTENT_LENGTH = "content-Length" ;
    
    /**
     * HTTPアクセス.
     * <BR><BR>
     * 指定URLに対してアクセスします.
     * <BR>
     * @param mode POSTの場合は[true]を設定します.
     * @param url 対象のURLを設定します.
     * @param timeout 対象のタイムアウト値を設定します.
     * @param header 対象のヘッダ情報を設定します.
     * @param params 対象のパラメータを設定します.
     * @param userAgent 対象のユーザエージェントを設定します.
     * @return HttpResult 処理結果が返されます.
     * @exception Exception 例外.
     */
    public static HttpResult connection( boolean mode,String url,Integer timeout,
        HttpHeader header,HttpParam params,String userAgent )
        throws Exception {
        if( url == null || ( url = url.trim() ).length() <= 0 ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        return executionConnection( mode,url,timeout,null,null,header,params,userAgent ) ;
    }
    
    /**
     * 指定バイナリをBodyに設定してHTTPアクセス.
     * <BR><BR>
     * 指定のバイナリをBodyに設定して、HTTPアクセスを行います.
     * <BR>
     * @param url 対象のURLを設定します.
     * @param timeout 対象のタイムアウト値を設定します.
     * @param body 対象のBody情報を設定します.
     * @param header 対象のヘッダ情報を設定します.
     * @param params 対象のパラメータを設定します.
     * @param userAgent 対象のユーザエージェントを設定します.
     * @return HttpResult 処理結果が返されます.
     * @exception Exception 例外.
     */
    public static HttpResult connectionByInputBody(
        String url,Integer timeout,byte[] body,String bodyType,HttpHeader header,
        HttpParam params,String userAgent )
        throws Exception {
        if( url == null || ( url = url.trim() ).length() <= 0 ||
            body == null || body.length <= 0 ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        return executionConnection( true,url,timeout,body,bodyType,header,params,userAgent ) ;
    }
    
    /**
     * コネクション実行.
     */
    public static HttpResult executionConnection(
        boolean mode,String url,Integer timeout,byte[] body,String bodyType,
        HttpHeader header,HttpParam params,String userAgent )
        throws Exception {
        if( timeout == null ) {
            timeout = DEFAULT_TIMEOUT ;
        }
        int rcnt = 0 ;
        HttpResult ret = null ;
        for( ;; ) {
            HttpMethod md = null ;
            try {
                if( REDIRECT_RETRY_COUNT <= rcnt ) {
                    throw new IOException( "リダイレクト数が規定値を越しました" ) ;
                }
                HttpClient cl = new HttpClient() ;
                cl.getParams().setParameter("http.socket.timeout", timeout) ;
                md = createMethod( mode,url,body,bodyType,header,params ) ;
                if( userAgent != null && ( userAgent = userAgent.trim() ).length() > 0 ) {
                    md.addRequestHeader( "User-Agent",userAgent ) ;
                }
                cl.getParams().setAuthenticationPreemptive( false ) ;
                cl.executeMethod( md ) ;
                ret = getHttpResponse( md ) ;
                break ;
            } catch( HttpRedirectUrlException hr ) {
                ret = null ;
                url = hr.getRedirectURL() ;
                rcnt ++ ;
            } catch( Exception e ) {
                ret = null ;
                throw e ;
            } finally {
                if( md != null ) {
                    md.releaseConnection() ;
                }
            }
        }
        return ret ;
    }
    
    /**
     * HttpMethodを生成.
     */
    private static final HttpMethod createMethod(
        boolean method,String url,byte[] body,String bodyType,HttpHeader headers,
        HttpParam params ) throws Exception {
        HttpMethod ret = null ;
        // postの場合.
        if( method == true ) {
            if( body != null ) {
                url = setParams( false,url,null,params ) ;
                PostMethod post = new PostMethod( url ) ;
                if( bodyType == null ) {
                    post.setRequestEntity( new ByteArrayRequestEntity( body ) ) ;
                }
                else {
                    post.setRequestEntity( new ByteArrayRequestEntity( body,bodyType ) ) ;
                }
                ret = post ;
            }
            else {
                PostMethod post = new PostMethod( url ) ;
                setParams( true,url,post,params ) ;
                ret = post ;
            }
        }
        // getの場合.
        else {
            url = setParams( false,url,null,params ) ;
            GetMethod get = new GetMethod( url ) ;
            ret = get ;
        }
        
        // ヘッダ情報を設定.
        if( headers != null ) {
            String[] keys = headers.getKeys() ;
            if( keys != null ) {
                int len = keys.length ;
                for( int i = 0 ; i < len ; i ++ ) {
                    if( CONTENT_LENGTH.equals( keys[ i ] ) ) {
                        continue ;
                    }
                    int lenJ = headers.size( keys[ i ] ) ;
                    for( int j = 0 ; j < lenJ ; j ++ ) {
                        String value = headers.getHeader( keys[ i ],j ) ;
                        if( value == null ) {
                            ret.addRequestHeader( keys[ i ],"" ) ;
                        }
                        else {
                            ret.addRequestHeader( keys[ i ],value ) ;
                        }
                    }
                }
            }
        }
        
        // リダイレクトなしで設定.
        ret.setFollowRedirects( false ) ;
        
        // リトライ回数をセット.
        ret.getParams().setParameter(
            HttpMethodParams.RETRY_HANDLER,
            new DefaultHttpMethodRetryHandler( 3,false )
        ) ;
        
        // 認証OFF.
        ret.setDoAuthentication( false ) ;
        return ret ;
    }
    
    /**
     * Postパラメータを設定.
     */
    private static final String setParams( boolean mode,String url,HttpMethod method,HttpParam params )
        throws Exception {
        // postの場合.
        if( mode == true ) {
            PostMethod post = ( PostMethod )method ;
            // パラメータ設定対象の場合.
            if( params != null && params.size() > 0 ) {
                String[] keys = params.getKeys() ;
                if( keys != null ) {
                    int len = keys.length ;
                    for( int i = 0 ; i < len ; i ++ ) {
                        int lenJ = params.size( keys[ i ] ) ;
                        for( int j = 0 ; j < lenJ ; j ++ ) {
                            String value = params.getParam( keys[ i ],j ) ;
                            if( value == null ) {
                                post.addParameter( keys[ i ],"" ) ;
                            }
                            else {
                                post.addParameter( keys[ i ],value ) ;
                            }
                        }
                    }
                }
            }
        }
        // getの場合.
        else {
            // パラメータ設定対象の場合.
            if( params != null && params.size() > 0 ) {
                String[] keys = params.getKeys() ;
                if( keys != null ) {
                    StringBuilder buf = new StringBuilder() ;
                    buf.append( url ).append( "?" ) ;
                    int len = keys.length ;
                    for( int i = 0 ; i < len ; i ++ ) {
                        if( i != 0 ) {
                            buf.append( "&" ) ;
                        }
                        int lenJ = params.size( keys[ i ] ) ;
                        for( int j = 0 ; j < lenJ ; j ++ ) {
                            if( j != 0 ) {
                                buf.append( "&" ) ;
                            }
                            String value = params.getParam( keys[ i ],j ) ;
                            if( value == null ) {
                                buf.append( keys[ i ] ).append( "=" ) ;
                            }
                            else {
                                buf.append( keys[ i ] ).append( "=" ).
                                    append( URLEncode.convert( value.toString() ) ) ;
                            }
                        }
                    }
                    url = buf.toString() ;
                }
            }
            return url ;
        }
        return null ;
    }
    
    /**
     * 接続結果を取得.
     */
    private static final HttpResult getHttpResponse( HttpMethod method ) throws Exception {
        HttpResult ret = null ;
        int state = method.getStatusCode() ;
        // 発生率の高いステータス.
        if( state == 200 ||
            state == 401 ||
            state == 403 ||
            state == 404 ||
            state >= 500 ) {
            
            byte[] binary = null ;
            InputStream in = method.getResponseBodyAsStream() ;
            ByteArrayOutputStream out = new ByteArrayOutputStream() ;
            try {
                byte[] buf = null ;
                
                buf = new byte[8192] ;
                int n ;
                while ((n = in.read(buf)) >= 0) {
                    if (n > 0) {
                        out.write(buf, 0, n) ;
                    }
                }
                out.flush() ;
                binary = out.toByteArray() ;
                out.close() ;
                out = null ;
                in.close() ;
                in = null ;
            } finally {
                if( in != null ) {
                    try {
                        in.close() ;
                    } catch( Exception e ) {
                    }
                }
                if( out != null ) {
                    try {
                        out.close() ;
                    } catch( Exception e ) {
                    }
                }
            }
            Header header = method.getResponseHeader( "Content-Type" ) ;
            ret = new HttpResult( binary,state,( header == null ) ? null : header.getValue() ) ;
        }
        // リダイレクト系ステータス.
        else if( state == 301 ||
            state == 302 ) {
            // リダイレクトを通知する例外を発行.
            if( method.getResponseHeader( "Location" ) == null ) {
                throw new IOException( "リダイレクト("+state+")に対して[Location]が存在しません" ) ;
            }
            throw new HttpRedirectUrlException( method.getResponseHeader( "Location" ).getValue() ) ;
        }
        // その他エラー.
        else {
            return null ;
        }
        return ret ;
    }
}
