package org.maachang.proxy.engine.net.server;

import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.maachang.proxy.engine.net.http.ConnectionInfo;

/**
 * １つのサーバと接続しているHTTPオブジェクト管理.
 * 
 * @version 2008/03/25
 * @author masahito suzuki
 * @since MaachangProxy 1.00
 */
public class HttpPoolingConnector {
    
    /** 受信タイムアウト. */
    private static final int RECEIVE_TIMEOUT = 5000 ;
    
    /** I/Oバッファ長. */
    private static final int BUFFER = 131072 ;
    
    /** コネクションタイムアウト値. */
    //private static final int CONNECT_TIMEOUT = 5000 ;
    
    /** SoLinger値.*/
    private static final int LINGER_TIME = 15 ;
    
    /** 利用不可コネクションタイム. */
    private static final long CLOSE_CONNECT_TIME = 3500L ;
    
    /**
     * Proxy情報.
     */
    private ProxyInfo proxy = null ;
    
    /**
     * コネクション管理.
     */
    private List<HttpConnector> conns = null ;
    
    /**
     * コンストラクタ.
     */
    private HttpPoolingConnector() {
        
    }
    
    /**
     * コンストラクタ.
     * <BR>
     * @param proxy 対象のプロキシ情報を設定します.
     * @exception Exception 例外.
     */
    public HttpPoolingConnector( ProxyInfo proxy ) throws Exception {
        if( proxy == null || proxy.isUse() == false ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        this.proxy = proxy ;
        this.conns = Collections.synchronizedList( new ArrayList<HttpConnector>() ) ;
        this.proxy.setHttpPoolingConnector( this ) ;
    }
    
    /**
     * デストラクタ.
     */
    protected void finalize() throws Exception {
        this.destroy() ;
    }
    
    /**
     * オブジェクト破棄.
     */
    public void destroy() {
        if( conns != null ) {
            int len = conns.size() ;
            for( int i = 0 ; i < len ; i ++ ) {
                HttpConnector con = conns.get( i ) ;
                if( con != null ) {
                    con.destroy() ;
                }
            }
            conns.clear() ;
        }
        conns = null ;
        proxy = null ;
    }
    
    /**
     * 有効なコネクションを取得.
     * @param conn 対象のクライアントコネクションオブジェクトを設定します.
     * @return HttpConnector 有効なコネクションオブジェクトが返されます.
     * @exception Exception 例外.
     */
    public HttpConnector getConnector( ConnectionInfo conn )
        throws Exception {
        if( conn == null ) {
            throw new IOException( "引数は不正です" ) ;
        }
        HttpConnector ret = getToRemove() ;
        if( ret == null ) {
            ret = createConnector() ;
        }
        if( ret == null ) {
            throw new IOException( "コネクションの生成に失敗しました" ) ;
        }
        ret.setConnectionInfo( conn ) ;
        return ret ;
    }
    
    /**
     * 有効なコネクションをリリース.
     * @param httpConnector リリース対象のHTTPコネクターを設定します.
     */
    public void releaseConnector( HttpConnector httpConnector ) {
        if( httpConnector == null || httpConnector.isUse() == false ) {
            return ;
        }
        httpConnector.setConnectionInfo( null ) ;
        httpConnector.setLastAccess() ;
        conns.add( httpConnector ) ;
    }
    
    /**
     * 空きコネクション数を取得.
     * @return int 空きコネクション数が返されます.
     * @exception Exception 例外.
     */
    public int getUseLength()
        throws Exception {
        if( isUse() == false ) {
            throw new IOException( "オブジェクトは既に破棄されています" ) ;
        }
        return conns.size() ;
    }
    
    /**
     * オブジェクトが有効かチェック.
     * @return boolean [true]の場合有効です.
     */
    public boolean isUse() {
        return ( proxy != null && proxy.isUse() ) ;
    }
    
    /**
     * コネクションが利用可能かチェック.
     * @param conn 対象のコネクションを設定します.
     * @return boolean [true]の場合は、利用可能です.
     */
    public boolean checkConnect( HttpConnector conn ) {
        return ( conn != null &&
            conn.isUse() == true &&
            conn.isPassage( CLOSE_CONNECT_TIME ) == false ) ;
    }
    
    /**
     * １つのコネクションを定義.
     */
    private HttpConnector createConnector() throws Exception {
        InetAddress addr = InetAddress.getByName( proxy.getDestUrl() ) ;
        //Socket socket = new Socket() ;
        Socket socket = new Socket( addr,proxy.getPort() ) ;
        socket.setSendBufferSize( BUFFER ) ;
        socket.setReceiveBufferSize( BUFFER ) ;
        socket.setKeepAlive( true ) ;
        socket.setTcpNoDelay( true ) ;
        socket.setReuseAddress( true ) ;
        socket.setSoLinger( true,LINGER_TIME ) ;
        socket.setSoTimeout( RECEIVE_TIMEOUT ) ;
        //socket.connect( new InetSocketAddress( addr,proxy.getPort() ),CONNECT_TIMEOUT ) ;
        return new HttpConnector( socket ) ;
    }
    
    /**
     * 先頭のコネクションを取得.
     */
    private HttpConnector getToRemove() {
        HttpConnector ret = null ;
        for( ;; ) {
            if( conns.size() <= 0 ) {
                return null ;
            }
            ret = conns.remove( 0 ) ;
            if( checkConnect( ret ) == true ) {
                return ret ;
            }
        }
    }
    
}
