/**
 * 
 */
package io.client.thrift;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.Socket;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

import javax.net.SocketFactory;

/**
 * @author HouKangxi
 *
 */
public class ClientInterfaceFactory {
	private ClientInterfaceFactory() {
	}

	private static ConcurrentHashMap<Long, Object> ifaceCache = new ConcurrentHashMap<Long, Object>();

	/**
	 * 获得与服务端通信的接口对象
	 * <p>
	 * 调用者可以实现自定义的 SocketFactory来内部配置Socket参数(如超时时间,SSL等),也可以通过返回包装的Socket来实现连接池
	 * <br/><pre>
	 *  class SocketPool extends javax.net.SocketFactory{
	 *    public Socket createSocket(String methodName,int flag)throws IOException{
	 *      return getSocketFromPool();
	 *    }
	 *    private MySocketWrapper getSocketFromPool(){
	 *     ...
	 *    }
	 *    private void returnToPool(MySocketWrapper socket){
	 *    ...
	 *    }
	 *  }
	 *  class MySocketWrapper extends Socket{
	 *    private Socket target;
	 *    private SocketPool pool;
	 *    // 包装close()方法,归还连接到连接池
	 *    public void close()throws IOException{
	 *      pool.returnToPool(this);
	 *    }
	 *    public OutputStream getOutputStream()throws IOException{
	 *        return target.getOutputStream();
	 *    }
	 *    public InputStream getInputStream()throws IOException{
	 *        return target.getInputStream();
	 *    }
	 *  }
	 * </pre>
	 * <br/>
	 * 
	 * 
	 * @param ifaceClass
	 *            - 接口class
	 * @param factory
	 *            - 套接字工厂类, 注意:需要实现 createSocket() 方法,需要实现hashCode()方法来区分factory
	 * @return 接口对象
	 */
	@SuppressWarnings("unchecked")
	public static <INTERFACE> INTERFACE getClientInterface(Class<INTERFACE> ifaceClass, SocketFactory factory) {
		long part1 = ifaceClass.getName().hashCode();
		final Long KEY = (part1 << 32) | factory.hashCode();
		INTERFACE iface = (INTERFACE) ifaceCache.get(KEY);
		if (iface == null) {
			iface = (INTERFACE) Proxy.newProxyInstance(ifaceClass.getClassLoader(), new Class[] { ifaceClass },
					new Handler(factory));
			ifaceCache.putIfAbsent(KEY, iface);
		}
		return iface;
	}

	private static class Handler implements InvocationHandler {
		final AtomicInteger seqIdHolder = new AtomicInteger(0);
		final SocketFactory factory;

		public Handler(SocketFactory factory) {
			this.factory = factory;
		}

		public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
			String methodName = method.getName();
			if (args == null || args.length == 0) {
				if (methodName.equals("toString")) {
					return Handler.class.getName() + "@" + System.identityHashCode(this);
				}
				if (methodName.equals("hashCode")) {
					return System.identityHashCode(this);
				}
			}

			int seqId = seqIdHolder.incrementAndGet();
			ByteArrayOutputStream outbuff = new ByteArrayOutputStream();
			TCompactProtocol protocol = new TCompactProtocol(outbuff, null);

			ProtocolIOUtil.write(methodName, seqId, protocol, method.getGenericParameterTypes(), args);
			Socket connection = null;
			Object rs = null;
			try {
				connection = factory.createSocket();
				OutputStream out = connection.getOutputStream();
				out.write(outbuff.toByteArray());
				out.flush();
				InputStream in = connection.getInputStream();
				if (in != null) {
					protocol.transIn = in;
					rs = ProtocolIOUtil.read(protocol, method.getGenericReturnType(), method.getExceptionTypes(),
							seqId);
				}
			} finally {
				if (connection != null) {
					connection.close();
				}
			}
			return rs;
		}

	}

}