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

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketTimeoutException;

import org.maachang.proxy.engine.net.RequestInfo;
import org.maachang.proxy.engine.net.ResponseInfo;
import org.maachang.proxy.engine.net.http.CloseClientException;
import org.maachang.proxy.engine.net.http.ConnectionInfo;

/**
 * １つのHTTP接続情報を保持するオブジェクト.
 * 
 * @version 2008/03/25
 * @author masahito suzuki
 * @since MaachangProxy 1.00
 */
public class HttpConnector {
    
    /**
     * ソケット情報.
     */
    private Socket socket = null ;
    
    /**
     * 入力情報.
     */
    private InputStream inputStream = null ;
    
    /**
     * 出力情報.
     */
    private OutputStream outputStream = null ;
    
    /**
     * コネクションオブジェクト.
     */
    private ConnectionInfo connectionInfo = null ;
    
    /**
     * 最終アクセス時間.
     */
    private long lastAccess = -1L ;
    
    /**
     * コンストラクタ.
     */
    public HttpConnector() {
        
    }
    
    /**
     * コンストラクタ.
     * @param socket 対象のソケットを生成します.
     * @param parent 対象の親オブジェクトを設定します.
     * @exception Exception 例外.
     */
    public HttpConnector( Socket socket ) throws Exception {
        if( socket == null ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        this.socket = socket ;
        this.inputStream = new BufferedInputStream( socket.getInputStream() ) ;
        this.outputStream = new BufferedOutputStream( socket.getOutputStream() ) ;
        this.connectionInfo = null ;
        this.lastAccess = System.currentTimeMillis() ;
        
    }
    
    /**
     * デストラクタ.
     */
    protected void finalize() throws Exception {
        this.destroy() ;
    }
    
    /**
     * オブジェクト破棄.
     */
    public synchronized void destroy() {
        if( inputStream != null ) {
            try {
                inputStream.close() ;
            } catch( Exception e ) {
            }
        }
        if( outputStream != null ) {
            try {
                outputStream.close() ;
            } catch( Exception e ) {
            }
        }
        if( socket != null ) {
            try {
                socket.close() ;
            } catch( Exception e ) {
            }
        }
        socket = null ;
        inputStream = null ;
        outputStream = null ;
        lastAccess = -1L ;
        connectionInfo = null ;
    }
    
    /**
     * 接続先のコネクションオブジェクトをセット.
     * @param connectionInfo 対象のコネクション情報を設定します.
     */
    public void setConnectionInfo( ConnectionInfo connectionInfo ) {
        this.connectionInfo = connectionInfo ;
    }
    
    /**
     * 最終アクセス時間を更新.
     */
    public void setLastAccess() {
        lastAccess = System.currentTimeMillis() ;
    }
    
    /**
     * 最終アクセス時間を取得.
     * @return long 最終アクセス時間が返されます.
     */
    public long getLastAccess() {
        return lastAccess ;
    }
    
    /**
     * HTTPリクエスト送信.
     * @param request 対象のリクエスト情報を送信します.
     * @exception Exception 例外.
     */
    public void send( RequestInfo request )
        throws Exception {
        if( isUse() == false ) {
            throw new ExpireServerConnectException( "既にオブジェクトはクローズされています" ) ;
        }
        request.outputStream( isLocal(),outputStream ) ;
    }
    
    /**
     * HTTPリクエスト受信.
     * @param out OutputStreamが直接指定されている場合は、この条件に直接内容を設定します.
     * @param res 対象のレスポンス情報を設定します.
     * @return ResponseInfo レスポンス情報が返されます.
     * @exception Exception 例外.
     */
    public ResponseInfo receive(OutputStream out,ResponseInfo res) throws Exception {
        try {
            if( isUse() == false ) {
                throw new ExpireServerConnectException( "既にオブジェクトはクローズされています" ) ;
            }
            // ヘッダを取得.
            if( res == null ) {
                res = ReadHttpResponse.readResponseByHeader( inputStream ) ;
            }
            // Body情報を取得.
            else {
                if( out == null ) {
                    out = new ByteArrayOutputStream() ;
                }
                ReadHttpResponse.readResponseByBody( out,res,inputStream ) ;
                out = null ;
            }
            return res ;
        } catch( SocketTimeoutException se ) {
            if( connectionInfo.isCloseSocket() == true ) {
                this.destroy() ;
                throw new CloseClientException( "クライアントが切断されました" ) ;
            }
            return null ;
        } catch( Exception e ) {
            this.destroy() ;
            throw e ;
        }
    }
    
    /**
     * オブジェクトが利用可能かチェック.
     * @return boolean [true]の場合、利用可能です.
     */
    public boolean isUse() {
        return ( socket != null && inputStream != null && outputStream != null ) ;
    }
    
    /**
     * オブジェクト最終利用から、指定秒経過したかチェックします.
     * @param time チェック対象の指定秒を設定します.
     * @return [true]の場合、指定秒から経過しています.
     */
    public boolean isPassage( long time ) {
        return ( lastAccess == -1L || lastAccess + time <= System.currentTimeMillis() ) ;
    }
    
    /**
     * 接続条件がローカルであるかチェック.
     */
    public boolean isLocal() {
        if( socket == null ) {
            return false ;
        }
        InetAddress addr = socket.getInetAddress() ;
        return "127.0.0.1".equals( addr.getHostAddress() ) ;
    }
}
