import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.URL;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;

import org.apache.commons.lang.StringUtils;
import org.apache.mina.common.ConnectFuture;
import org.apache.mina.common.IdleStatus;
import org.apache.mina.common.IoAcceptor;
import org.apache.mina.common.IoBuffer;
import org.apache.mina.common.IoHandlerAdapter;
import org.apache.mina.common.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.executor.ExecutorFilter;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
import org.apache.mina.transport.socket.nio.NioSocketConnector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class VertexMain extends IoHandlerAdapter {
	private static Logger log = LoggerFactory.getLogger(VertexMain.class);

	@Override
	public void exceptionCaught(IoSession sessionIn, Throwable causeIn) throws Exception {
		StringBuilder builder = new StringBuilder();
		causeIn.printStackTrace();
		log.info("Returning 500, throwable cause was : " + causeIn.getMessage());
		builder.append("HTTP/1.1 500 OK").append("\r\n\r\n");

		sessionIn.write(IoBuffer.wrap(builder.toString().getBytes()));
	}

	public static void main(String[] args) throws IOException {
		//		IoAcceptor acceptor = new NioSocketAcceptor(Runtime.getRuntime().availableProcessors() + 1);
		IoAcceptor acceptor = new NioSocketAcceptor();

		//		acceptor.getFilterChain().addLast("logger", new LoggingFilter());
		acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(new HttpProxyProtocolCodecFactory()));
		acceptor.getFilterChain().addLast("threadPool", new ExecutorFilter(Executors.newCachedThreadPool()));

		acceptor.setHandler(new VertexMain());

		acceptor.getSessionConfig().setReadBufferSize(2048);
		acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 10);
		acceptor.bind(new InetSocketAddress(8080));
		log.info("Started on port 8080");
	}

	@Override
	public void messageReceived(IoSession sessionIn, Object messageIn) throws Exception {
		HttpProxyRequest request = HttpProxyRequest.class.cast(messageIn);

		SimpleHttpClient simpleHttpClient = new SimpleHttpClient(request.getTargetUrl(), request.getMethod(), request.getHeaders(), sessionIn);
		simpleHttpClient.connect();
	}
}

class SimpleHttpClient extends IoHandlerAdapter {
	private static Logger log = LoggerFactory.getLogger(SimpleHttpClient.class);

	@Override
	public void exceptionCaught(IoSession session, Throwable cause) throws Exception {
		cause.printStackTrace();
		this.sessionClosed(session);
	}

	private URL targetUrl;
	private IoSession clientSession;
	private Map<String, List<String>> headers;
	private String method;

	public SimpleHttpClient(URL targetUrl, String method, Map<String, List<String>> headers, IoSession sessionIn) {
		this.targetUrl = targetUrl;
		this.method = method;
		this.headers = headers;
		this.clientSession = sessionIn;
	}

	public boolean connect() {
		// Create TCP/IP connector.
		NioSocketConnector connector = new NioSocketConnector();

		// Set connect timeout.
		connector.setConnectTimeout(30);
		connector.getSessionConfig().setReadBufferSize(4096);

		// Start communication.
		connector.setHandler(this);
		String host = targetUrl.getHost();
		int port = targetUrl.getPort();
		if (port == -1) {
			port = targetUrl.getDefaultPort();
		}

		ConnectFuture connect = connector.connect(new InetSocketAddress(host, port));
		connect.awaitUninterruptibly();

		return connect.isConnected();
	}

	@Override
	public void messageReceived(IoSession session, Object message) throws Exception {
		IoBuffer buffer = (IoBuffer) message;
		clientSession.write(message);
	}

	@Override
	public void sessionClosed(IoSession session) throws Exception {
		this.clientSession.close();
		log.info("Session closed for : " + this.targetUrl.toExternalForm());
	}

	@Override
	public void sessionCreated(IoSession session) throws Exception {
		StringBuilder request = new StringBuilder();

		String urlPart = this.targetUrl.getPath();
		if (this.targetUrl.getQuery() != null) {
			urlPart += "?" + this.targetUrl.getQuery();
		}
		request.append(this.method).append(" ").append(urlPart).append(" HTTP/1.1").append("\r\n");
		request.append("Host: ").append(this.targetUrl.getHost()).append("\r\n");

		for (String headerKey : this.headers.keySet()) {
			if (!headerKey.startsWith("Host")) {
				List<String> headerValues = this.headers.get(headerKey);
				request.append(headerKey).append(": ").append(StringUtils.join(headerValues.toArray())).append("\r\n");
			}
		}
		request.append("\r\n");

		String requestString = request.toString();
		if (!this.targetUrl.getFile().endsWith(".jpg")) {
			log.info("Request : \n" + requestString);
		}

		session.write(IoBuffer.wrap(requestString.getBytes()));
		log.info("Starting session for : " + this.targetUrl.toExternalForm());
	}
}