package org.maachang.proxy.engine.net;

import java.io.OutputStream;
import java.util.ArrayList;

import org.maachang.util.ArrayBinary;
import org.maachang.util.ConvertParam;

/**
 * リクエスト情報.
 * 
 * @version 2008/03/25
 * @author masahito suzuki
 * @since MaachangProxy 1.00
 */
public class RequestInfo {
    /** メソッド名. */
    private String method = null ;
    
    /** 接続先URL. */
    private String url = null ;
    
    /** バージョン. */
    private String version = null ;
    
    /** ヘッダ情報. */
    private ArrayList<ValueInfo> header = null ;
    
    /** Body情報. */
    private byte[] body = null ;
    
    /** セッションID. */
    private String sessionId = null ;
    
    /** 変換キャラクタセット */
    private String charset = null ;
    
    /**
     * コンストラクタ.
     */
    public RequestInfo() {
        header = new ArrayList<ValueInfo>() ;
    }
    
    /**
     * デストラクタ.
     */
    protected void finalize() throws Exception {
        destroy() ;
    }
    
    /**
     * オブジェクト破棄.
     */
    public void destroy() {
        method = null ;
        url = null ;
        version = null ;
        header = null ;
        body = null ;
        sessionId = null ;
        charset = null ;
    }
    
    /**
     * メソッド設定.
     * @param method メソッドを設定します.
     */
    public void setMethod( String method ) {
        this.method = method ;
    }
    
    /**
     * メソッドを取得.
     * @return String メソッドが返されます.
     */
    public String getMethod() {
        return method ;
    }
    
    /**
     * URLを設定.
     * @param url URLを設定します.
     */
    public void setUrl( String url ) {
        if( url == null || ( url = url.trim() ).length() <= 0 ) {
            return ;
        }
        if( url.startsWith( "/" ) == false ) {
            url = "/" + url ;
        }
        this.url = url ;
    }
    
    /**
     * URLを取得.
     * @return String URLが返されます.
     */
    public String getUrl() {
        return url ;
    }
    
    /**
     * HTTPバージョンを設定.
     * @param version versionを設定します.
     */
    public void setVersion( String version ) {
        if( version == null || ( version = version.trim() ).length() <= 0 ) {
            return ;
        }
        version = version.toUpperCase() ;
        if( version.startsWith( "HTTP/" ) == false ) {
            version = "HTTP/" + version ;
        }
        this.version = version ;
    }
    
    /**
     * HTTPバージョンを取得.
     * @return String HTTPバージョンが返されます.
     */
    public String getVersion() {
        if( version != null && version.startsWith( "HTTP/" ) ) {
            return version.substring( "HTTP/".length() ) ;
        }
        return version ;
    }
    
    /**
     * Bodyバイナリを設定.
     * @param body Bodyバイナリを設定します.
     */
    public void setBody( byte[] body ) {
        if( "POST".equals( this.method ) ) {
            this.body = body ;
        }
    }
    
    /**
     * Bodyバイナリを取得.
     * @return byte[] Bodyバイナリが返されます.
     */
    public byte[] getBody() {
        return this.body ;
    }
    
    /**
     * セッションIDを設定.
     * @param sessionId 対象のセッションIDを設定します.
     */
    public void setSessionId( String sessionId ) {
        this.sessionId = sessionId ;
    }
    
    /**
     * セッションIDを取得.
     * @return String セッションIDが返されます.
     */
    public String getSessionId() {
        return this.sessionId ;
    }
    
    /**
     * charset を取得.
     * <BR><BR>
     * @return charset が返されます.
     */
    public String getCharset() {
        return charset;
    }
    
    /**
     * charset を設定.
     * <BR><BR>
     * @param charset charset を設定します.
     */
    public void setCharset(String charset) {
        this.charset = charset;
    }
    
    /**
     * ヘッダをセット.
     * @param key キー名を設定します.
     * @param value ヘッダ要素を設定します.
     */
    public void putHeader( String key,String value ) {
        if( header == null || key == null || ( key = key.trim() ).length() <= 0 ||
            value == null || ( value = value.trim() ).length() <= 0 ) {
            return ;
        }
        ValueInfo info = ValueInfo.getValueInfo( header,key ) ;
        if( info == null ) {
            info = new ValueInfo() ;
            info.setKey( key ) ;
            header.add( info ) ;
        }
        info.putValue( value ) ;
    }
    
    /**
     * ヘッダを追加.
     * @param key キー名を設定します.
     * @param value ヘッダ要素を設定します.
     */
    public void addHeader( String key,String value ) {
        ValueInfo.add( header,key,value ) ;
    }
    
    /**
     * ヘッダを削除.
     * @param key キー名を設定します.
     */
    public void removeHeader( String key ) {
        ValueInfo.remove( header,key ) ;
    }
    
    /**
     * ヘッダを削除.
     * @param key キー名を設定します.
     * @param no 削除項番を設定します.
     */
    public void removeHeader( String key,int no ) {
        ValueInfo.remove( header,key,no ) ;
    }
    
    /**
     * ヘッダを取得.
     * @param key キー名を設定します.
     * @return String[] 要素群が返されます.
     */
    public String[] getHeader( String key ) {
        return ValueInfo.get( header,key ) ;
    }
    
    /**
     * ヘッダを取得.
     * @param key キー名を設定します.
     * @param no 取得項番を設定します.
     * @return String 要素が返されます.
     */
    public String getHeader( String key,int no ) {
        return ValueInfo.get( header,key,no ) ;
    }
    
    /**
     * ヘッダサイズを取得.
     * @param key キー名を設定します.
     * @return int キー名に対する要素数が返されます.
     */
    public int headerSize( String key ) {
        return ValueInfo.size( header,key ) ;
    }
    
    /**
     * ヘッダサイズを取得.
     * @return int 登録されているヘッダ数が返されます.
     */
    public int headerSize() {
        return ValueInfo.size( header ) ;
    }
    
    /** 変換コード. */
    private static final String CHARSET = "ISO-8859-1" ;
    
    /**
     * ヘッダ情報をバイナリ変換.
     * @return byte[] バイナリ情報が返されます.
     * @exception Exception 例外.
     */
    public byte[] convertHeaderByMaachangCometProxy() throws Exception {
        if( method == null || url == null || version == null ||
            header == null || header.size() <= 0 ) {
            return null ;
        }
        ArrayBinary ret = new ArrayBinary() ;
        // メソッド名.
        putValue( ret,method ) ;
        // URLパス.
        putValue( ret,url ) ;
        // HTTPバージョン.
        putValue( ret,version ) ;
        int len = header.size() ;
        // ヘッダ数.
        ret.write( ConvertParam.convertInt( len ) ) ;
        for( int i = 0 ; i < len ; i ++ ) {
            ValueInfo val = header.get( i ) ;
            if( val == null || val.getKey().length() <= 0 ) {
                continue ;
            }
            int lenJ = val.size() ;
            // ヘッダキー名.
            putValue( ret,val.getKey() ) ;
            // ヘッダキー名に対する要素数.
            ret.write( ConvertParam.convertInt( lenJ ) ) ;
            String[] vals = val.getValue() ;
            for( int j = 0 ; j < lenJ ; j ++ ) {
                // ヘッダ要素.
                putValue( ret,vals[ j ] ) ;
            }
        }
        return ret.getBinary() ;
    }
    
    /**
     * １つの要素をセット.
     */
    private static final void putValue( ArrayBinary out,String value )
        throws Exception {
        if( value == null || ( value = value.trim() ).length() <= 0 ) {
            out.write( ConvertParam.convertInt( 0 ) ) ;
        }
        else {
            byte[] tmp = value.getBytes( CHARSET ) ;
            out.write( ConvertParam.convertInt( tmp.length ) ) ;
            out.write( tmp ) ;
        }
    }
    
    /**
     * 内容をHTTPリクエストプロトコル形式に変換して、OutputStreamに出力.
     * @param closeMode [true]の場合、クローズモードで送信します.
     * @param out 対象の出力オブジェクトを設定します.
     */
    public void outputStream( boolean closeMode,OutputStream out )
        throws Exception {
        outputHeader( closeMode,out,this ) ;
        if( body != null && body.length > 0 ) {
            out.write( body ) ;
        }
        out.flush() ;
    }
    
    /**
     * ヘッダ内容をバイナリ変換.
     */
    private static final void outputHeader( boolean closeMode,OutputStream out,RequestInfo req )
        throws Exception {
        String enter = "\r\n" ;
        String url = req.getUrl() ;
        StringBuilder buf = null ;
        buf = new StringBuilder() ;
        buf.append( req.getMethod() ).append( " " ).append( url ).
            append( " " ).append( req.version ).append( enter ) ;
        ArrayList<ValueInfo> headers = req.header ;
        int len = req.header.size() ;
        boolean addFlag = false ;
        for( int i = 0 ; i < len ; i ++ ) {
            if( addFlag == true ) {
                buf.append( enter ) ;
            }
            addFlag = false ;
            ValueInfo value = headers.get( i ) ;
            if( value == null || value.size() <= 0 ) {
                continue ;
            }
            if( closeMode == true ) {
                if( "Keep-Alive".equals( value.getKey() ) ) {
                    continue ;
                }
                else if( "Connection".equals( value.getKey() ) ) {
                    addFlag = true ;
                    buf.append( "Connection: close" ) ;
                    continue ;
                }
            }
            int lenJ = value.size() ;
            String[] values = value.getValue() ;
            buf.append( value.getKey() ).append( ": " ) ;
            for( int j = 0 ; j < lenJ ; j ++ ) {
                if( j != 0 ) {
                    buf.append( "," ) ;
                }
                buf.append( values[ j ] ) ;
            }
            addFlag = true ;
        }
        buf.append( enter ) ;
        buf.append( enter ) ;
        String s = buf.toString() ;
        buf = null ;
        out.write( s.getBytes( "ISO-8859-1" ) ) ;
        s = null ;
    }
    
}
