/*
 * LOGICAL-PARADOX.ORG
 * Copyright (C)2005 satoshi akabane(akabane@logical-paradox.org)
 * $Id: Multiplexer.java,v 1.8 2005/05/15 03:54:09 rampil Exp $
 */
package org.logical_paradox.common.net.async;

import java.io.IOException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.channels.spi.SelectorProvider;
import java.util.Iterator;
import java.util.Map;
import java.util.TooManyListenersException;
import java.util.Vector;

/**
 * Multiplexer
 * マルチプレクサ
 * 
 * @author satoshi akabane@logical-paradox.org
 * @version $Revision: 1.8 $
 */
public class Multiplexer {
	/**	サーバーソケットファクトリの設定名 */
	public static final String KEY_SERVER_SOCKET_FACTORY = "serversocketfactory";
	/** クライアントソケットファクトリの設定名 */
	public static final String KEY_CLIENT_SOCKET_FACTORY = "clientsocketfactory";
	/** ポート番号 */
	public static final String KEY_PORT_NUMBER = "portnumber";
	/** リモートホスト(IPアドレスまたはホスト名) */
	public static final String KEY_REMOTE_HOST = "remotehost";

	/** マルチプレクサのタイプ - クライアントタイプ */
	public static final int MPX_MODE_CLIENT = 1;
	/** マルチプレクサのタイプ - サーバータイプ */
	public static final int MPX_MODE_SERVER = 100;

	/** デフォルトのソケットタイムアウト秒数 */
	public static final int DEFAULT_TIMEOUT_SEC = 1000 * 60 * 1;

	/** リモートホスト(またはIPアドレス) */
	private final String remoteHost;
	/** ポート番号 */
	private final int port;
	/** クライアント用のソケットチャンネル */
	private Vector clientChannels = new Vector();
	/** マルチプレクサ */
	private final Selector selector;
	/** マルチプレクサの動作モード */
	private final int mpxMode;
	/** ソケット待ち受けタイムアウト秒数 */
	private int liteningSocketTimeoutSec = DEFAULT_TIMEOUT_SEC;
	/** サーバーソケットファクトリ */
	private final CAsyncServerSocketFactory serverSocketFactory;
	/** クライアントソケットファクトリ */
	private final CAsyncClientSocketFactory clientSocketFactory;

	/**
	 * コンストラクタ
	 * @param mode ソケットモード
	 * @param m 初期化パラメータ
	 * @throws IOException 通信障害
	 */
	public Multiplexer(int mode, Map m) throws IOException {
		mpxMode = mode;
		if(mpxMode == MPX_MODE_SERVER) {
			remoteHost = "localhost";
		} else {
			remoteHost = (String)m.get(KEY_REMOTE_HOST);
		}
		// ポート番号
		port = Integer.parseInt((String)m.get(KEY_PORT_NUMBER));
		// クライアントソケットファクトリー
		clientSocketFactory = (CAsyncClientSocketFactory)m.get(KEY_CLIENT_SOCKET_FACTORY);
		// サーバーソケットファクトリー
		serverSocketFactory = (CAsyncServerSocketFactory)m.get(KEY_SERVER_SOCKET_FACTORY);
		// セレクター
		selector = SelectorProvider.provider().openSelector();
	}

	/**
	 * このマルチプレクサに対して，監視対象のソケットを追加する
	 * @param client クライアントソケット
	 * @param ops 操作セット
	 * @throws TooManyListenersException リスナーが既に存在する場合に発生する例外
	 * @throws IOException 何らかの理由でチャネルの生成に失敗した場合に発生する例外
	 * @see java.nio.channels.SelectionKey
	 */
	public void addChannel(CAsyncSocket client, int ops) throws TooManyListenersException, IOException {
		client.addEventListener(client);
		client.configureBlocking(false);		// ノンブロッキングモード(注意:registerを呼び出す前に実行すること)
		client.register(selector, ops);
		clientChannels.addElement(client);
	}
	/**
	 * このマルチプレクサに対して，監視対象のソケットを追加する
	 * @param ops 操作セット
	 * @throws TooManyListenersException リスナーが既に存在する場合に発生する例外
	 * @throws IOException 何らかの理由でチャネルの生成に失敗した場合に発生する例外
	 * @see java.nio.channels.SelectionKey
	 */
	public void addChannel(int ops) throws TooManyListenersException, IOException {
		CAsyncSocket client = null;
		switch(mpxMode) {
			case MPX_MODE_CLIENT:
				client = clientSocketFactory.createClientSocket(remoteHost, port);
				break;
			case MPX_MODE_SERVER:
				client = serverSocketFactory.createServerSocket(port);
				break;
			default:
				throw new IllegalArgumentException("マルチプレクサ: ソケットの作成に失敗");
		}

		addChannel(client, ops);
	}
	/**
	 * 現在登録されているチャンネルを返す
	 * @return チャンネルの配列
	 */
	public Vector getChannels() {
		return clientChannels;
	}
	/**
	 * 送受信処理を実行する
	 * @throws Exception 送受信処理中に発生した例外
	 */
	public void doWork() throws Exception {
		int nofk = selector.select();
		if(nofk <= 0) {
			return;
		}
		Iterator it = selector.selectedKeys().iterator();

		while(it.hasNext()) {
			SelectionKey key = (SelectionKey)it.next();
			it.remove();

			AsyncEventListener callback = (AsyncEventListener)key.attachment();
			if(callback != null) {
				if(key.isConnectable()) {
					if(key.channel() instanceof SocketChannel) {
						SocketChannel sockch = (SocketChannel)key.channel();
						if(sockch.isConnectionPending() == true) {
							sockch.finishConnect();
							// 接続完了イベント
							callback.onConnect(new AsyncEvent(this, key.channel()));
						}
					}
				} else if(key.isReadable()) {
					// 読み込み可能イベント
					callback.onReceive(new AsyncEvent(this, key.channel()));
				} else if(key.isWritable()) {
					// 書き込み可能イベント
					callback.onSend(new AsyncEvent(this, key.channel()));
				} else if(key.isAcceptable()) {
					ServerSocketChannel server = (ServerSocketChannel)key.channel();
					SocketChannel client = server.accept();
					addChannel(clientSocketFactory.createClientSocket(client), SelectionKey.OP_CONNECT | SelectionKey.OP_READ | SelectionKey.OP_WRITE);

					// 受付イベント
					callback.onAccept(new AsyncEvent(this, client));
				}
			}
		}
	}
}

// end of Multiplexer.java
