package org.maachang.comet.net.nio ;

import java.net.Socket;
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;

/**
 * NIO-TCP/IPサーバユーティリティ.
 * 
 * @version 2008/11/15
 * @author  masahito suzuki
 * @since   MaachangComet 1.29
 */
abstract class NioTcpUtil {
    private NioTcpUtil(){}
    
    /**
     * ログ.
     */
    private static final Log LOG = LogFactory.getLog( NioTcpUtil.class ) ;
    
    /**
     * accept処理.
     * <p>単一スレッド用</p>
     */
    protected static boolean toAcceptBySingle( Selector selector,SelectionKey key,IsWriteQueue queue )
        throws Exception {
        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 ) {
                    setOption( sc ) ;
                    NioElement em = new NioElement( sc,queue ) ;
                    SelectionKey regKey = sc.register( selector,SelectionKey.OP_READ,em ) ;
                    if( regKey != null ) {
                        em.setKey( regKey ) ;
                    }
                    else {
                        em.destroy() ;
                    }
                }
                return true ;
            } catch( Exception e ) {
                if( sc != null ) {
                    try { sc.close() ; } catch( Exception ee ) {}
                }
            }
        }
        return false ;
    }
    
    /**
     * accept処理.
     * <p>複数スレッド用</p>
     */
    protected static boolean toAcceptByPlural( SelectionKey key,NioReceiveServerArray recvServer )
        throws Exception {
        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 ) {
                    setOption( sc ) ;
                    recvServer.setElement( new NioElement( sc ) ) ;
                }
                return true ;
            } catch( Exception e ) {
                if( sc != null ) {
                    try { sc.close() ; } catch( Exception ee ) {}
                }
            }
        }
        return false ;
    }
    
    /**
     * 受信処理.
     */
    protected static boolean isRead( boolean mode,Selector selector,SelectionKey key,
        ReceiveLinkQueue queue,ByteBuffer buffer )
        throws Exception {
        // read.
        if( ( key.readyOps() & SelectionKey.OP_READ ) == SelectionKey.OP_READ ) {
            NioElement em ;
            if( ( em = getNioElement( key ) ) == null ) {
                return true ;
            }
            try {
                // ターボモードがONの場合.
                if( mode ) {
                    // 受信データが存在する分だけ、受信.
                    if( recvAll( em,buffer ) == false ) {
                        // データが-1バイトの場合は、クライアント
                        // コネクション切断なので、要素を破棄する.
                        em.destroy() ;
                        return true ;
                    }
                } 
                // ターボモードがOFFの場合.
                else{
                    // 差分データ読み込み処理.
                    if( recv( em,buffer ) == false ) {
                        // データが0バイトの場合は、クライアント
                        // コネクション切断なので、要素を破棄する.
                        em.destroy() ;
                        return true ;
                    }
                }
                // データ送信がはじめての場合.
                if( em.isExecutionFlag() == false ) {
                    // 接続条件をPoolキューに通知.
                    em.setExecutionFlag( true ) ;
                    queue.append( em ) ;
                }
                // OP_WRITEを除外する.
                key.interestOps( key.interestOps() & (~SelectionKey.OP_WRITE) ) ;
                return true ;
            } catch( Exception e ) {
                //LOG.error( "tcp@readError",e ) ;
                if( em != null ) {
                    em.destroy() ;
                }
                return true ;
            }
        }
        return false ;
    }
    
    /**
     * 送信処理.
     */
    protected static boolean isWrite( Selector selector,SelectionKey key ) throws Exception {
        // write.
        if( key.isValid() &&
            ( key.readyOps() & SelectionKey.OP_WRITE ) == SelectionKey.OP_WRITE ) {
            NioElement em ;
            if( ( em = getNioElement( key ) ) == null ) {
                return true ;
            }
            try {
                ByteBuffer buf = em.getWriteBuffer() ;
                // 送信データが存在する場合.
                if( buf != null ) {
                    // 書き込みの終端を検知.
                    if( buf.limit() == 0 ) {
                        // コネクションクローズの場合.
                        if( em.isCloseFlag() == true ) {
                            em.destroy() ;
                            return true ;
                        }
                        // 書き込み終了なので、OP_WRITEを除外する.
                        else {
                            key.interestOps( key.interestOps() & (~SelectionKey.OP_WRITE) ) ;
                        }
                    }
                    // 通常の送信データが存在する場合は、
                    // SocketChannelに書き込む.
                    else {
                        SocketChannel ch = em.getChannel() ;
                        ch.write( buf ) ;
                    }
                }
                // 送信データが存在しない場合.
                else {
                    // 書き込みが無いので、OP_WRITEを除外する.
                    key.interestOps( key.interestOps() & (~SelectionKey.OP_WRITE) ) ;
                }
                return true ;
            } catch( Exception e ) {
                LOG.error( "tcp@writeError",e ) ;
                if( em != null ) {
                    em.destroy() ;
                }
                return true ;
            }
        }
        return false ;
    }
    
    private static final int BUFFER = 32768 ;
    private static final int LINGER = 5 ;
    
    /**
     * Acceptソケット設定.
     */
    protected static void setOption( SocketChannel channel )
        throws Exception {
        channel.configureBlocking( false ) ;
        Socket soc = channel.socket() ;
        soc.setSendBufferSize( BUFFER ) ;
        soc.setReceiveBufferSize( BUFFER ) ;
        soc.setKeepAlive( true ) ;
        soc.setTcpNoDelay( true ) ;
        soc.setReuseAddress( true ) ;
        soc.setSoLinger( true,LINGER ) ;
    }
    
    /**
     * Accept,書き込み処理待ち.
     */
    protected static void isSendOrWrite( Selector selector,IsReadQueue read,IsWriteQueue write ) throws Exception {
        // 受信データ存在確認.
        if( read.size() > 0 ) {
            for( ;; ) {
                NioElement element = read.getQueue() ;
                if( element == null ) {
                    break ;
                }
                else if( element.isUse() ) {
                    SelectionKey key = element.getChannel().register(
                        selector,SelectionKey.OP_READ,element ) ;
                    if( key != null ) {
                        element.setKey( key ) ;
                    }
                    else {
                        element.destroy() ;
                    }
                }
            }
        }
        // 送信データ存在確認.
        if( write.size() > 0 ) {
            for( ;; ) {
                NioElement element = write.getQueue() ;
                if( element == null ) {
                    break ;
                }
                else if( element.isUse() && element.getKey() != null ) {
                    element.getKey().interestOps( element.getKey().interestOps() | SelectionKey.OP_WRITE ) ;
                }
            }
        }
    }
    
    /**
     * 書き込み処理待ち.
     */
    protected static void isWrite( IsWriteQueue queue ) throws Exception {
        // 送信データ存在確認.
        if( queue.size() > 0 ) {
            for( ;; ) {
                NioElement element = queue.getQueue() ;
                if( element == null ) {
                    break ;
                }
                else if( element.isUse() && element.getKey() != null ) {
                    element.getKey().interestOps( element.getKey().interestOps() | SelectionKey.OP_WRITE ) ;
                }
            }
        }
    }
    
    /**
     * Keyが無効な場合の処理.
     */
    protected static void destroyKey( SelectionKey key ) {
        NioElement em ;
        if( ( em = ( NioElement )key.attachment() ) == null ) {
            try {
                key.channel().close() ;
            } catch( Exception e ) {
            }
        }
        else {
            em.destroy() ;
        }
        key.cancel() ;
    }
    
    /**
     * 要素取得.
     */
    protected static NioElement getNioElement( SelectionKey key ) throws Exception {
        NioElement ret = null ;
        // 要素取得に失敗および、取得要素が不正.
        if( ( ret = ( NioElement )key.attachment() ) == null || ret.isUse() == false ) {
            if( key.channel() != null ) {
                try {
                    key.channel().close() ;
                } catch( Exception e ) {
                }
            }
            key.attach( null ) ;
            key.cancel() ;
            return null ;
        }
        return ret ;
    }
    
    /**
     * データ受信処理.
     */
    protected static boolean recv( NioElement emt,ByteBuffer buffer ) throws Exception {
        if( emt.isUse() == false ) {
            return false ;
        }
        ReceiveBuffer emtBuf = emt.getBuffer() ;
        SocketChannel channel = emt.getChannel() ;
        buffer.clear() ;
        int len = channel.read( buffer ) ;
        if( len <= 0 ) {
            return false ;
        }
        else {
            buffer.flip() ;
            emtBuf.put( buffer ) ;
            emt.update() ;
        }
        return true ;
    }
    
    /**
     * データ受信処理(あるだけ全部).
     */
    protected static boolean recvAll( NioElement emt,ByteBuffer buffer ) throws Exception {
        ReceiveBuffer emtBuf = emt.getBuffer() ;
        SocketChannel channel = emt.getChannel() ;
        boolean flg = false ;
        for( ;; ) {
            if( emt.isUse() == false ) {
                return false ;
            }
            buffer.clear() ;
            int len = channel.read( buffer ) ;
            if( len <= 0 ) {
                if( flg == false || len <= -1 ) {
                    return false ;
                }
                break ;
            }
            else {
                buffer.flip() ;
                emtBuf.put( buffer ) ;
                emt.update() ;
                flg = true ;
            }
        }
        return true ;
    }
}
