package jp.cssj.cti.ctip.driver;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;

import jp.cssj.cti.ctip.CTIPUtils;
import jp.cssj.cti.helpers.ContentBuilder;
import jp.cssj.cti.helpers.RequestConsumer;
import jp.cssj.cti.helpers.RequestProducer;

/**
 * @author <a href="mailto:miyabe at gnn.co.jp">MIYABE Tatsuhiko </a>
 * @version $Id: CTIPRequestConsumer.java,v 1.4 2005/08/18 04:51:26 harumanx Exp $
 */
class CTIPRequestConsumer implements RequestConsumer {
	private static final int BUFFER_SIZE = 1024;

	private final String encoding;

	private final byte[] buff = new byte[BUFFER_SIZE + 5];

	private final SocketChannel channel;

	private int pos = 0;

	private CTIPSession session;

	private ContentBuilder builder = null;

	CTIPRequestConsumer(SocketChannel channel, String encoding)
			throws IOException {
		this.channel = channel;
		this.encoding = encoding;
	}

	public void property(String name, String value) throws IOException {
		this.flush();
		byte[] nameBytes = CTIPUtils.toBytes(name, this.encoding);
		byte[] valueBytes = CTIPUtils.toBytes(value, this.encoding);

		ByteBuffer src = ByteBuffer.allocate(nameBytes.length
				+ valueBytes.length + 9);
		src.putInt(nameBytes.length + valueBytes.length + 5);
		src.put(RequestProducer.PROPERTY);
		src.putShort((short) nameBytes.length);
		src.put(nameBytes);
		src.putShort((short) valueBytes.length);
		src.put(valueBytes);
		CTIPUtils.writeAll(this.channel, src);
	}

	public void resource(String uri, String mimeType, String encoding)
			throws IOException {
		this.flush();
		byte[] uriBytes = CTIPUtils.toBytes(uri, this.encoding);
		byte[] mimeTypeBytes = CTIPUtils.toBytes(mimeType, this.encoding);
		byte[] encodingBytes = CTIPUtils.toBytes(encoding, this.encoding);

		ByteBuffer src = ByteBuffer.allocate(uriBytes.length
				+ mimeTypeBytes.length + encodingBytes.length + 11);
		src.putInt(uriBytes.length + mimeTypeBytes.length
				+ encodingBytes.length + 7);
		src.put(RequestProducer.RESOURCE);
		src.putShort((short) uriBytes.length);
		src.put(uriBytes);
		src.putShort((short) mimeTypeBytes.length);
		src.put(mimeTypeBytes);
		src.putShort((short) encodingBytes.length);
		src.put(encodingBytes);
		CTIPUtils.writeAll(this.channel, src);
	}

	public void main(String uri, String mimeType, String encoding)
			throws IOException {
		this.flush();
		byte[] uriBytes = CTIPUtils.toBytes(uri, this.encoding);
		byte[] mimeTypeBytes = CTIPUtils.toBytes(mimeType, this.encoding);
		byte[] encodingBytes = CTIPUtils.toBytes(encoding, this.encoding);

		ByteBuffer src = ByteBuffer.allocate(uriBytes.length
				+ mimeTypeBytes.length + encodingBytes.length + 11);
		src.putInt(uriBytes.length + mimeTypeBytes.length
				+ encodingBytes.length + 7);
		src.put(RequestProducer.MAIN);
		src.putShort((short) uriBytes.length);
		src.put(uriBytes);
		src.putShort((short) mimeTypeBytes.length);
		src.put(mimeTypeBytes);
		src.putShort((short) encodingBytes.length);
		src.put(encodingBytes);
		CTIPUtils.writeAll(this.channel, src);

		this.builder = this.session.createContentBuilder();
	}

	protected void setCTIPSession(CTIPSession session) {
		this.session = session;
	}

	public void write(byte[] b, int off, int len) throws IOException {
		for (int i = 0; i < len; ++i) {
			this.write(b[i + off]);
		}
	}

	private void write(int b) throws IOException {
		if (this.pos >= BUFFER_SIZE) {
			this.flush();
		}
		this.buff[(this.pos++) + 5] = (byte) b;
	}

	private void flush() throws IOException {
		if (this.pos > 0) {
			int payload = this.pos + 1;
			ByteBuffer src = ByteBuffer.wrap(this.buff, 0, this.pos + 5);
			src.putInt(payload);
			src.put(RequestProducer.DATA);

			if (this.builder != null) {
				src.position(0);

				Selector selector = this.channel.provider().openSelector();
				this.channel.configureBlocking(false);
				SelectionKey key = this.channel.register(selector,
						SelectionKey.OP_READ | SelectionKey.OP_WRITE);
				for (;;) {
					selector.select();
					if (src.remaining() > 0 && key.isWritable()) {
						this.channel.write(src);
					}
					if (key.isReadable()) {
						this.session.buildNext(this.builder);
					}
					if (src.remaining() <= 0) {
						break;
					}
				}
				selector.close();
				this.channel.configureBlocking(true);
			} else {
				CTIPUtils.writeAll(this.channel, src);
			}

			this.pos = 0;
		}
	}

	public void end() throws IOException {
		this.flush();
		ByteBuffer src = ByteBuffer.allocate(4);
		src.putInt(0);
		src.position(0);
		this.channel.write(src);

		if (this.builder != null) {
			this.session.finishBuild(this.builder);
			this.builder.finish();
		}
	}
}