/*
 * This file is part of Nuts Framework.
 * Copyright(C) 2009-2012 Nuts Develop Team.
 *
 * Nuts Framework is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License any later version.
 * 
 * Nuts Framework is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Nuts Framework. If not, see <http://www.gnu.org/licenses/>.
 */
package nuts.core.beans;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/**
 * JavaBeanHandler
 */
public class JavaBeanHandler<T> extends AbstractJavaBeanHandler<T> {

	private Map<String, PropertyDescriptor> descriptors;
	private String[] readPropertyNames;
	private String[] writePropertyNames;

	/**
	 * Constructor
	 * @param factory bean handler factory
	 * @param type bean type
	 */
	public JavaBeanHandler(BeanHandlerFactory factory, Class<T> type) {
		super(factory, type);

		BeanInfo beanInfo;
		try {
			beanInfo = Introspector.getBeanInfo(type);
		}
		catch (IntrospectionException e) {
			throw new RuntimeException(e);
		}

		descriptors = new HashMap<String, PropertyDescriptor>();
		for (PropertyDescriptor pd : beanInfo.getPropertyDescriptors()) {
			descriptors.put(pd.getName(), pd);
			try {
				if (Character.isUpperCase(pd.getName().charAt(0))) {
					String pn = pd.getName().substring(0, 1).toLowerCase()
							+ pd.getName().substring(1);
					PropertyDescriptor pd2 = new PropertyDescriptor(pn, pd.getReadMethod(), pd.getWriteMethod());
					descriptors.put(pd.getName(), pd2);
				}
			}
			catch (Exception e) {
				throw new RuntimeException(e);
			}
		}
		
		List<String> readPropertyNames = new ArrayList<String>();
		List<String> writePropertyNames = new ArrayList<String>();
		for (Entry<String, PropertyDescriptor> en : descriptors.entrySet()) {
			String pn = en.getKey();
			PropertyDescriptor pd = en.getValue();
			
			Method getter = pd.getReadMethod();
			if (getter != null) {
				readPropertyNames.add(pn);
			}
			
			Method setter = pd.getWriteMethod();
			if (setter != null) {
				writePropertyNames.add(pn);
			}
		}
		this.readPropertyNames = readPropertyNames.toArray(new String[readPropertyNames.size()]);
		this.writePropertyNames = writePropertyNames.toArray(new String[writePropertyNames.size()]);
	}

	/**
	 * get read property names
	 * @param beanObject bean object (can be null)
	 * @return property names
	 */
	public String[] getReadPropertyNames(T beanObject) {
		return readPropertyNames;
	}

	/**
	 * get write property names
	 * @param beanObject bean object (can be null)
	 * @return property names
	 */
	public String[] getWritePropertyNames(T beanObject) {
		return writePropertyNames;
	}

	/**
	 * get simple property type
	 * @param beanObject bean object (can be null)
	 * @param propertyName property name
	 * @return property type
	 */
	public Class getPropertyType(T beanObject, String propertyName) {
		return getPropertyDescriptor(propertyName).getPropertyType();
	}

	/**
	 * get simple property value 
	 * @param beanObject bean object
	 * @param propertyName property name
	 * @return value
	 */
	public Object getPropertyValue(T beanObject, String propertyName) {
		PropertyDescriptor descriptor = getPropertyDescriptor(propertyName);
		Method getter = getReadMethod(descriptor);
		try {
			return getter.invoke(beanObject);
		}
		catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * set simple property value 
	 * @param beanObject bean object
	 * @param propertyName property name
	 * @param value value
	 */
	public void setPropertyValue(T beanObject, String propertyName, Object value) {
		PropertyDescriptor descriptor = getPropertyDescriptor(propertyName);
		Method setter = getWriteMethod(descriptor);
		try {
			setter.invoke(beanObject, value);
		}
		catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * getPropertyDescriptor
	 * 
	 * @param propertyName property name
	 * @return PropertyDescriptor
	 */
	private PropertyDescriptor getPropertyDescriptor(String propertyName) {
		PropertyDescriptor descriptor = descriptors.get(propertyName);
		if (descriptor == null) {
			throw noSuchPropertyException(propertyName);
		}
		return descriptor;
	}
	
	/**
	 * getReadMethod
	 * @param propertyDescriptor PropertyDescriptor
	 * @return read method
	 * @throws RuntimeException if read method is null 
	 */
	private Method getReadMethod(PropertyDescriptor propertyDescriptor) {
		Method getter = propertyDescriptor.getReadMethod();
		if (getter == null) {
			throw noGetterMethodException(propertyDescriptor.getName());
		}
		return getter;
	}
	
	/**
	 * getWriteMethod
	 * @param propertyDescriptor PropertyDescriptor
	 * @return write method
	 * @throws RuntimeException if write method is null 
	 */
	private Method getWriteMethod(PropertyDescriptor propertyDescriptor) {
		Method setter = propertyDescriptor.getWriteMethod();
		if (setter == null) {
			throw noSetterMethodException(propertyDescriptor.getName());
		}
		return setter;
	}
}
