package org.xson.core.util;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.xson.core.XsonConst;
import org.xson.core.XsonException;
import org.xson.core.asm.ClassWriter;

/**
 * @author david<[email protected]>
 * @since JDK1.6
 */
public class XsonTypeUtils {

	private static XsonClassLoader	classLoader	= new XsonClassLoader();

	public static char				ARRAY_MARK	= '[';

	public static String toAsmClass(String className) {
		return className.replace('.', '/');
	}

	public static String getLittleClassName(String fullName) {
		int index = fullName.lastIndexOf(".");
		if (index > 0) {
			return fullName.substring(index + 1);
		} else {
			return fullName;
		}
	}

	public static List<FieldModel> getFieldModels(Class<?> clazz) {
		List<FieldModel> fieldInfoList = XsonConst.BEAN_FIELD_MODEL_MAP.get(clazz);
		if (null != fieldInfoList) {
			return fieldInfoList;
		}
		fieldInfoList = getFieldModels0(clazz);
		XsonConst.BEAN_FIELD_MODEL_MAP.put(clazz, fieldInfoList);
		return fieldInfoList;
	}

	private static List<FieldModel> getFieldModels0(Class<?> clazz) {

		List<Field> fieldList = getFieldList(clazz);

		Map<String, Method> methodMap = getMethodMap(clazz);

		List<FieldModel> fieldInfoList = new ArrayList<FieldModel>();

		int size = fieldList.size();
		for (int i = 0; i < size; i++) {
			Field field = fieldList.get(i);
			if (checkField(field)) {
				continue;
			}
			String fieldName = field.getName();

			String getName = getFormatMethodName("get", fieldName);
			Method getter = methodMap.get(getName);

			if (null == getter) {
				getName = getFormatMethodName("is", fieldName);
				getter = methodMap.get(getName);
				if (null == getter) {
					continue;
				}
			}

			if (checkGetter(getter)) {
				continue;
			}

			String setName = getFormatMethodName("set", fieldName);
			Method setter = methodMap.get(setName);
			if (null == setter) {
				continue;
			}

			if (checkSetter(setter, field)) {
				continue;
			}

			fieldInfoList.add(new FieldModel(field, getter, setter, clazz));
		}

		Collections.sort(fieldInfoList);
		return fieldInfoList;
	}

	private static String getFormatMethodName(String prefix, String name) {
		if (1 == name.length()) {
			return prefix + name.toUpperCase();
		} else {
			return prefix + name.substring(0, 1).toUpperCase() + name.substring(1);
		}
	}

	private static boolean checkField(Field field) {
		int modifiers = field.getModifiers();
		if (Modifier.isFinal(modifiers)) {
			return true;
		}
		if (Modifier.isTransient(modifiers)) {
			return true;
		}
		if (Modifier.isStatic(modifiers)) {
			return true;
		}
		return false;
	}

	private static boolean checkGetter(Method method) {
		int modifiers = method.getModifiers();
		if (Modifier.isStatic(modifiers)) {
			return true;
		}
		if (Modifier.isNative(modifiers)) {
			return true;
		}
		if (Modifier.isAbstract(modifiers)) {
			return true;
		}
		if (method.getReturnType().equals(Void.TYPE)) {
			return true;
		}
		if (method.getParameterTypes().length != 0) {
			return true;
		}
		if (method.getReturnType() == ClassLoader.class) {
			return true;
		}
		if (method.getName().equals("getClass")) {
			return true;
		}
		return false;
	}

	private static boolean checkSetter(Method method, Field field) {
		int modifiers = method.getModifiers();
		if (Modifier.isStatic(modifiers)) {
			return true;
		}
		if (Modifier.isNative(modifiers)) {
			return true;
		}
		if (Modifier.isAbstract(modifiers)) {
			return true;
		}
		if (!method.getReturnType().equals(Void.TYPE)) {
			return true;
		}
		if (method.getParameterTypes().length != 1) {
			return true;
		}
		if (!method.getParameterTypes()[0].equals(field.getType())) {
			return true;
		}
		return false;
	}

	private static List<Field> getFieldList(Class<?> clazz) {
		List<Field> fieldList = new ArrayList<Field>();
		Class<?> currentClazz = clazz;
		do {
			Field[] fields = currentClazz.getDeclaredFields();
			for (Field item : fields) {
				fieldList.add(item);
			}
			currentClazz = currentClazz.getSuperclass();
		} while (currentClazz != null && currentClazz != Object.class);
		return fieldList;
	}

	public static Map<String, Method> getMethodMap(Class<?> clazz) {
		Map<String, Method> methodMap = new HashMap<String, Method>();
		Method[] methods = clazz.getMethods();
		int length = methods.length;
		for (int i = 0; i < length; i++) {
			methodMap.put(methods[i].getName(), methods[i]);
		}
		return methodMap;
	}

	public static int getArrayDimensions(Class<?> clazz) {
		String var = clazz.getName();
		char[] data = var.toCharArray();
		int count = 0;
		for (int i = 0; i < data.length; i++) {
			if (ARRAY_MARK == data[i]) {
				count++;
			} else {
				break;
			}
		}
		return count;
	}

	public static String[] getExceptionDesc(Method method) {
		Class<?>[] exClasses = method.getExceptionTypes();
		if (null != exClasses && exClasses.length > 0) {
			String[] exClassesName = new String[exClasses.length];
			for (int i = 0; i < exClasses.length; i++) {
				exClassesName[i] = toAsmClass(exClasses[i].getName());
			}
			return exClassesName;
		}
		return null;
	}

	public static String getDesc(Method method) {
		StringBuffer buf = new StringBuffer();
		buf.append("(");
		java.lang.Class<?>[] types = method.getParameterTypes();
		for (int i = 0; i < types.length; ++i) {
			buf.append(getDesc(types[i]));
		}
		buf.append(")");
		buf.append(getDesc(method.getReturnType()));
		return buf.toString();
	}

	public static String getDesc(Class<?> returnType) {
		if (returnType.isPrimitive()) {
			return getPrimitiveLetter(returnType);
		} else if (returnType.isArray()) {
			return "[" + getDesc(returnType.getComponentType());
		} else {
			return "L" + getType(returnType) + ";";
		}
	}

	public static String getType(Class<?> parameterType) {
		if (parameterType.isArray()) {
			return "[" + getDesc(parameterType.getComponentType());
		} else {
			if (!parameterType.isPrimitive()) {
				String clsName = parameterType.getName();
				return clsName.replaceAll("\\.", "/");
			} else {
				return getPrimitiveLetter(parameterType);
			}
		}
	}

	public static String getPrimitiveLetter(Class<?> type) {
		if (Integer.TYPE.equals(type)) {
			return "I";
		} else if (Void.TYPE.equals(type)) {
			return "V";
		} else if (Boolean.TYPE.equals(type)) {
			return "Z";
		} else if (Character.TYPE.equals(type)) {
			return "C";
		} else if (Byte.TYPE.equals(type)) {
			return "B";
		} else if (Short.TYPE.equals(type)) {
			return "S";
		} else if (Float.TYPE.equals(type)) {
			return "F";
		} else if (Long.TYPE.equals(type)) {
			return "J";
		} else if (Double.TYPE.equals(type)) {
			return "D";
		}
		throw new IllegalStateException("Type: " + type.getCanonicalName() + " is not a primitive type");
	}

	public static String getComponentDesc(Class<?> type, int dimension) {
		String brackets = "";
		int count = dimension;
		while (count-- > 1) {
			brackets += "[";
		}
		String clsName = null;
		if (type.isPrimitive()) {
			clsName = getPrimitiveLetter(type);
		} else {
			clsName = "L" + type.getName() + ";";
		}
		return brackets + clsName;
	}

	public static Class<?> getArrayType(Class<?> componentType, int dimension) {
		if (1 == dimension) {
			return componentType;
		} else if (dimension > 1) {
			// return loadClass(getComponentDesc(componentType, dimension));
			return findClass(getComponentDesc(componentType, dimension));
		} else {
			throw new XsonException("Unlawful dimension:" + dimension);
		}
	}

	public static Class<?> getComponentType(Class<?> type) {
		if (type.isArray()) {
			return getComponentType(type.getComponentType());
		}
		return type;
	}

	public static Class<?> findClass(String className) {
		Throwable prob = null;
		ClassLoader loader = Thread.currentThread().getContextClassLoader();

		if (loader != null) {
			try {
				return Class.forName(className, true, loader);
			} catch (Exception e) {
				prob = e;
			}
		}
		try {
			return Class.forName(className);
		} catch (Exception e) {
			prob = e;
		}

		throw new XsonException(prob);
	}

	public static Object createByteCode(ClassWriter classWriter, String className) throws Exception {
		byte[] classCode = classWriter.toByteArray();

		// logger
		// writeClass(classCode, className);
		// System.out.println("createByteCode:" + className);

		Class<?> exampleClass = classLoader.defineClassPublic(className, classCode, 0, classCode.length);
		Object instance = exampleClass.newInstance();
		return instance;
	}

	public static void writeClass(byte[] code, String className) {
		// String classPath = "C:/Users/david/Desktop/aaadd/code/";
		// String classPath = "C:/Users/Administrator/Desktop/jd-gui/";
		String classPath = "C:/Users/gaop/Desktop/反编译/xson/";
		try {
			FileOutputStream fos = new FileOutputStream(classPath + className + ".class");
			fos.write(code);
			fos.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

}