package org.maachang.comet.net.nio ;

import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.maachang.comet.net.ssl.CloseSslException;
import org.maachang.comet.net.ssl.SslElement;
import org.maachang.comet.net.ssl.SslOption;

/**
 * NIO-SSLサーバユーティリティ.
 * 
 * @version 2008/11/15
 * @author  masahito suzuki
 * @since   MaachangComet 1.29
 */

abstract class NioSslUtil {
    
    /**
     * ログ.
     */
    private static final Log LOG = LogFactory.getLog( NioSslServer.class ) ;
    
    /**
     * accept処理.
     * <p>単一スレッド用</p>
     */
    protected static final boolean toAcceptBySingle( Selector selector,SelectionKey key,IsWriteQueue queue,SslOption option )
        throws Exception {
        // accept.
        if( ( key.readyOps() & SelectionKey.OP_ACCEPT  ) == SelectionKey.OP_ACCEPT ) {
            SocketChannel sc = null ;
            try {
                // 新しいAcceptをセット.
                ServerSocketChannel ssc = ( ServerSocketChannel )key.channel() ;
                sc = ssc.accept() ;
                if( sc != null ) {
                    NioTcpUtil.setOption( sc ) ;
                    NioElement em = new NioElement( sc,queue ) ;
                    em.setSsl( option ) ;
                    em.createSsl() ;
                    SelectionKey regKey = sc.register( selector,SelectionKey.OP_READ,em ) ;
                    if( regKey != null ) {
                        em.setKey( regKey ) ;
                    }
                    else {
                        em.destroy() ;
                    }
                }
            } catch( Exception e ) {
                //LOG.warn( "## accept失敗",e ) ;
                if( sc != null ) {
                    try { sc.close() ; } catch( Exception ee ) {}
                }
            }
            return true ;
        }
        return false ;
    }
    
    /**
     * accept処理.
     * <p>複数スレッド用</p>
     */
    protected static boolean toAcceptByPlural( SelectionKey key,NioSslReceiveServerArray recvServer,SslOption option )
        throws Exception {
        // accept.
        if( ( key.readyOps() & SelectionKey.OP_ACCEPT  ) == SelectionKey.OP_ACCEPT ) {
            SocketChannel sc = null ;
            try {
                // 新しいAcceptをセット.
                ServerSocketChannel ssc = ( ServerSocketChannel )key.channel() ;
                sc = ssc.accept() ;
                if( sc != null ) {
                    NioTcpUtil.setOption( sc ) ;
                    NioElement em = new NioElement( sc ) ;
                    em.setSsl( option ) ;
                    em.createSsl() ;
                    recvServer.setElement( em ) ;
                }
            } catch( Exception e ) {
                //LOG.warn( "## accept失敗",e ) ;
                if( sc != null ) {
                    try { sc.close() ; } catch( Exception ee ) {}
                }
            }
            return true ;
        }
        return false ;
    }
    
    /**
     * 受信処理.
     */
    protected static boolean isRead( Selector selector,SelectionKey key,
        ReceiveLinkQueue queue )
        throws Exception {
        if( ( key.readyOps() & SelectionKey.OP_READ ) == SelectionKey.OP_READ ) {
            NioElement em ;
            if( ( em = NioTcpUtil.getNioElement( key ) ) == null ) {
                return true ;
            }
            SslElement ssl = em.getSslElement() ;
            ByteBuffer buffer = null ;
            try {
                buffer = ssl.getRecvBuffer() ;
                
                ///////////////////////////////////////////////////
                // HandShake処理の場合.
                ///////////////////////////////////////////////////
                if( ssl.isSendEndHandShake() == false ) {
                    // 受信情報を取得.
                    int len = em.getChannel().read( buffer ) ;
                    if( len <= 0 ) {
                        if( len <= -1 ) {
                            // データが0バイトの場合は、クライアント
                            // コネクション切断なので、要素を破棄する.
                            em.destroy() ;
                            return true ;
                        }
                    }
                    // ハンドシェイク処理を行う.
                    else {
                        ssl.handShake( buffer ) ;
                        // 受信残りデータが存在する場合、処理する.
                        while( ssl.isUseReadData( buffer ) ) {
                            ssl.handShake( buffer ) ;
                        }
                        // OP_READ/OP_WRITEを有効にする.
                        key.interestOps( key.interestOps() | SelectionKey.OP_WRITE ) ;
                    }
                }
                ///////////////////////////////////////////////////
                // 通常通信の場合.
                ///////////////////////////////////////////////////
                else {
                    // 差分データ読み込み処理.
                    if( ssl.read( buffer ) == false ) {
                        // データが-1バイトの場合は、クライアント
                        // コネクション切断なので、要素を破棄する.
                        em.destroy() ;
                        return true ;
                    }
                    // データ送信がはじめての場合.
                    if( em.isExecutionFlag() == false ) {
                        // 接続条件をPoolキューに通知.
                        em.setExecutionFlag( true ) ;
                        queue.append( em ) ;
                    }
                    // 書き込み終了なので、OP_WRITEを除外する.
                    key.interestOps( key.interestOps() & (~SelectionKey.OP_WRITE) ) ;
                }
            } catch( Exception e ) {
                //LOG.error( "ssl@readError",e ) ;
                if( em != null ) {
                    em.destroy() ;
                }
                if( buffer != null ) {
                    buffer.clear() ;
                }
            }
            return true  ;
        }
        return false ;
    }
    
    /**
     * 送信処理.
     */
    protected static boolean isWrite( Selector selector,SelectionKey key,
        ReceiveLinkQueue queue ) throws Exception {
        // write.
        if( key.isValid() &&
            ( key.readyOps() & SelectionKey.OP_WRITE ) == SelectionKey.OP_WRITE ) {
            NioElement em ;
            if( ( em = NioTcpUtil.getNioElement( key ) ) == null ) {
                return true ;
            }
            SslElement ssl = em.getSslElement() ;
            ByteBuffer buffer = null ;
            try {
                buffer = ssl.getRecvBuffer() ;
                
                ///////////////////////////////////////////////////
                // HandShake処理の場合.
                ///////////////////////////////////////////////////
                if( ssl.isSendEndHandShake() == false ) {
                    // ハンドシェイクデータを送信.
                    ByteBuffer buf = em.getWriteBuffer() ;
                    if( buf != null ) {
                        em.getChannel().write( buf ) ;
                    }
                    // ハンドシェイク自身の処理が終了している場合.
                    if( em.getWriteSize() <= 0 && ssl.isHandShake() ) {
                        // ハンドシェイク送信完了とする.
                        ssl.sendEndHandShake() ;
                        // OP_WRITEを除外する.
                        key.interestOps( key.interestOps() & (~SelectionKey.OP_WRITE) ) ;
                        
                        // 受信データが存在する場合.
                        // ハンドシェイクデータ自身に、HTTPリクエストが
                        // 付加されるので、ここで判別処理を行う.
                        if( ssl.readTo( buffer ) == true ) {
                            // データ送信がはじめての場合.
                            if( em.isExecutionFlag() == false ) {
                                // 接続条件をPoolキューに通知.
                                em.setExecutionFlag( true ) ;
                                queue.append( em ) ;
                            }
                        }
                    }
                }
                ///////////////////////////////////////////////////
                // 通常通信の場合.
                ///////////////////////////////////////////////////
                else {
                    ByteBuffer buf = em.getWriteBuffer() ;
                    // 送信データが存在する場合.
                    if( buf != null ) {
                        // 書き込みの終端を検知.
                        if( buf.limit() == 0 ) {
                            // コネクションクローズの場合.
                            if( em.isCloseFlag() == true ) {
                                // SSLを終了.
                                ssl.close() ;
                                // コネクションを破棄.
                                em.destroy() ;
                                return true ;
                            }
                            // 書き込み終了なので、OP_WRITEを除外する.
                            else {
                                key.interestOps( key.interestOps() & (~SelectionKey.OP_WRITE) ) ;
                                // バッファを初期化する.
                                ssl.clearBuffer() ;
                            }
                        }
                        // 通常の送信データが存在する場合は、
                        // SSL要素経由でデータ出力.
                        else {
                            ssl.write( buf ) ;
                        }
                    }
                    // 送信データが存在しない場合.
                    else {
                        // 書き込みが無いので、OP_WRITEを除外する.
                        key.interestOps( key.interestOps() & (~SelectionKey.OP_WRITE) ) ;
                    }
                }
            } catch( CloseSslException cs ) {
                // SSLステータス:CLOSEDを検出したとき.
                if( em != null ) {
                    em.destroy() ;
                }
                if( buffer != null ) {
                    buffer.clear() ;
                }
            } catch( Exception e ) {
                LOG.error( "ssl@writeError",e ) ;
                if( em != null ) {
                    em.destroy() ;
                }
                if( buffer != null ) {
                    buffer.clear() ;
                }
            }
            return true ;
        }
        return false ;
    }
}
