package org.maachang.comet.net.nio ;

import java.nio.ByteBuffer;
import java.nio.channels.Channel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.Iterator;

import org.maachang.util.thread.LoopThread;


/**
 * Nio-SSL用受信サーバ群管理.
 * 
 * @version 2008/05/28
 * @author  masahito suzuki
 * @since   MaachangComet 1.1C
 */
class NioSslReceiveServerArray {
    
    /**
     * デフォルトサイズ.
     */
    private static final int MIN_SIZE = 1 ;
    
    /**
     * NioReceiveServer.
     */
    private NioSslReceiveServer[] recvs = null ;
    
    /**
     * 受信スレッド管理数.
     */
    private int size = -1 ;
    
    /**
     * シーケンスID.
     */
    private volatile int seq = 0 ;
    
    /**
     * コンストラクタ.
     */
    private NioSslReceiveServerArray() {
        
    }
    
    /**
     * コンストラクタ.
     * @param queue 対象のキューを設定します.
     * @param size 受信スレッド数を設定します.
     * @exception Exception 例外.
     */
    public NioSslReceiveServerArray( ReceiveLinkQueue queue,int size )
        throws Exception {
        if( queue == null ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        if( size <= MIN_SIZE ) {
            size = MIN_SIZE ;
        }
        NioSslReceiveServer[] rv = new NioSslReceiveServer[ size ] ;
        for( int i = 0 ; i < size ; i ++ ) {
            rv[ i ] = new NioSslReceiveServer( queue ) ;
        }
        this.recvs = rv ;
        this.size = size ;
        this.seq = 0 ;
    }
    
    /**
     * デストラクタ.
     */
    protected void finalize() throws Exception {
        destroy() ;
    }
    
    /**
     * オブジェクト破棄.
     */
    public synchronized void destroy() {
        if( recvs != null ) {
            int len = recvs.length ;
            for( int i = 0 ; i < len ; i ++ ) {
                if( recvs[ i ] != null ) {
                    recvs[ i ].destroy() ;
                }
                recvs[ i ] = null ;
            }
            recvs = null ;
        }
    }
    
    /**
     * Acceptされた要素を受信用にセット.
     * @param element 対象のElementを設定します.
     * @exception Exception 例外.
     */
    public synchronized void setElement( NioElement element )
        throws Exception {
        if( element == null ) {
            return ;
        }
        if( recvs == null ) {
            element.destroy() ;
            return ;
        }
        if( seq >= size ) {
            seq = 0 ;
        }
        recvs[ seq ].setElement( element ) ;
        seq ++ ;
    }
}

/**
 * 1つの受信ソケット.
 */
class NioSslReceiveServer extends LoopThread {
    
    /**
     * Selectorタイムアウト.
     */
    private static final int SELECTOR_TIMEOUT = 1 ;
    
    /**
     * Selector.
     */
    private Selector selector = null ;
    
    /**
     * 読み込み可能キュー.
     */
    private IsReadQueue isRecvQueue = null ;
    
    /**
     * 書き込み可能キュー.
     */
    private IsWriteQueue isWriteQueue = null ;
    
    /**
     * 受信処理キュー.
     */
    private ReceiveLinkQueue executionQueue = null ;
    
    /**
     * コンストラクタ.
     */
    private NioSslReceiveServer() {
        
    }
    
    /**
     * コンストラクタ.
     * @param queue 受信実行スレッドとリンクするキューを設定します.
     * @exception Exception 例外.
     */
    public NioSslReceiveServer( ReceiveLinkQueue executionQueue )
        throws Exception {
        if( executionQueue == null ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        this.selector = Selector.open() ;
        this.isRecvQueue = new IsReadQueue() ;
        this.isWriteQueue = new IsWriteQueue() ;
        this.executionQueue = executionQueue ;
        this.startThread() ;
    }
    
    /**
     * デストラクタ.
     */
    protected void finalize() throws Exception {
        this.destroy() ;
    }
    
    /**
     * オブジェクト破棄.
     */
    public void destroy() {
        super.stopThread() ;
    }
    
    /**
     * AcceptされたNioElementを受信用にセット.
     * @param element 対象のElementを設定します.
     * @exception Exception 例外.
     */
    public synchronized void setElement( NioElement element )
        throws Exception {
        element.setIsWriteQueue( isWriteQueue ) ;
        isRecvQueue.append( element ) ;
    }
    
    /**
     * オブジェクトクリア.
     */
    protected void clear() {
        if( this.selector != null ) {
            try {
                Iterator itr = this.selector.keys().iterator();
                while(itr.hasNext()){
                    try {
                        SelectionKey key = (SelectionKey)itr.next();
                        Channel c = key.channel();
                        c.close();
                    } catch( Exception ee ) {
                    }
                }
            } catch(Exception e) {}
        }
        this.selector = null ;
        this.isRecvQueue = null ;
        this.isWriteQueue = null ;
        this.executionQueue = null ;
    }
    
    /**
     * 実行処理.
     * @exception Exception 例外.
     */
    protected boolean execution() throws Exception {
        // selector処理.
        while ( selector.select( SELECTOR_TIMEOUT ) > 0 ) {
            //setSendRecv() ;
            Iterator itr = selector.selectedKeys().iterator() ;
            while(itr.hasNext()) {
                setSendRecv() ;
                SelectionKey key = ( SelectionKey )itr.next() ;
                itr.remove() ;
                
                // use-socket.
                if( key.isValid() ) {
                    boolean isReadable = key.isReadable() ;
                    boolean isWritable = key.isWritable() ;
                    
                    // read.
                    if( isReadable ) {
                        NioElement em = null ;
                        ByteBuffer buffer = null ;
                        try {
                            em = ( NioElement )key.attachment() ;
                            buffer = em.getSslElement().getRecvBuffer() ;
                            
                            ///////////////////////////////////////////////////
                            // HandShake処理の場合.
                            ///////////////////////////////////////////////////
                            if( em.getSslElement().isSendEndHandShake() == false ) {
                                // 受信情報を取得.
                                int len = em.getChannel().read( buffer ) ;
                                if( len <= 0 ) {
                                    if( len <= -1 ) {
                                        // データが0バイトの場合は、クライアント
                                        // コネクション切断なので、要素を破棄する.
                                        em.destroy() ;
                                        continue ;
                                    }
                                }
                                // ハンドシェイク処理を行う.
                                else {
                                    em.getSslElement().handShake( buffer ) ;
                                    // 受信残りデータが存在する場合、処理する.
                                    while( em.getSslElement().isUseReadData( buffer ) ) {
                                        em.getSslElement().handShake( buffer ) ;
                                    }
                                    // OP_READ/OP_WRITEを有効にする.
                                    key.interestOps( SelectionKey.OP_READ | SelectionKey.OP_WRITE ) ;
                                }
                            }
                            ///////////////////////////////////////////////////
                            // 通常通信の場合.
                            ///////////////////////////////////////////////////
                            else {
                                // 差分データ読み込み処理.
                                if( em.getSslElement().read( buffer ) == false ) {
                                    // データが0バイトの場合は、クライアント
                                    // コネクション切断なので、要素を破棄する.
                                    em.destroy() ;
                                    continue ;
                                }
                                // データ送信がはじめての場合.
                                if( em.isExecutionFlag() == false ) {
                                    // 接続条件をPoolキューに通知.
                                    em.setExecutionFlag( true ) ;
                                    executionQueue.append( em ) ;
                                }
                                key.interestOps( SelectionKey.OP_READ ) ;
                            }
                        } catch( Exception e ) {
                            e.printStackTrace() ;
                            if( em != null ) {
                                em.destroy() ;
                            }
                            if( buffer != null ) {
                                buffer.clear() ;
                            }
                            continue ;
                        }
                    }
                    
                    // write.
                    if( isWritable ) {
                        NioElement em = null ;
                        ByteBuffer buffer = null ;
                        try {
                            em = ( NioElement )key.attachment() ;
                            buffer = em.getSslElement().getRecvBuffer() ;
                            
                            ///////////////////////////////////////////////////
                            // HandShake処理の場合.
                            ///////////////////////////////////////////////////
                            if( em.getSslElement().isSendEndHandShake() == false ) {
                                // ハンドシェイクデータを送信.
                                ByteBuffer buf = em.getWriteBuffer().getQueue() ;
                                if( buf != null ) {
                                    em.getChannel().write( buf ) ;
                                }
                                // ハンドシェイク自身の処理が終了している場合.
                                if( em.getWriteBuffer().size() <= 0 && em.getSslElement().isHandShake() ) {
                                    // ハンドシェイク送信完了とする.
                                    em.getSslElement().sendEndHandShake() ;
                                    // OP_READのみとする.
                                    key.interestOps( SelectionKey.OP_READ ) ;
                                    
                                    // 受信データが存在する場合.
                                    // ハンドシェイクデータ自身に、HTTPリクエストが
                                    // 付加されるので、ここで判別処理を行う.
                                    if( em.getSslElement().readTo( buffer ) == true ) {
                                        // データ送信がはじめての場合.
                                        if( em.isExecutionFlag() == false ) {
                                            // 接続条件をPoolキューに通知.
                                            em.setExecutionFlag( true ) ;
                                            executionQueue.append( em ) ;
                                        }
                                    }
                                }
                            }
                            ///////////////////////////////////////////////////
                            // 通常通信の場合.
                            ///////////////////////////////////////////////////
                            else {
                                ByteBuffer buf = em.getWriteBuffer().getQueue() ;
                                // 送信データが存在する場合.
                                if( buf != null ) {
                                    // 書き込みの終端を検知.
                                    if( buf.limit() == 0 ) {
                                        // コネクションクローズの場合.
                                        if( em.isCloseFlag() == true ) {
                                            // SSLを終了.
                                            em.getSslElement().close() ;
                                            // コネクションを破棄.
                                            em.destroy() ;
                                        }
                                        // 書き込み終了なので、OP_READのみとする.
                                        else {
                                            key.interestOps( SelectionKey.OP_READ ) ;
                                        }
                                    }
                                    // 通常の送信データが存在する場合は、
                                    // SSL要素経由でデータ出力.
                                    else {
                                        em.getSslElement().write( buf ) ;
                                    }
                                }
                            }
                        } catch( Exception e ) {
                            e.printStackTrace() ;
                            if( em != null ) {
                                em.destroy() ;
                            }
                            if( buffer != null ) {
                                buffer.clear() ;
                            }
                            continue ;
                        }
                    }
                    
                }
                // not-use-socket.
                else {
                    try { (( NioElement )key.attachment()).destroy() ; } catch( Exception e ){}
                }
            }
        }
        setSendRecv() ;
        return true ;
    }
    
    /**
     * 送受信用条件を付加.
     */
    private void setSendRecv() throws Exception {
        // 受信データ存在確認.
        while( isRecvQueue.size() > 0 ) {
            NioElement element = isRecvQueue.getQueue() ;
            if( element != null && element.isUse() ) {
                SelectionKey key = element.getChannel().register(
                    selector,SelectionKey.OP_READ,element ) ;
                if( key != null ) {
                    element.setKey( key ) ;
                }
                else {
                    element.destroy() ;
                }
            }
        }
        // 送信データ存在確認.
        while( isWriteQueue.size() > 0 ) {
            if( isWriteQueue.size() <= 0 ) {
                break ;
            }
            NioElement element = isWriteQueue.getQueue() ;
            if( element.isUse() == true && element.getKey() != null ) {
                element.getKey().interestOps( SelectionKey.OP_READ | SelectionKey.OP_WRITE ) ;
            }
        }
    }
}

