package com.github.luohaha.connection;

import java.io.IOException;
import java.net.SocketAddress;
import java.net.SocketOption;
import java.net.StandardSocketOptions;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.logging.Logger;

import com.github.luohaha.context.Context;
import com.github.luohaha.context.ContextBean;
import com.github.luohaha.exception.ConnectionCloseException;

public class Connection implements Conn {
	private Context context;
	private SocketChannel channel;
	private Selector selector;
	private BlockingQueue<ByteBuffer> readyToWrite = new LinkedBlockingQueue<>();
	// write function could be call just once
	private boolean onWriteCalled = false;
	private boolean readyToClose = false;
	private Logger logger = Logger.getLogger("LightComm4J");

	public Connection(Context context, SocketChannel channel, Selector selector) {
		super();
		this.context = context;
		this.channel = channel;
		this.selector = selector;
	}

	public synchronized void write(byte[] data) throws ConnectionCloseException, ClosedChannelException {
		if (readyToClose)
			throw new ConnectionCloseException();
		ContextBean bean = context.getChanToContextBean().get(channel);
		ByteBuffer buffer = ByteBuffer.allocate(data.length + 4);
		buffer.putInt(data.length);
		buffer.put(data);
		buffer.flip();
		readyToWrite.add(buffer);
		int ops = bean.getOps();
		ops |= SelectionKey.OP_WRITE;
		bean.setOps(ops);
		this.channel.register(this.selector, ops);
		this.selector.wakeup();
	}

	/**
	 * set close flag
	 * 
	 */
	public void close() {
		this.readyToClose = true;
		if (this.readyToWrite.isEmpty()) {
			doClose();
		}
	}

	/**
	 * close channel
	 */
	public void doClose() {
		this.context.removeContextByChan(channel);
		try {
			this.channel.close();
		} catch (IOException e) {
			this.logger.warning("[Close Event] : " + e.toString());
		}
	}

	public BlockingQueue<ByteBuffer> getReadyToWrite() {
		return readyToWrite;
	}

	public boolean isOnWriteCalled() {
		return onWriteCalled;
	}

	public void setOnWriteCalled(boolean onWriteCalled) {
		this.onWriteCalled = onWriteCalled;
	}

	public boolean isReadyToClose() {
		return readyToClose;
	}

	public void setReadyToClose(boolean readyToClose) {
		this.readyToClose = readyToClose;
	}

	@Override
	public SocketAddress getLocalAddress() throws IOException {
		return this.channel.getLocalAddress();
	}

	@Override
	public SocketAddress getRemoteAddress() throws IOException {
		return this.channel.getRemoteAddress();
	}

	/**
	 * set send buffer's size
	 */
	public void setSendBuffer(int size) throws IOException {
		this.channel.setOption(StandardSocketOptions.SO_SNDBUF, size);
	}

	/**
	 * get send buffer' size
	 * 
	 * @return
	 * SO_SNDBUF
	 * @throws IOException
	 * IOException
	 */
	public int getSendBuffer() throws IOException {
		return this.channel.getOption(StandardSocketOptions.SO_SNDBUF);
	}

	/**
	 * set recv buffer's size
	 */
	public void setRecvBuffer(int size) throws IOException {
		this.channel.setOption(StandardSocketOptions.SO_RCVBUF, size);
	}

	/**
	 * get recv buffer's size
	 * 
	 * @return
	 * SO_RCVBUF
	 * @throws IOException
	 * IOException
	 */
	public int getRecvBuffer() throws IOException {
		return this.channel.getOption(StandardSocketOptions.SO_RCVBUF);
	}

	/**
	 * set keep alive
	 */
	public void setKeepAlive(boolean flag) throws IOException {
		this.channel.setOption(StandardSocketOptions.SO_KEEPALIVE, flag);
	}

	/**
	 * get keep alive
	 * 
	 * @return
	 * SO_KEEPALIVE
	 * @throws IOException
	 * IOException
	 */
	public boolean getKeepAlive() throws IOException {
		return this.channel.getOption(StandardSocketOptions.SO_KEEPALIVE);
	}

	/**
	 * set reuse address
	 */
	public void setReUseAddr(boolean flag) throws IOException {
		this.channel.setOption(StandardSocketOptions.SO_REUSEADDR, flag);
	}

	/**
	 * get reuse address
	 * 
	 * @return
	 * SO_REUSEADDR
	 * @throws IOException
	 * IOException
	 */
	public boolean getReUseAddr() throws IOException {
		return this.channel.getOption(StandardSocketOptions.SO_REUSEADDR);
	}

	/**
	 * set no delay
	 */
	public void setNoDelay(boolean flag) throws IOException {
		this.channel.setOption(StandardSocketOptions.TCP_NODELAY, flag);
	}

	/**
	 * get no delay
	 * 
	 * @return
	 * TCP_NODELAY
	 * @throws IOException
	 * IOException
	 */
	public boolean getNoDelay() throws IOException {
		return this.channel.getOption(StandardSocketOptions.TCP_NODELAY);
	}
}