package org.maachang.comet.net.nio ;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;

import org.maachang.comet.net.ssl.SslElement;
import org.maachang.comet.net.ssl.SslOption;

/**
 * Nio受信データ要素.
 * 
 * @version 2008/05/28
 * @author  masahito suzuki
 * @since   MaachangComet 1.1B
 */
public class NioElement {
    
    /**
     * SelectionKey.
     */
    private SelectionKey key = null ;
    
    /**
     * SocketChannel.
     */
    private SocketChannel channel = null ;
    
    /**
     * 受信バッファ.
     */
    private ReceiveBuffer buffer = null ;
    
    /**
     * 送信バッファ.
     */
    private WriteDataQueue writeBuffer = null ;
    
    /**
     * 書き込み可能を示すバッファ.
     */
    private IsWriteQueue isWriteQueue = null ;
    
    /**
     * 最新時間.
     */
    private long time = -1L ;
    
    /**
     * 処理中フラグ.
     */
    private boolean executionFlag = false ;
    
    /**
     * クローズフラグ.
     */
    private boolean closeFlag = false ;
    
    /**
     * 付加オブジェクト.
     */
    private Object object = null ;
    
    /**
     * SSL要素.
     */
    private SslElement sslElement = null ;
    
    /**
     * SSLオプション.
     */
    private SslOption sslOption = null ;
    
    /**
     * 同期オブジェクト.
     */
    private final Object sync = new Object() ;
    
    /**
     * コンストラクタ.
     */
    private NioElement() {
        
    }
    
    /**
     * コンストラクタ.
     * @param channel 対象のチャネル情報を設定します.
     * @exception Exception 例外.
     */
    public NioElement( SocketChannel channel ) throws Exception {
        this( channel,null ) ;
    }
    
    /**
     * コンストラクタ.
     * @param channel 対象のチャネル情報を設定します.
     * @param queue 書き込み通知キューを設定します.
     * @exception Exception 例外.
     */
    public NioElement( SocketChannel channel,IsWriteQueue queue ) throws Exception {
        if( channel == null || channel.isConnected() == false ) {
            throw new IOException( "ソケットチャネルは不正です" ) ;
        }
        this.channel = channel ;
        this.isWriteQueue = queue ;
        this.writeBuffer = new WriteDataQueue() ;
        this.buffer = new ReceiveBuffer() ;
        this.time = System.currentTimeMillis() ;
        this.executionFlag = false ;
        this.closeFlag = false ;
        this.object = null ;
    }
    
    /**
     * デストラクタ.
     */
    protected void finalize() throws Exception {
        destroy() ;
    }
    
    /**
     * オブジェクト破棄.
     */
    protected void destroy() {
        synchronized( sync ) {
            if( sslElement != null ) {
                sslElement.destroy() ;
            }
            sslElement = null ;
            if( key != null ) {
                key.attach( null ) ;
                try {
                    key.cancel() ;
                } catch( Exception e ) {
                }
            }
            key = null ;
            if( channel != null ) {
                try {
                    channel.close() ;
                } catch( Exception e ) {
                }
            }
            channel = null ;
            if( buffer != null ) {
                buffer.destroy() ;
            }
            buffer = null ;
            if( writeBuffer != null ) {
                writeBuffer.destroy() ;
            }
            writeBuffer = null ;
            isWriteQueue = null ;
            object = null ;
            executionFlag = false ;
            closeFlag = false ;
            sslOption = null ;
        }
    }
    
    /**
     * 書き込み開始キューを設定.
     * @param isWrite 書き込み開始キューを設定します.
     */
    protected void setIsWriteQueue( IsWriteQueue isWriteQueue ) {
        synchronized( sync ) {
            this.isWriteQueue = isWriteQueue ;
        }
    }
    
    /**
     * SSL条件を追加.
     * @param sslOption 対象のSSLオプションを設定します.
     * @param sslElement 対象のSSL要素を追加します.
     */
    protected void setSsl( SslOption sslOption ) {
        synchronized( sync ) {
            this.sslOption = sslOption ;
            this.sslElement = null ;
        }
    }
    
    /**
     * SSL要素を取得.
     * @return SslElement SSL要素が返されます.
     */
    public SslElement getSslElement() {
        SslElement ret ;
        synchronized( sync ) {
            ret = sslElement ;
        }
        return ret ;
    }
    
    /**
     * SSL要素を取得.
     * @return SslElement SSL要素が返されます.
     */
    public SslOption getSslOption() {
        SslOption ret ;
        synchronized( sync ) {
            ret = sslOption ;
        }
        return ret ;
    }
    
    /**
     * 新しいSSLオプションを生成.
     * @exception Exception 例外.
     */
    public void createSsl() throws Exception {
        synchronized( sync ) {
            if( sslOption != null && sslElement == null ) {
                try {
                    sslElement = new SslElement( this,sslOption.getSSLEngine() ) ;
                } catch( Exception e ) {
                    sslElement = null ;
                    throw e ;
                }
            }
        }
    }
    
    /**
     * SSL要素をクリア.
     */
    public void clearSsl() {
        synchronized( sync ) {
            sslElement = null ;
        }
    }
    
    /**
     * SelectionKeyを設定.
     * @param key SelectionKeyを設定します.
     */
    public void setKey( SelectionKey key ) {
        synchronized( sync ) {
            this.key = key ;
        }
    }
    
    /**
     * SelectionKeyを取得.
     * @return SelectionKey SelectionKeyが返されます.
     */
    public SelectionKey getKey() {
        SelectionKey ret ;
        synchronized( sync ) {
            ret = key ;
        }
        return ret ;
    }
    
    /**
     * SocketChannelを取得.
     * @return SocketChannel SocketChannelが返されます.
     */
    public SocketChannel getChannel() {
        SocketChannel ret ;
        synchronized( sync ) {
            ret = channel ;
        }
        return ret ;
    }
    
    /**
     * 受信バッファを取得.
     * @return ReceiveBuffer 受信バッファを取得します.
     */
    public ReceiveBuffer getBuffer() {
        ReceiveBuffer ret ;
        synchronized( sync ) {
            ret = buffer ;
        }
        return ret ;
    }
    
    /**
     * 受信バッファが存在するかチェック.
     * @return boolean [true]の場合、存在します.
     */
    public boolean isBuffer() {
        boolean ret ;
        synchronized( sync ) {
            ret = buffer.length() > 0 ;
        }
        return ret ;
    }
    
    /**
     * 書き込み開始/再開を通知.
     */
    public void restartWrite() {
        IsWriteQueue queue = null ;
        boolean flg = false ;
        synchronized( sync ) {
            if( isWriteQueue != null &&
                key != null && ( key.interestOps() & SelectionKey.OP_WRITE ) == 0 ) {
                queue = isWriteQueue ;
                flg = true ;
            }
        }
        if( flg ) {
            queue.append( this ) ;
            synchronized( sync ) {
                key.selector().wakeup() ;
            }
        }
    }
    
    /**
     * 書き込みバッファオブジェクトを取得.
     * @return WriteDataQueue 書き込みバッファオブジェクトが返されます.
     */
    public WriteDataQueue getWriteDataQueue() {
        WriteDataQueue ret ;
        synchronized( sync ) {
            ret = writeBuffer ;
        }
        return ret ;
    }
    
    /**
     * 書き込みバッファに情報を追加.
     * @param buffer 対象のバッファ情報を設定します.
     */
    public void setWriteBuffer( ByteBuffer buffer ) {
        WriteDataQueue buf ;
        synchronized( sync ) {
            buf = writeBuffer ;
        }
        buf.append( buffer ) ;
    }
    
    /**
     * 書き込みバッファに情報を追加.
     * @param buffer 対象のバッファ情報を設定します.
     */
    public void setWriteBufferHead( ByteBuffer buffer ) {
        WriteDataQueue buf ;
        synchronized( sync ) {
            buf = writeBuffer ;
        }
        buf.appendHead( buffer ) ;
    }
    
    /**
     * 書き込みバッファを取得.
     * @return ByteBuffer バッファ情報が返されます.
     */
    public ByteBuffer getWriteBuffer() {
        WriteDataQueue buf ;
        synchronized( sync ) {
            buf = writeBuffer ;
        }
        return buf.getQueue() ;
    }
    
    /**
     * 書き込みバッファ数を取得.
     * @return int 書き込みバッファ数が返されます.
     */
    public int getWriteSize() {
        WriteDataQueue buf ;
        synchronized( sync ) {
            buf = writeBuffer ;
        }
        return buf.size() ;
    }
    
    /**
     * クローズフラグをセット.
     * @param mode [true]の場合、クローズとなります.
     */
    public void setCloseFlag( boolean mode ) {
        synchronized( sync ) {
            closeFlag = mode ;
        }
    }
    
    /**
     * クローズフラグを取得.
     * @return boolean [true]の場合、クローズです.
     */
    public boolean isCloseFlag() {
        boolean ret ;
        synchronized( sync ) {
            ret = closeFlag ;
        }
        return ret ;
    }
    
    /**
     * 更新処理.
     */
    public void update() {
        synchronized( sync ) {
            time = System.currentTimeMillis() ;
        }
    }
    
    /**
     * 最新時間を取得.
     * @return long 最新時間が返されます.
     */
    public long getTime() {
        long ret ;
        synchronized( sync ) {
            ret = time ;
        }
        return ret ;
    }
    
    /**
     * 処理中フラグを設定.
     * @param flg [true]の場合、処理中フラグをONにします.
     */
    protected void setExecutionFlag( boolean flg ) {
        synchronized( sync ) {
            this.executionFlag = flg ;
        }
        if( flg == false ) {
            update() ;
        }
    }
    
    /**
     * 処理中フラグを取得.
     * @return boolean [true]の場合、処理中です.
     */
    protected boolean isExecutionFlag() {
        boolean ret ;
        synchronized( sync ) {
            ret = executionFlag ;
        }
        return ret ;
    }
    
    /**
     * 付加オブジェクトを設定.
     * @param object 付加オブジェクトを設定します.
     */
    public void setObject( Object object ) {
        synchronized( sync ) {
            this.object = object ;
        }
    }
    
    /**
     * 付加オブジェクトを取得.
     * @return Object 付加オブジェクトが返されます.
     */
    public Object getObject() {
        Object ret ;
        synchronized( sync ) {
            ret = object ;
        }
        return ret ;
    }
    
    /**
     * オブジェクトが有効かチェック.
     * @return boolean [true]の場合、有効です.
     */
    public boolean isUse() {
        boolean ret ;
        synchronized( sync ) {
            ret = ( channel != null && channel.socket() != null ) ;
        }
        if( ret == false ) {
            synchronized( sync ) {
                if( sslElement != null ) {
                    sslElement.destroy() ;
                }
                sslElement = null ;
                if( buffer != null ) {
                    buffer.destroy() ;
                }
                buffer = null ;
                if( writeBuffer != null ) {
                    writeBuffer.destroy() ;
                }
                writeBuffer = null ;
            }
        }
        return ret ;
    }
    
    /**
     * 同期オブジェクトを取得.
     * @return Object 同期オブジェクトが返されます.
     */
    public Object getSync() {
        return sync ;
    }
}
