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

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.net.ProtocolException;
import java.net.Socket;
import java.net.URL;
import java.net.URLConnection;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

import javax.net.SocketFactory;

import org.json.JSONArray;
import org.json.JSONObject;

import io.client.thrift.Json.Strategy;
import io.client.thrift.annotaion.Index;

/**
 * 使用 HTTP + JSON 协议
 * <p>
 * 只有这一个文件,适合轻量级通信,如单一接口。由于依赖的 org.json 包 在 android.jar中 已存在,代码整体尺寸很小。
 * 
 * @author HouKangxi
 *
 */
public class ClientInterfaceFactory {

	private ClientInterfaceFactory() {
	}

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

	/**
	 * 获得与服务端通信的接口对象
	 * <p>
	 * 调用者可以实现自定义的 SocketFactory来内部配置Socket参数(如超时时间,SSL等),也可以通过返回包装的Socket来实现连接池<br/>
	 * SocketFactory::createSocket(String host,int ip)//NOTE: 实际传入createSocket(methodName,flag)
	 * 
	 * @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;
	}
    
	@SuppressWarnings("unchecked")
	public static <INTERFACE> INTERFACE getClientInterface(Class<INTERFACE> ifaceClass, String host) {
		long part1 = ifaceClass.getName().hashCode();
		final Long KEY = (part1 << 32) | host.hashCode();
		INTERFACE iface = (INTERFACE) ifaceCache.get(KEY);
		if (iface == null) {
			iface = (INTERFACE) Proxy.newProxyInstance(ifaceClass.getClassLoader(), new Class[] { ifaceClass },
					new Handler(host));
			ifaceCache.putIfAbsent(KEY, iface);
		}
		return iface;
	}

	private static ConcurrentHashMap<Class<?>, Map<Object, Field>> fieldCache = new ConcurrentHashMap<Class<?>, Map<Object, Field>>();

	private static class Handler extends Json.Strategy implements InvocationHandler {
		final AtomicInteger seqIdHolder = new AtomicInteger(0);
		final SocketFactory factory;
		String host;

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

		Handler(String host) {
			factory = null;
			this.host = host;
		}

		{ // 只处理public 且非 static 的字段
			publicFieldsOnly();
		}

		@Override
		public String fieldName(Field field) {
			Index id = field.getAnnotation(Index.class);
			if (id != null) {
				return String.valueOf(id.value());
			}
			return super.fieldName(field);
		}

		@Override
		public Field field(Class<?> cls, String fieldName) throws NoSuchFieldException, SecurityException {
			char c0 = fieldName.charAt(0);
			if (c0 >= '0' && c0 <= '9') {
				Map<Object, Field> cache = fieldCache.get(cls);
				if (cache == null) {
					Field[] fs = cls.getFields();
					cache = new HashMap<Object, Field>(fs.length);
					for (Field f : fs) {
						Index id = f.getAnnotation(Index.class);
						if (id != null) {
							cache.put(id.value(), f);
						}
					}
					fieldCache.putIfAbsent(cls, cache);
				}
				Field fd = cache.get(Integer.parseInt(fieldName));
				return fd;
			}
			return super.field(cls, fieldName);
		}

		@Override
		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();
			//

			StringBuilder sb = new StringBuilder(256);
			sb.append('[').append('"').append(methodName).append('"')//
					.append(',').append(1).append(',').append(seqId).append(',')
					.append(args == null ? "[]" : Json.toJson(args, this)).append(']');
			String jsonStr = sb.toString();
			//
			System.out.println("===========  Request ============");
			System.out.println(jsonStr);
			//

			Socket connection = null;
			Reader reader = null;
			try {
				InputStream in;
				OutputStream out;
				if (factory != null) {
					connection = factory.createSocket(methodName, 0);
					out = connection.getOutputStream();
					out.write(jsonStr.getBytes());
					out.flush();
					in = connection.getInputStream();
				} else {
					URLConnection conn = new URL(host).openConnection();
					conn.setDoOutput(true);
					conn.connect();
					out = conn.getOutputStream();
					out.write(jsonStr.getBytes());
					out.flush();
					in = conn.getInputStream();
				}
				int firstByte = in.read();
				reader = new InputStreamReader(in, "UTF-8");
				sb = new StringBuilder(512);
				if (firstByte == '[') {
					sb.append((char) firstByte);
				}
				char[] charbuf = new char[512];
				int len;
				while ((len = reader.read(charbuf)) > 0) {
					sb.append(charbuf, 0, len);
					if (len < charbuf.length) {
						break;
					}
				}
				jsonStr = sb.toString();
			} catch (IOException ex) {
				jsonStr = "";
			} finally {
				try {
					if (connection != null) {
						connection.close();
					} else if (reader != null) {
						reader.close();
					}
				} catch (IOException e) {
				}
			}
			System.out.println("===========  response json: ===========");
			System.out.println(jsonStr);
			if (jsonStr == null || jsonStr.length() < 1) {
				return null;
			}
			return read(jsonStr, method.getReturnType(), method.getExceptionTypes(), seqId, this);
		}

	}

	private static <T> T read(String json, Type resultBeanClass, Class<?>[] exceptionsTypes, int seqid_,
			Strategy strategy) throws Throwable {
		JSONArray arr = new JSONArray(json);
		String methodName = arr.getString(0);
		int type = arr.getInt(1);
		int seqid = arr.getInt(2);
		if (type == 3) {
			JSONObject respFull = arr.getJSONObject(arr.length() - 1);
			String errMsg = respFull.getString("message");
			throw new ProtocolException(errMsg);
		}
		// 验证seqId 与请求的是否相同
		if (seqid != seqid_) {
			throw new ProtocolException(methodName + " failed: out of sequence response");
		}
		JSONObject respFull = arr.getJSONObject(arr.length() - 1);
		Object respObj = respFull.opt("success");
		if (respObj == null) {
			respObj = respFull.opt("0");
		}
		if (respObj == null) {
			respObj = respFull.opt("ex");
			if (respObj == null) {
				respObj = respFull.opt("1");
			}
			if (respObj != null && exceptionsTypes != null && exceptionsTypes.length > 0) {
				resultBeanClass = exceptionsTypes[0];
				String objson = Json.toJson(resultBeanClass, respObj, strategy);
				Object ex = Json.fromJson(objson, resultBeanClass, strategy);
				if (ex instanceof Throwable) {
					throw (Throwable) ex;
				}
			}
			return null;
		} else {
			String objson = Json.toJson(resultBeanClass, respObj, strategy);
			T bean = Json.fromJson(objson, resultBeanClass, strategy);
			//
			return bean;
		}
	}
}