package org.maachang.connector ;

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

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.maachang.util.ConvertParam;

/**
 * ConnectorSession.
 * 
 * @version 2008/05/25
 * @author masahito suzuki
 * @since MaachangBase 1.00
 */
public class ConnectorSession {
    private static final Log LOG = LogFactory.getLog( ConnectorSession.class ) ;
    public static final int RECEIVE_TIMEOUT = 2500 ;
    protected static final long SESSION_TIMEOUT = 30000L ;
    protected static final String LOCAL_HOST = "127.0.0.1" ;
    private static final int BUFFER = 131072 ;
    private static final int LINGER = 5 ;
    private long updateTime = -1L ;
    private Socket socket = null ;
    private InputStream inputStream = null ;
    private OutputStream outputStream = null ;
    private InetAddress connectAddress = null ;
    private int connectPort = -1 ;
    private byte[] headerBinary = null ;
    
    private ConnectorSession() {
        
    }
    
    /**
     * コンストラクタ.
     * @param headerBinary 対象のヘッダバイナリを設定します.
     * @param socket AcceptされたSocketを設定します.
     * @exception Exception 例外.
     */
    public ConnectorSession( byte[] headerBinary,Socket socket ) throws Exception {
        if( socket == null || socket.isClosed() == true ) {
            throw new IllegalArgumentException( "接続は不正です" ) ;
        }
        try {
            this.socket = socket ;
            this.socket.setSendBufferSize( BUFFER ) ;
            this.socket.setReceiveBufferSize( BUFFER ) ;
            this.socket.setKeepAlive( true ) ;
            this.socket.setTcpNoDelay( true ) ;
            this.socket.setReuseAddress( true ) ;
            this.socket.setSoLinger( true,LINGER ) ;
            this.socket.setSoTimeout( RECEIVE_TIMEOUT ) ;
            this.inputStream = new BufferedInputStream( this.socket.getInputStream() ) ;
            this.outputStream = new BufferedOutputStream( this.socket.getOutputStream() ) ;
            this.connectAddress = socket.getInetAddress() ;
            this.connectPort = socket.getPort() ;
            this.updateTime = System.currentTimeMillis() ;
            this.headerBinary = headerBinary ;
        } catch( Exception e ) {
            if( socket != null ) {
                try {
                    socket.close() ;
                } catch( Exception ee ) {
                }
            }
            throw e ;
        }
    }
    
    /**
     * デストラクタ.
     */
    protected void finalize() throws Exception {
        this.destroy() ;
    }
    
    /**
     * オブジェクト破棄.
     */
    public synchronized void destroy() {
        if( this.connectAddress != null ) {
            if( LOG.isDebugEnabled() ) {
                LOG.debug( 
                    new StringBuilder().
                    append("## close:").
                    append( this.connectAddress.getHostAddress() ).
                    append( "/" ).append( this.connectPort ).toString() ) ;
            }
        }
        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 ) {
            }
        }
        this.socket = null ;
        this.inputStream = null ;
        this.outputStream = null ;
        this.connectAddress = null ;
        this.headerBinary = null ;
    }
    
    /**
     * 接続元のInetAddressを取得.
     * @return InetAddress 接続元のInetAddressが返されます.
     * @exception Exception 例外.
     */
    public synchronized InetAddress getInetAddress()
        throws Exception {
        if( isClosed() == true ) {
            throw new IOException( "オブジェクトはクローズされています" ) ;
        }
        return socket.getInetAddress() ;
    }
    
    /**
     * 接続元のポート番号を取得.
     * @return int 接続元のポート番号が返されます.
     * @exception Exception 例外.
     */
    public synchronized int getPort()
        throws Exception {
        if( isClosed() == true ) {
            throw new IOException( "オブジェクトはクローズされています" ) ;
        }
        return socket.getPort() ;
    }
    
    /**
     * ローカルInetAddressを取得.
     * @return InetAddress ローカルInetAddressが返されます.
     * @exception Exception 例外.
     */
    public synchronized InetAddress getLocalAddress()
        throws Exception {
        if( isClosed() == true ) {
            throw new IOException( "オブジェクトはクローズされています" ) ;
        }
        return socket.getLocalAddress() ;
    }
    
    /**
     * ローカルポート番号を取得.
     * @return int ローカルポート番号が返されます.
     * @exception Exception 例外.
     */
    public synchronized int getLocalPort()
        throws Exception {
        if( isClosed() == true ) {
            throw new IOException( "オブジェクトはクローズされています" ) ;
        }
        return socket.getLocalPort() ;
    }
    
    /**
     * 送信処理.
     * @param binary 送信対象のバイナリを設定します.
     * @exception Exception 例外.
     */
    public synchronized void send( byte[] binary ) throws Exception {
        if( isClosed() == true ) {
            throw new IOException( "オブジェクトはクローズされています" ) ;
        }
        if( binary == null || binary.length <= 0 ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        this.outputStream.write( headerBinary ) ;
        this.outputStream.write( ConvertParam.convertInt( binary.length + headerBinary.length + 4 ) ) ;
        this.outputStream.write( binary ) ;
        this.outputStream.flush() ;
    }
    
    /**
     * オブジェクトが既に破棄されているかチェック.
     * @return boolean [true]の場合、オブジェクトはクローズされています.
     */
    public synchronized boolean isClosed() {
        boolean ret = false ;
        try {
            if( this.socket == null || this.socket.isBound() == false ||
                this.socket.isClosed() == true ) {
                this.destroy() ;
                ret = true ;
            }
            if( updateTime != Long.MAX_VALUE &&
                System.currentTimeMillis() >= SESSION_TIMEOUT + updateTime ) {
                ret = true ;
            }
            else {
                ret = false ;
            }
        } catch( Exception e ) {
            this.destroy() ;
            ret = true ;
        }
        return ret ;
    }
    
    /**
     * 受信情報が存在するかチェック.
     * @return boolean [true]が返された場合、受信情報は存在します.
     */
    protected synchronized boolean isReceive() throws Exception {
        if( isClosed() == true ) {
            throw new IOException( "既にクローズされています" ) ;
        }
        boolean ret = false ;
        if( this.inputStream().available() > 0 ) {
            ret = true ;
        }
        return ret ;
    }
    
    /**
     * セッション更新.
     */
    protected synchronized void updateTime() {
        updateTime = System.currentTimeMillis() ;
    }
    
    /**
     * 処理中更新.
     */
    protected synchronized void executionUpdateTime() {
        updateTime = Long.MAX_VALUE ;
    }
    
    /**
     * アクセスがローカルホストかチェック.
     */
    protected synchronized boolean isLocalHost() throws Exception {
        if( socket != null && socket.isClosed() == false ) {
            return LOCAL_HOST.equals( socket.getInetAddress().getHostAddress() ) ;
        }
        return false ;
    }
    
    /**
     * ソケットオブジェクトを取得.
     */
    protected synchronized Socket socket() {
        if( isClosed() == true ) {
            return null ;
        }
        return this.socket ;
    }
    
    /**
     * SocketのInputStreamを取得.
     */
    protected synchronized InputStream inputStream() {
        if( isClosed() == true ) {
            return null ;
        }
        return this.inputStream ;
    }
    
    /**
     * SocketのOutputStreamを取得.
     */
    protected synchronized OutputStream outputStream() {
        if( isClosed() == true ) {
            return null ;
        }
        return this.outputStream ;
    }
    
}

