/*
 * LOGICAL-PARADOX.ORG
 * Copyright (C)2005 satoshi akabane(akabane@logical-paradox.org)
 * $Id: CAsyncSocket.java,v 1.6 2005/05/15 12:37:53 rampil Exp $
 */
package org.logical_paradox.common.net.async;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.util.TooManyListenersException;

import org.logical_paradox.common.net.ReadWriteBuffer;

/**
 * CAsyncSocket
 * 並行入出力が可能なnon-blockingタイプのソケット
 * ソケットのような動作もするが，実はソケットではない．
 * 
 * @author satoshi akabane@logical-paradox.org
 * @version $Revision: 1.6 $
 */
public abstract class CAsyncSocket implements AsyncEventListener {
	/** 送信バッファのサイズ */
	public static final int SEND_BUFFER_SIZE = 4096;
	/** 受信バッファのサイズ */
	public static final int RECEIVE_BUFFER_SIZE = 4096;

	protected SelectableChannel channel = null;
	protected AsyncEventListener listener = null;

	/** 読み書き同時可能なバッファ(送信バッファ) */
	private ReadWriteBuffer sendBuffer = new ReadWriteBuffer();
	/** 読み書き同時可能なバッファ(受信バッファ) */
	private ReadWriteBuffer receiveBuffer = new ReadWriteBuffer();

	/**
	 * コンストラクタ
	 */
	public CAsyncSocket() {
	}

	/*
	 * イベントハンドラの登録と削除
	 */
	public void addEventListener(AsyncEventListener l) throws TooManyListenersException {
		if( listener != null ) {
			throw new TooManyListenersException( "AsyncEventListenerは既に登録されています" );
		}
		listener = l;
	}
	public void removeEventListener() {
		listener = null;
	}

	/**
	 * マルチプレクサにこのソケットを結合する
	 * @param s セレクター
	 * @param o 操作セット
	 * @return キー
	 * @throws ClosedChannelException チャンネルが閉じられている場合に発生する例外
	 * @see java.nio.channels.SelectionKey
	 */
	public SelectionKey register(Selector s, int o) throws ClosedChannelException {
		SelectionKey skey = channel.register(s, o);
		skey.attach(listener);				// セレクションキーにイベントハンドラを結合する

		return skey;
	}

	/**
	 * ブロッキングモードを設定する
	 * @param mode true:ブロッキングモード / false: 非ブロッキングモード
	 * @throws IOException 設定に失敗した場合に発生する例外
	 */
	public void configureBlocking(boolean mode) throws IOException {
		if(channel != null) {
			channel.configureBlocking(mode);
		}
	}
	/**
	 * ソケットチャンネルを返す
	 * @return ソケットチャンネル
	 */
	public SelectableChannel getSelectableChannel() {
		return channel;
	}

	/**
	 * 指定されたソケットチャンネルから，読み込みバッファに対してデータを読み込む
	 * @param src
	 */
	protected void readMessage( SocketChannel src ) {
		ByteBuffer rb = ByteBuffer.allocate( 4096 );
		try {
			src.read( rb );
			rb.flip();
			Charset charset = Charset.forName( "MS932" );
			CharsetDecoder csdecoder = charset.newDecoder();
			CharBuffer cb = csdecoder.decode( rb );
		} catch( IOException ae ) {
		}
	}
	/**
	 * 送信バッファに対してデータを書き込む
	 * 送信バッファの内容はソケットが送信可能状態になった際に送信される
	 * @param b 送信対象データ
	 */
	public void sendMessage(byte[] b) {
		sendBuffer.write(b);
		onSend(new AsyncEvent(this,channel));
	}
	/**
	 * ソケットが読み込み可能状態になった際に呼ばれるイベントハンドラ
	 * @param e イベントオブジェクト
	 */
	public void onReceive(AsyncEvent o) {
		try {
			SocketChannel channel = (SocketChannel)o.getChannel();
			ByteBuffer bb = ByteBuffer.allocate(RECEIVE_BUFFER_SIZE);
		} catch(Exception e) {
		}
	}
	/**
	 * ソケットが書き込み可能状態になった際に呼ばれるイベントハンドラ
	 * @param e イベントオブジェクト
	 */
	public void onSend(AsyncEvent o) {
		// 送信バッファに溜まっている内容をソケットに対して送信する
		byte[] sendbuf = new byte[SEND_BUFFER_SIZE];
		int sendsize = 0;
		while((sendsize = sendBuffer.read(sendbuf)) > 0) {
			// 送信バッファに何か格納されている間，送信し続ける
			ByteBuffer bb = null;
			try {
				bb = ByteBuffer.wrap(sendbuf, 0, sendsize);
				((SocketChannel)o.getChannel()).write(bb);
			} catch(Exception e) {
				// 通信エラーが発生した
			} finally {
				if(bb != null) {
					bb.clear();
					bb = null;
				}
			}
		}
	}

}

// end of CAsyncSocket.java
