package jp.sf.amateras.mirage.bean;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class BeanDescImpl implements BeanDesc {

	private Class<?> clazz;
	private Map<String, PropertyDesc> propertyMap = new ConcurrentHashMap<String, PropertyDesc>();
	private PropertyDesc[] propertyArray;

	public BeanDescImpl(Class<?> clazz){
		this.clazz = clazz;

		List<PropertyDesc> list = new ArrayList<PropertyDesc>();

		Map<String, PropertyInfo> map = new HashMap<String, PropertyInfo>();

		Method[] methods = clazz.getMethods();
		for(Method method: methods){
			// ignore java.lang.Object methods
			if(method.getDeclaringClass() == Object.class){
				continue;
			}

			String methodName = method.getName();

			if((methodName.startsWith("get") || methodName.startsWith("is")) && method.getParameterTypes().length == 0){
				String propertyName = getPropertyName(methodName);
				PropertyInfo info = map.get(propertyName);
				if(info == null){
					info = new PropertyInfo();
					info.name = propertyName;
					info.getterMethod = method;
					info.type = method.getReturnType();
					map.put(propertyName, info);
				} else if(info.type == method.getReturnType()){
					info.getterMethod = method;
				}
			}
			if(methodName.startsWith("set") && method.getParameterTypes().length == 1){
				String propertyName = getPropertyName(methodName);
				PropertyInfo info = map.get(propertyName);
				if(info == null){
					info = new PropertyInfo();
					info.name = propertyName;
					info.setterMethod = method;
					info.type = method.getParameterTypes()[0];
					map.put(propertyName, info);
				} else if(info.type == method.getParameterTypes()[0]){
					info.setterMethod = method;
				}
			}
		}

		for(PropertyInfo info: map.values()){
			PropertyDesc pd = new PropertyDescImpl(this, info.name, info.type, null, info.getterMethod, info.setterMethod);
			list.add(pd);
			this.propertyMap.put(pd.getPropertyName(), pd);
		}

		Field[] fields = clazz.getFields();
		for(Field field: fields){
			if(!map.containsKey(field.getName())){
				PropertyDesc pd = new PropertyDescImpl(this, field.getName(), field.getType(), field, null, null);
				list.add(pd);
				this.propertyMap.put(pd.getPropertyName(), pd);
			}
		}

		this.propertyArray = list.toArray(new PropertyDesc[list.size()]);
	}

	@Override
	public Class<?> getType(){
		return clazz;
	}

	@Override
	public PropertyDesc getPropertyDesc(String name){
		return propertyMap.get(name);
	}

	@Override
	public int getPropertyDescSize(){
		return propertyArray.length;
	}

	@Override
	public PropertyDesc getPropertyDesc(int i){
		return propertyArray[i];
	}

	protected static String getPropertyName(String methodName){
		if(methodName.startsWith("get") || methodName.startsWith("set")){
			methodName = methodName.substring(3);
		}
		if(methodName.startsWith("is")){
			methodName = methodName.substring(2);
		}
		StringBuilder sb = new StringBuilder();
		for(int i=0;i<methodName.length();i++){
			char c = methodName.charAt(i);
			if(i == 0){
				sb.append(String.valueOf(c).toLowerCase());
			} else {
				sb.append(c);
			}
		}
		return sb.toString();
	}

	private static class PropertyInfo {
		public String name;
		public Class<?> type;
		public Method getterMethod;
		public Method setterMethod;
	}

	@Override
	public <T extends Annotation> T getAnnotation(Class<T> type) {
		return clazz.getAnnotation(type);
	}

}
