package org.maachang.mimdb.server ;

import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

import org.maachang.mimdb.core.util.AtomicNumber32;
import org.maachang.mimdb.core.util.AtomicObject;

/**
 * ノンブロッキングセレクタ.
 * 
 * @version 2014/01/16
 * @author  masahito suzuki
 * @since MasterInMemDB 1.02
 */
public final class NioSelector {
    
    /** Selector. **/
    private final AtomicObject<Selector> selector = new AtomicObject<Selector>( null ) ;
    
    /** wakeupカウンタ. **/
    private final AtomicNumber32 wakeupCounter = new AtomicNumber32( 0 ) ;
    
    /**
     * コンストラクタ.
     * @exception Exception 例外.
     */
    public NioSelector() throws Exception {
        selector.set( Selector.open() ) ;
    }
    
    /**
     * オブジェクトクローズ.
     */
    public final void close() {
        Selector s = selector.setToBeforeReturn( null ) ;
        if( s != null ) {
            try {
                Iterator<SelectionKey> it = s.keys().iterator();
                while (it.hasNext()) {
                    destroyKey( it.next() ) ;
                }
            } catch( Throwable t ) {}
            try {
                s.close() ;
            } catch( Throwable t ) {}
        }
        s = null ;
    }
    
    /**
     * 待機処理.
     * @return int 処理件数が返却されます.
     * @exception Exception 例外.
     */
    public final int select() throws Exception {
        int ret = 0 ;
        Selector s = selector.get() ;
        if( s == null ) {
            return -1 ;
        }
        Thread.yield() ;
        if( wakeupCounter.get() > 0 ) {
            ret = s.selectNow() ;
        }
        else {
            wakeupCounter.set( -1 ) ;
            ret = s.select() ;
        }
        wakeupCounter.set( 0 ) ;
        return ret ;
    }
    
    /**
     * 待機処理.
     * @param timeout タイムアウト値を設定します.
     * @return int 処理件数が返却されます.
     * @exception Exception 例外.
     */
    public final int select( final int timeout )
        throws Exception {
        int ret = 0 ;
        Selector s = selector.get() ;
        if( s == null ) {
            return -1 ;
        }
        Thread.yield() ;
        if( wakeupCounter.get() > 0 ) {
            ret = s.selectNow() ;
        }
        else {
            wakeupCounter.set( -1 ) ;
            ret = s.select( timeout ) ;
        }
        wakeupCounter.set( 0 ) ;
        return ret ;
    }
    
    /**
     * wakeup.
     */
    public final void wakeup() {
        Selector s = selector.get() ;
        if( s != null ) {
            if( wakeupCounter.inc() == 0 ) {
                s.wakeup() ;
            }
        }
    }
    
    /**
     * Iteratorを取得.
     * @return Iterator<SelectionKey> Iteratorが返却されます.
     */
    public final Iterator<SelectionKey> iterator() {
        Selector s = selector.get() ;
        if( s != null ) {
            return s.selectedKeys().iterator() ;
        }
        return null ;
    }
    
    /**
     * チャネル登録.
     * @param channel 登録対象のチャネルを設定します.
     * @param op 対象のオプションを設定します.
     * @return SelectionKey 登録されたキー情報が返却されます.
     * @exception Exception 例外.
     */
    public final SelectionKey register( final SelectableChannel channel,final int op )
        throws Exception {
        return register( channel,op,null ) ;
    }
    
    /**
     * チャネル登録.
     * @param channel 登録対象のチャネルを設定します.
     * @param op 対象のオプションを設定します.
     * @param obj キー登録するオブジェクトを設定します.
     * @return SelectionKey 登録されたキー情報が返却されます.
     * @exception Exception 例外.
     */
    public final SelectionKey register( final SelectableChannel channel,final int op,final Object obj )
        throws Exception {
        Selector s = selector.get() ;
        if( s != null ) {
            return channel.register( s,op,obj ) ;
        }
        return null ;
    }
    
    /**
     * SelectionKeyの破棄.
     * @param key 破棄対象のSelectionKeyを設定します.
     */
    public static final void destroyKey( final SelectionKey key ) {
        if( key != null ) {
            MimdbConnectElement em = ( MimdbConnectElement )key.attachment() ;
            if( em != null ) {
                key.attach( null ) ;
                em.destroy() ;
            }
            key.cancel() ;
            if( key.channel() instanceof SocketChannel ) {
                try {
                    ((SocketChannel)key.channel()).socket().close() ;
                } catch( Throwable e ) {
                }
            }
            try {
                key.channel().close() ;
            } catch( Throwable e ) {
            }
        }
    }

}
