package com.feeyo.net.nio;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.concurrent.atomic.AtomicBoolean;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.feeyo.net.nio.util.TimeUtil;

public abstract class ClosableConnection {
	
	private static Logger LOGGER = LoggerFactory.getLogger( ClosableConnection.class );
	
	
	// 连接的方向,in表示是客户端连接过来的,out表示自己作为客户端去连接对端Sever
	public enum Direction {
		in, out
	}
	
	//
	public static final int STATE_CONNECTING = 0;
	public static final int STATE_CONNECTED = 1;
	public static final int STATE_CLOSING = -1;
	public static final int STATE_CLOSED = -2;
	
	//
	public static final int OP_NOT_READ = ~SelectionKey.OP_READ;
	public static final int OP_NOT_WRITE = ~SelectionKey.OP_WRITE;
	
	//
	protected Direction direction = Direction.out;

	protected String host;
	protected int port;
	protected int localPort;
	protected long id;
	protected String reactor;
	protected Object attachement;
	protected int state = STATE_CONNECTING;

	// socket
	protected SocketChannel socketChannel;
	protected SelectionKey processKey;

	//
	protected AtomicBoolean isClosed;
	protected boolean isSocketClosed;
	
	//
	protected long startupTime;
	protected long lastReadTime;
	protected long lastWriteTime;
	
	//																//
	protected long netInBytes;											
	protected long netOutBytes;
	protected int writeAttempts;
	
	protected long idleTimeout;
	
	@SuppressWarnings("rawtypes")
	protected NIOHandler handler;


	public ClosableConnection(SocketChannel socketChannel) {
		this.socketChannel = socketChannel;
		this.isClosed = new AtomicBoolean(false);
		this.startupTime = TimeUtil.currentTimeMillis();
		this.lastReadTime = startupTime;
		this.lastWriteTime = startupTime;
		this.id = ConnectIdGenerator.getINSTNCE().getId();
	}

	
	//
	public long getIdleTimeout() {
		return idleTimeout;
	}

	public void setIdleTimeout(long idleTimeout) {
		this.idleTimeout = idleTimeout;
	}
	

	public String getHost() {
		return host;
	}

	public void setHost(String host) {
		this.host = host;
	}

	public int getPort() {
		return port;
	}

	public void setPort(int port) {
		this.port = port;
	}

	public long getId() {
		return id;
	}

	public int getLocalPort() {
		return localPort;
	}

	public void setLocalPort(int localPort) {
		this.localPort = localPort;
	}

	public void setId(long id) {
		this.id = id;
	}

	public SocketChannel getSocketChannel() {
		return socketChannel;
	}

	public long getStartupTime() {
		return startupTime;
	}

	public long getLastReadTime() {
		return lastReadTime;
	}

	public long getLastWriteTime() {
		return lastWriteTime;
	}
	
	public long getNetInBytes() {
		return netInBytes;
	}
	
	public long getNetOutBytes() {
		return netOutBytes;
	}
	

	public void setHandler(NIOHandler<? extends ClosableConnection> handler) {
		this.handler = handler;
	}

	@SuppressWarnings("rawtypes")
	public NIOHandler getHandler() {
		return this.handler;
	}

	public boolean isConnected() {
		boolean isConnected = (this.state != STATE_CONNECTING && state != STATE_CLOSING && state != STATE_CLOSED);
		return isConnected;
	}

	public boolean isClosed() {
		return isClosed.get();
	}

	public boolean isIdleTimeout() {
 		return TimeUtil.currentTimeMillis() > Math.max(lastWriteTime, lastReadTime) + idleTimeout;
	}

	public void idleCheck() {		
		if ( isIdleTimeout() ) {			
			if ( LOGGER.isDebugEnabled() ) {
				LOGGER.debug(toString() + " idle timeout");
			}
			close("idle timeout ");
		}
	}
	
	//
	public void setReactor(String reactorName) {
		this.reactor = reactorName;
	}

	public String getReactor() {
		return this.reactor;
	}

	public boolean belongsReactor(String reacotr) {
		return reactor.equals(reacotr);
	}

	public Object getAttachement() {
		return attachement;
	}

	public void setAttachement(Object attachement) {
		this.attachement = attachement;
	}

	public void setState(int newState) {
		this.state = newState;
	}
	
	public int getState() {
		return state;
	}

	public Direction getDirection() {
		return direction;
	}

	public void setDirection(Direction in) {
		this.direction = in;
	}
	
	//
	// ______________________________________________________________________________________
	
	// Accept  register
	public abstract void register(Selector selector) throws IOException;
	
	// 异步读取,该方法在 reactor 中被调用
	public abstract void asynRead() throws IOException;
	
	//
	public abstract void doNextWriteCheck();
	public abstract void write(byte[] data);
	public abstract void write(ByteBuffer data);
	
	// 
	public abstract void close(String reason);
	
	// Network Flow Guard (NFG) 
	protected boolean flowGuard(long length) {
		return false;
		// ignore
	}
	
	
	//
	// ______________________________________________________________________________________
	//
	protected void disableRead() {
		SelectionKey key = this.processKey;
		key.interestOps(key.interestOps() & OP_NOT_READ);
	}

	protected void enableRead() {
		boolean needWakeup = false;
		try {
			SelectionKey key = this.processKey;
			key.interestOps(key.interestOps() | SelectionKey.OP_READ);
			needWakeup = true;
		} catch (Exception e) {
			LOGGER.warn("enable read fail ", e);
		}
		
		if (needWakeup) {
			processKey.selector().wakeup();
		}
	}

	protected void disableWrite() {
		try {
			SelectionKey key = this.processKey;
			key.interestOps(key.interestOps() & OP_NOT_WRITE);
		} catch (Exception e) {
			LOGGER.warn("can't disable write " + this, e);
		}
	}

	protected void enableWrite(boolean wakeup) {
		boolean needWakeup = false;
		try {
			SelectionKey key = this.processKey;
			key.interestOps(key.interestOps() | SelectionKey.OP_WRITE);
			needWakeup = true;
		} catch (Exception e) {
			LOGGER.warn("can't enable write: ", e);
		}
		
		if (needWakeup && wakeup) {
			processKey.selector().wakeup();
		}
	}
	
}