package cn.lechange.happor.utils;

import java.util.concurrent.TimeUnit;

import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.DefaultFullHttpRequest;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpRequestEncoder;
import io.netty.handler.codec.http.HttpResponseDecoder;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.AttributeKey;

public class AsyncHttpClient {

	private static Logger logger = Logger.getLogger(AsyncHttpClient.class);

	private Bootstrap bootstrap;
	
	private int timeout = 3;
	private int maxHttpSize = 8000;

	public int getTimeout() {
		return timeout;
	}

	public void setTimeout(int timeout) {
		this.timeout = timeout;
	}

	public int getMaxHttpSize() {
		return maxHttpSize;
	}

	public void setMaxHttpSize(int maxHttpSize) {
		this.maxHttpSize = maxHttpSize;
	}

	public AsyncHttpClient() {
		EventLoopGroup workerGroup = new NioEventLoopGroup();
		bootstrap = new Bootstrap();
		bootstrap.group(workerGroup);
		bootstrap.channel(NioSocketChannel.class);
		bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
		bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000);
		bootstrap.handler(new ChannelInitializer<SocketChannel>() {
			@Override
			public void initChannel(SocketChannel ch) throws Exception {
				ChannelInboundHandlerAdapter handler = new SimpleChannelInboundHandler<FullHttpResponse>() {

					@Override
					protected void channelRead0(ChannelHandlerContext ctx,
							FullHttpResponse response) throws Exception {
						// TODO Auto-generated method stub
						Callback cb = ctx.channel().attr(KEY_CB).get();
						FullHttpRequest request = ctx.channel().attr(KEY_REQ).get();
						logger.info("recv http response[" + request.getUri() + "] " + response.getStatus());
						cb.onResponse(response);
						ctx.channel().close();
					}

					@Override
					public void userEventTriggered(ChannelHandlerContext ctx,
							Object evt) throws Exception {
						if (evt instanceof IdleStateEvent) {
							Callback cb = ctx.channel().attr(KEY_CB).get();
							FullHttpRequest request = ctx.channel().attr(KEY_REQ).get();
							logger.warn("http request[" + request.getUri() + "] timeout.");
							cb.onTimeout();
							ctx.channel().close();
						}
					}
				};

				ch.pipeline()
						.addLast(new HttpResponseDecoder())
						.addLast(new HttpRequestEncoder())
						.addLast(new HttpObjectAggregator(getMaxHttpSize()))
						.addLast(new IdleStateHandler(getTimeout(), getTimeout(), getTimeout(), TimeUnit.SECONDS))
						.addLast(handler);
			}
		});
	}

	public static interface Callback {
		public void onResponse(FullHttpResponse response);
		public void onTimeout();
		public void onConnectFail();
	}

	final static AttributeKey<Callback> KEY_CB = AttributeKey.valueOf("cb");
	final static AttributeKey<FullHttpRequest> KEY_REQ = AttributeKey.valueOf("req");

	public void sendRequest(final String host, final int port,
			final FullHttpRequest request, final Callback cb) {
		logger.info("send http request[" + request.getUri() + "] to " + host + ":" + port);

		bootstrap.connect(host, port).addListener(new ChannelFutureListener() {

			public void operationComplete(ChannelFuture f) throws Exception {
				// TODO Auto-generated method stub
				if (f.isSuccess()) {
					request.headers().set("Host", host + ":" + port);
					f.channel().attr(KEY_CB).set(cb);
					f.channel().attr(KEY_REQ).set(request);
					f.channel().writeAndFlush(request);
				} else {
					logger.warn("connect to " + host + ":" + port + " failed.");
					cb.onConnectFail();
				}
			}

		});
	}

	public static void main(String[] args) {
		PropertyConfigurator.configure("conf/log4j.properties");

		AsyncHttpClient client = new AsyncHttpClient();
		DefaultFullHttpRequest request = new DefaultFullHttpRequest(
				HttpVersion.HTTP_1_1, HttpMethod.GET, "/AsyncHttpClient");
		client.sendRequest("127.0.0.1", 8888, request, new Callback() {

			public void onResponse(FullHttpResponse response) {
				// TODO Auto-generated method stub
				System.out.println("AsyncHttpClient onResponse");
			}

			public void onTimeout() {
				// TODO Auto-generated method stub
				System.out.println("AsyncHttpClient onTimeout");
			}

			public void onConnectFail() {
				// TODO Auto-generated method stub
				System.out.println("AsyncHttpClient onConnectFail");
			}

		});
	}

}