package com.lenzhao.framework.connect;

import java.net.InetSocketAddress;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

import com.lenzhao.framework.common.RpcContext;
import com.lenzhao.framework.protocol.RpcResponse;
import org.jboss.netty.bootstrap.ClientBootstrap;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFactory;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelHandler;
import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;

import com.lenzhao.framework.common.Constants;
import com.lenzhao.framework.config.ClientConfig;
import com.lenzhao.framework.exception.RpcException;
import com.lenzhao.framework.protocol.FutureAdapter;
import com.lenzhao.framework.protocol.InvokeFuture;
import com.lenzhao.framework.protocol.RpcRequest;
import com.lenzhao.framework.protocol.RpcRequestEncode;
import com.lenzhao.framework.protocol.RpcResponseDecode;
import com.lenzhao.framework.util.NamedTheadFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *netty客户端长连接
 */
public class NettyRpcConnection extends SimpleChannelHandler implements IRpcConnection {

	private static final Logger logger = LoggerFactory.getLogger(NettyRpcConnection.class);
	
	private static final ScheduledThreadPoolExecutor executorService = new ScheduledThreadPoolExecutor(1, new NamedTheadFactory("ConnectionHeart"));

	private volatile long lastConnectedTime = System.currentTimeMillis();
	
	private InetSocketAddress inetAddr;

	private volatile Channel channel;

	//是否已经连接的标示,初始化打开和周期检测时会设置该标示
	private volatile AtomicBoolean connected = new AtomicBoolean(false);
	//客户端配置文件
	private static final ClientConfig clientConfig = ClientConfig.getInstance();
	
	private ClientBootstrap bootstrap = null;
	
	//处理超时事件
//	private Timer timer=null;
	
	private static final ChannelFactory factory = new NioClientSocketChannelFactory(
			Executors.newCachedThreadPool(),
			Executors.newCachedThreadPool(),clientConfig.getMaxThreadCount());
	
	public NettyRpcConnection(String connStr) {
		this.inetAddr = new InetSocketAddress(connStr.split(":")[0],Integer.parseInt(connStr.split(":")[1]));
		initReconnect();
	}

	public NettyRpcConnection(String host, int port) {
		this.inetAddr = new InetSocketAddress(host, port);
		initReconnect();
	}

	public RpcResponse sendRequest(RpcRequest request, boolean async) throws Throwable {
		if (!isConnected() || !channel.isConnected()) {
			throw new RpcException("not connected");
		}
		//如果request已经超时,直接抛弃
		if(System.currentTimeMillis() - request.getAddTime().getTime() > Constants.TIMEOUT_INVOKE_MILLSECOND) {
			logger.error("request timeout exception");
			throw new RpcException("request timeout exception");
		}
		//异步发送请求
		InvokeFuture invokeFuture = new InvokeFuture(channel,request);
		invokeFuture.send();
		if(async) {
			//如果是异步,则封装context
			RpcContext.getContext().setFuture(new FutureAdapter<Object>(invokeFuture));
			return new RpcResponse();
		}
		else {
			//如果是同步,则阻塞调用get方法
			RpcContext.getContext().setFuture(null);
			return invokeFuture.get(Constants.TIMEOUT_INVOKE_MILLSECOND);
		}
	}
	
	/**
	 * 初始化连接
	 */
	public void open() throws Throwable {
		open(true);
	}

	/**
	 * @param connectStatus 心跳检测状态是否正常
	 * @throws Throwable
	 */
	public void open(boolean connectStatus) throws Throwable {
		logger.info("open start,"+getConnStr());
		bootstrap = new ClientBootstrap(factory);
//		timer = new HashedWheelTimer();
		{
			bootstrap.setOption("tcpNoDelay", Boolean.parseBoolean(clientConfig.getTcpNoDelay()));
			bootstrap.setOption("reuseAddress", Boolean.parseBoolean(clientConfig.getReuseAddress()));
			bootstrap.setOption("SO_RCVBUF",1024*128);
			bootstrap.setOption("SO_SNDBUF",1024*128);
			bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
				public ChannelPipeline getPipeline() {
					ChannelPipeline pipeline = Channels.pipeline();
//					int readTimeout = clientConfig.getReadTimeout();
//					if (readTimeout > 0) {
//						pipeline.addLast("timeout", new ReadTimeoutHandler(timer,
//								readTimeout, TimeUnit.MILLISECONDS));
//					}
					pipeline.addLast("encoder", new RpcRequestEncode());
					pipeline.addLast("decoder", new RpcResponseDecode());
					pipeline.addLast("handler", NettyRpcConnection.this);
					return pipeline;
				}
			});
		}
		connected.set(connectStatus);
		logger.info("open finish,"+getConnStr());
	}
	
	public void initReconnect() {
		Runnable connectStatusCheckCommand =  new Runnable() {
			@Override
			public void run() {
				try {
				 if(!isConnected()) {
					 try {
						open(false);
						connect();
						connected.set(true);
					} catch (Throwable e) {
						logger.error("connect open error,conn: {}", getConnStr());
					}
				 }
				 if (isConnected() && isClosed()) {
					 try {
						 connect();
					} catch (Throwable e) {
						logger.error("connect error,conn: {}", getConnStr());
					}
                 }  
				 if(isConnected() && !isClosed()) {
                     lastConnectedTime = System.currentTimeMillis();
                 }
				 if (System.currentTimeMillis() - lastConnectedTime > Constants.TIMEOUT_HEARTBEAT_MILLSECOND) {
                     if (connected.get()) {
                    	 connected.set(false);
                    	 logger.error("connected has loss heartbeat,conn: {}", getConnStr());
                     }
                 }
				}
				catch(Throwable e) {
					logger.error("connectStatusCheckCommand error");
				}
			}
		};
		//1秒后每隔3秒发送一次心跳
		executorService.scheduleAtFixedRate(connectStatusCheckCommand, 1000, 3000, TimeUnit.MILLISECONDS);
	}
	
	/**
	 * 尝试连接
	 */
	public void connect() {
        ChannelFuture future = bootstrap.connect(inetAddr);
        try{
            boolean ret = future.awaitUninterruptibly(Constants.TIMEOUT_CONNECTION_MILLSECOND, TimeUnit.MILLISECONDS);
            if (ret && future.isSuccess()) {
                Channel newChannel = future.getChannel();
                newChannel.setInterestOps(Channel.OP_READ_WRITE);
                try {
                    // 关闭旧的连接
                    Channel oldChannel = NettyRpcConnection.this.channel;
                    if (oldChannel != null) {
                        logger.info("Close old netty channel {} on create new netty channel {}", oldChannel, newChannel);
                        oldChannel.close();
                    }
                } finally {
                    if (!isConnected()) {
                        try {
                            logger.info("Close new netty channel {}, because the client closed.", newChannel);
                            newChannel.close();
                        } finally {
                        	NettyRpcConnection.this.channel = null;
                        }
                    } else {
                    	NettyRpcConnection.this.channel = newChannel;
                    }
                }
            } else if (null != future.getCause()) {
            	logger.error("connect fail", future.getCause());
            	throw new RuntimeException("connect error", future.getCause());
            } else {
            	logger.error("connect fail,connstr: "+this.getConnStr());
            	throw new RuntimeException("connect error");
            }
        }finally{
            if (! isConnected()) {
                future.cancel();
            }
        }
	}

	/**
	 * 客户端接受并处理消息
	 */
	public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
		RpcResponse response = (RpcResponse) e.getMessage();
		InvokeFuture.receive(channel, response);
	}
	

	public void close() throws Throwable {
		connected.set(false);
//		if (null != timer) {
//			timer.stop();
//			timer = null;
//		}
		if (null != channel) {
			channel.close().awaitUninterruptibly();
			channel.getFactory().releaseExternalResources();

			synchronized (channel) {
				channel.notifyAll();
			}
			channel = null;
		}
	}

	public boolean isConnected() {
		return connected.get();
	}

	public boolean isClosed() {
		return (null == channel) || !channel.isConnected() || !channel.isReadable() || !channel.isWritable();
	}

	public String getConnStr() {
		return inetAddr.getHostName()+":"+inetAddr.getPort();
	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e)
			throws Exception {
		super.exceptionCaught(ctx, e);
		logger.error("exceptionCaught", e.getCause());
	}

}