package org.maachang.comet.net ;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

import org.maachang.comet.httpd.HttpConnectionInfo;
import org.maachang.comet.httpd.HttpSocket;

/**
 * HttpSocket.
 * 
 * @version 2007/11/17
 * @author  masahito suzuki
 * @since   MaachangComet 1.00
 */
public class NetHttpSocketImpl implements HttpSocket {
    
    /**
     * LOG.
     */
    //private static final Log LOG = LogFactory.getLog( HttpSocket.class ) ;
    
    /**
     * 基本受信タイムアウト(30秒).
     */
    public static final int RECEIVE_TIMEOUT = 30000 ;
    
    private static final long DELETE_SOCKET_TIME = 60000L ;
    private static final int BUFFER = 131072 ;
    private static final int LINGER = 15 ;
    private Socket socket = null ;
    private InputStream inputStream = null ;
    private OutputStream outputStream = null ;
    private HttpConnectionInfo connectionInfo = null ;
    private long time = -1L ;
    private boolean isCloseSocket = false ;
    private Object ioSync = new Object() ;
    private long startReceiveTime = -1L ;
    
    /**
     * コンストラクタ.
     */
    private NetHttpSocketImpl() { }
    
    /**
     * コンストラクタ.
     * <BR><BR>
     * ソケット条件を設定してオブジェクトを設定します.
     * <BR>
     * @param socket 対象のソケット情報を設定します.
     * @exception Exception 例外.
     */
    protected NetHttpSocketImpl( Socket socket ) throws Exception {
        if( socket == null || socket.isClosed() == true ) {
            throw new IllegalArgumentException( "コネクションは不正です" ) ;
        }
        this.socket = socket ;
        this.inputStream = null ;
        this.outputStream = null ;
        this.socket.setSendBufferSize( BUFFER ) ;
        this.socket.setReceiveBufferSize( BUFFER ) ;
        this.socket.setKeepAlive( true ) ;
        this.socket.setTcpNoDelay( true ) ;
        this.socket.setReuseAddress( true ) ;
        this.socket.setSoLinger( false,LINGER ) ;
        this.socket.setSoTimeout( RECEIVE_TIMEOUT ) ;
        this.time = System.currentTimeMillis() ;
        this.connectionInfo = null ;
    }
    
    /**
     * デストラクタ.
     */
    protected void finalize() throws Exception {
        this.destroy() ;
    }
    
    /**
     * オブジェクト破棄.
     */
    public void destroy() {
        //if( this.socket != null ) {
        //    LOG.info( "closeSocket:" + this.socket.getInetAddress().getHostAddress() +
        //":" + this.socket.getPort() ) ;
        //}
        if( this.inputStream != null ) {
            try {
                this.inputStream.close() ;
            } catch( Exception e ) {
            }
        }
        if( this.outputStream != null ) {
            try {
                this.outputStream.flush() ;
            } catch( Exception e ) {
            }
            try {
                this.outputStream.close() ;
            } catch( Exception e ) {
            }
        }
        if( this.socket != null ) {
            try {
                this.socket.close() ;
            } catch( Exception e ) {
            }
        }
        if( connectionInfo != null ) {
            connectionInfo.destroy() ;
        }
        this.socket = null ;
        this.inputStream = null ;
        this.outputStream = null ;
        this.time = -1L ;
    }
    
    /**
     * コネクションオブジェクトを設定.
     * <BR><BR>
     * コネクションオブジェクトを設定します.
     * <BR>
     * @param connectionInfo コネクションオブジェクトを設定します.
     */
    public synchronized void setConnectionInfo( HttpConnectionInfo connectionInfo ) {
        if( connectionInfo == null ) {
            return ;
        }
        this.connectionInfo = connectionInfo ;
    }
    
    /**
     * コネクションオブジェクトを取得.
     * <BR><BR>
     * コネクションオブジェクトを設定します.
     * <BR>
     * @return HttpConnectionInfo コネクションオブジェクトが返されます.
     */
    public synchronized HttpConnectionInfo getConnectionInfo() {
        return this.connectionInfo ;
    }
    
    /**
     * 更新処理.
     * <BR><BR>
     * このオブジェクトを再利用するための更新処理を実施します.
     * <BR>
     * @exception Exception 例外.
     */
    public void update() throws Exception {
        if( isClosed() == true ) {
            this.time = -1L ;
        }
        else {
            this.time = System.currentTimeMillis() ;
        }
    }
    
    /**
     * 受信情報が存在するかチェック.
     * <BR><BR>
     * 受信情報が存在するかチェックします.
     * <BR>
     * @return boolean [true]の場合、受信情報が存在します.
     * @exception Exception 例外.
     */
    public boolean isReceive() throws Exception {
        if( isClosed() == true ) {
            throw new IOException( "既にクローズされています" ) ;
        }
        if( this.time + DELETE_SOCKET_TIME <= System.currentTimeMillis() ) {
            throw new IOException( "コネクション未使用時間が一定時間を超しました" ) ;
        }
        boolean ret = false ;
        if( this.inputStream().available() > 0 ) {
            ret = true ;
        }
        return ret ;
    }
    
    /**
     * ソケットオブジェクトを取得.
     * <BR><BR>
     * ソケットオブジェクトを取得します.
     * <BR>
     * @return Socket ソケットオブジェクトが返されます.
     */
    public Socket socket() {
        if( isClosed() == true ) {
            return null ;
        }
        return this.socket ;
    }
    
    /**
     * ソケット受信ストリームを取得.
     * <BR><BR>
     * ソケット受信ストリームを取得します.
     * <BR>
     * @return InputStream ソケット受信ストリームが返されます.
     * @exception Exception 例外.
     */
    public InputStream inputStream()
        throws Exception {
        if( isClosed() == true ) {
            return null ;
        }
        InputStream ret = null ;
        synchronized( ioSync ) {
            if( this.inputStream == null ) {
                this.inputStream = this.socket.getInputStream() ;
            }
            ret = this.inputStream ;
        }
        return ret ;
    }
    
    /**
     * ソケット送信ストリームを取得.
     * <BR><BR>
     * ソケット送信ストリームを取得します.
     * <BR>
     * @return OutputStream ソケット送信ストリームが返されます.
     */
    public OutputStream outputStream()
        throws Exception {
        if( isClosed() == true ) {
            return null ;
        }
        OutputStream ret = null ;
        synchronized( ioSync ) {
            if( this.outputStream == null ) {
                this.outputStream = this.socket.getOutputStream() ;
            }
            ret = this.outputStream ;
        }
        return ret ;
    }
    
    /**
     * 最終更新時間を取得.
     * <BR><BR>
     * 最終更新時間を取得します.
     * <BR>
     * @return long 最終更新時間が返されます.
     */
    public long time() {
        if( isClosed() == true ) {
            return -1L ;
        }
        return this.time ;
    }
    
    /**
     * このソケットを利用後にクローズするか設定.
     * <BR><BR>
     * このソケットを利用後にクローズする場合は[true]を設定します.
     * <BR>
     * @param flag [true]の場合、利用終了後にクローズします.
     */
    public void setCloseFlag( boolean flag ) {
        this.isCloseSocket = flag ;
    }
    
    /**
     * このソケットを利用後にクローズする場合は[true]が返されます.
     * <BR>
     * @return boolean [true]の場合、クローズされます.
     */
    public boolean isCloseFlag() {
        return isCloseSocket ;
    }
    
    /**
     * クローズされているかチェック.
     * <BR>
     * @return boolean [true]の場合、このオブジェクトはクローズされています.
     */
    public boolean isClosed() {
        boolean ret = false ;
        try {
            if( this.socket == null || this.socket.isClosed() == true ) {
                ret = true ;
            }
        } catch( Exception e ) {
            ret = true ;
        }
        return ret ;
    }
    
    /**
     * 受信開始時に呼び出す.
     */
    public void startReceive() {
        if( startReceiveTime == -1L ) {
            startReceiveTime = System.currentTimeMillis() ;
        }
    }
    
    /**
     * 受信(処理)完了時に呼び出す.
     */
    public void endReceive() {
        startReceiveTime = -1L ;
    }
    
    /**
     * 現在までの受信時間を返す.
     * @return long 受信開始時間から現在までの時間を取得.
     */
    public long getReceiveStartTimeToNow() {
        if( startReceiveTime == -1L ) {
            return -1L ;
        }
        return System.currentTimeMillis() - startReceiveTime ;
    }
}

