/*
 * 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.Introspector;
import java.beans.PropertyDescriptor;

import nuts.core.lang.ClassUtils;
import nuts.core.lang.DynamicClassLoader;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * FastBeanHandlerFactory
 */
public class FastBeanHandlerFactory extends BeanHandlerFactory {
	/**
	 * log
	 */
	private static Log log = LogFactory.getLog(FastBeanHandlerFactory.class);

	/**
	 * dynamicClassLoader
	 */
	private DynamicClassLoader dynamicClassLoader = new DynamicClassLoader();

	/**
	 * create java bean handler
	 * @param type bean type
	 * @return BeanHandler
	 */
	@Override
	protected BeanHandler createJavaBeanHandler(Class type) {
		try {
			return createFastBeanHandler(type);
		}
		catch (Throwable e) {
			log.warn("Failed to create FastBeanHandler, use default JavaBeanHandler instead.", e);
			return super.createJavaBeanHandler(type);
		}
	}

	private synchronized BeanHandler createFastBeanHandler(Class type) throws Exception {
		String packageName = "nuts.runtime." + type.getPackage().getName();
		String simpleName = type.getSimpleName() + "BeanHandler";
		String className = packageName + "." + simpleName;
		String typeName = type.getName().replace('$', '.');

		try {
			Class clazz = dynamicClassLoader.loadClass(className);
			return (BeanHandler)(ClassUtils.newInstance(clazz, this, BeanHandlerFactory.class));
		}
		catch (ClassNotFoundException e) {
		}

		StringBuilder src = new StringBuilder();

		src.append("package ").append(packageName).append(";\n");
		src.append("public class ")
			.append(simpleName)
			.append(" extends ")
			.append(AbstractJavaBeanHandler.class.getName())
			.append('<').append(typeName).append('>')
			.append(" {\n");

		src.append("  public ").append(simpleName).append("(")
			.append(BeanHandlerFactory.class.getName()).append(" factory) {\n");
		src.append("    super(factory, ").append(typeName).append(".class);\n");
		src.append("  }\n");

		BeanInfo beanInfo = Introspector.getBeanInfo(type);
		PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();

		boolean first = true;
		src.append("  private static String[] rpns = new String[] {\n");
		for (PropertyDescriptor pd : propertyDescriptors) {
			String propName = pd.getName();
			if (pd.getReadMethod() != null) {
				src.append("    ").append(first ? "" : ", ")
					.append("\"").append(propName).append("\"\n");
				first = false;
			}

			if (Character.isUpperCase(propName.charAt(0))) {
				propName = propName.substring(0, 1).toLowerCase() + propName.substring(1);
				if (pd.getReadMethod() != null) {
					src.append("    ").append(first ? "" : ", ")
						.append("\"").append(propName).append("\"\n");
				}
			}
		}
		src.append("  };\n");

		first = true;
		src.append("  private static String[] wpns = new String[] {\n");
		for (PropertyDescriptor pd : propertyDescriptors) {
			String propName = pd.getName();
			if (pd.getWriteMethod() != null) {
				src.append("    ").append(first ? "" : ", ")
					.append("\"").append(propName).append("\"\n");
				first = false;
			}

			if (Character.isUpperCase(propName.charAt(0))) {
				propName = propName.substring(0, 1).toLowerCase() + propName.substring(1);
				if (pd.getWriteMethod() != null) {
					src.append("    ").append(first ? "" : ", ")
						.append("\"").append(propName).append("\"\n");
				}
			}
		}
		src.append("  };\n");

		int i = 0;
		src.append("  private static java.util.Map<String, Integer> mm = new java.util.HashMap<String, Integer>();\n");
		src.append("  static {\n");
		for (PropertyDescriptor pd : propertyDescriptors) {
			String propName = pd.getName();
			src.append("    mm.put(\"").append(propName).append("\", ").append(++i).append(");\n");

			if (Character.isUpperCase(propName.charAt(0))) {
				propName = propName.substring(0, 1).toLowerCase() + propName.substring(1);
				src.append("    mm.put(\"").append(propName).append("\", ").append(++i).append(");\n");
			}
		}
		src.append("  }\n");

		src.append("  public String[] getReadPropertyNames(").append(typeName).append(" bo) {\n");
		src.append("    return rpns;\n");
		src.append("  }\n");
		
		src.append("  public String[] getWritePropertyNames(").append(typeName).append(" bo) {\n");
		src.append("    return wpns;\n");
		src.append("  }\n");
		
		src.append("  public Class getPropertyType(").append(typeName).append(" bo, String pn) {\n");
		src.append("    Integer i = mm.get(pn);\n");
		src.append("    if (i == null) {\n");
		src.append("      throw noSuchPropertyException(pn);\n");
		src.append("    }\n");
		src.append("    switch (i) {\n");
		i = 0;
		for (PropertyDescriptor pd : propertyDescriptors) {
			String propName = pd.getName();
			String propType = ClassUtils.getQualifiedClassName(pd.getPropertyType());

			src.append("    case ").append(++i)
				.append(": return ")
				.append(propType).append(".class;\n");

			if (Character.isUpperCase(propName.charAt(0))) {
				propName = propName.substring(0, 1).toLowerCase() + propName.substring(1);
				src.append("    case ").append(++i)
					.append(": return ")
					.append(propType).append(".class;\n");
			}
		}
		src.append("    default:\n");
		src.append("      throw noSuchPropertyException(pn);\n");
		src.append("    }\n");
		src.append("  }\n");

		src.append("  public Object getPropertyValue(").append(typeName).append(" bo, String pn) {\n");
		src.append("    Integer i = mm.get(pn);\n");
		src.append("    if (i == null) {\n");
		src.append("      throw noSuchPropertyException(pn);\n");
		src.append("    }\n");
		src.append("    switch (i) {\n");
		i = 0;
		for (PropertyDescriptor pd : propertyDescriptors) {
			String propName = pd.getName();
			String getter = null;
			if (pd.getReadMethod() != null) {
				getter = pd.getReadMethod().getName();
			}

			src.append("    case ").append(++i).append(": ");
			if (getter == null) {
				src.append("throw noGetterMethodException(pn);\n");
			}
			else {
				src.append("return bo.").append(getter).append("();\n");
			}

			if (Character.isUpperCase(propName.charAt(0))) {
				propName = propName.substring(0, 1).toLowerCase() + propName.substring(1);
				src.append("    case ").append(++i).append(": ");
				if (getter == null) {
					src.append("throw noGetterMethodException(pn);\n");
				}
				else {
					src.append("return bo.").append(getter).append("();\n");
				}
			}
		}
		src.append("    default:\n");
		src.append("      throw noSuchPropertyException(pn);\n");
		src.append("    }\n");
		src.append("  }\n");

		src.append("  public void setPropertyValue(").append(typeName).append(" bo, String pn, Object value) {\n");
		src.append("    Integer i = mm.get(pn);\n");
		src.append("    if (i == null) {\n");
		src.append("      throw noSuchPropertyException(pn);\n");
		src.append("    }\n");
		src.append("    switch (i) {\n");
		i = 0;
		for (PropertyDescriptor pd : propertyDescriptors) {
			String propName = pd.getName();
			String propType = ClassUtils.getCastableClassName(pd.getPropertyType());
			String setter = null;
			
			if (pd.getWriteMethod() != null) {
				setter = pd.getWriteMethod().getName();
			}

			src.append("    case ").append(++i).append(": ");
			if (setter == null) {
				src.append("throw noSetterMethodException(pn);");
			}
			else {
				src.append("bo.").append(setter).append("((")
					.append(propType).append(")value); return;");
			}

			if (Character.isUpperCase(propName.charAt(0))) {
				propName = propName.substring(0, 1).toLowerCase() + propName.substring(1);
				src.append("    case ").append(++i).append(": ");
				if (setter == null) {
					src.append("throw noSetterMethodException(pn);");
				}
				else {
					src.append("bo.").append(setter).append("((")
						.append(propType).append(")value); return;");
				}
			}
		}
		src.append("    default:\n");
		src.append("      throw noSuchPropertyException(pn);\n");
		src.append("    }\n");
		src.append("  }\n");

		src.append("}\n");

		Class clazz = dynamicClassLoader.loadClass(className, src.toString());
		BeanHandler bh = (BeanHandler)(ClassUtils.newInstance(clazz, this, BeanHandlerFactory.class));

		return bh;
	}
}
