/*
 * 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 nuts.core.lang.ClassUtils;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * BeanHandlerFactory
 */
public class BeanHandlerFactory {
	/**
	 * handler map
	 */
	protected Map<Class, BeanHandler> handlers = new ConcurrentHashMap<Class, BeanHandler>();

	/**
	 * BeanHandlerFactory instance
	 */
	private static BeanHandlerFactory instance;

	static {
		try {
			instance = (BeanHandlerFactory)ClassUtils.newInstance(
					BeanHandlerFactory.class.getPackage().getName().toString()
					+ ".FastBeanHandlerFactory");
		}
		catch (Throwable e) {
			instance = new BeanHandlerFactory();
		}
	}

	/**
	 * @return instance
	 */
	public static BeanHandlerFactory getInstance() {
		return instance;
	}

	/**
	 * @param instance the instance to set
	 */
	public static void setInstance(BeanHandlerFactory instance) {
		BeanHandlerFactory.instance = instance;
	}

	/**
	 * prepareBeanHandler
	 * 
	 * @param type class type
	 */
	@SuppressWarnings("unchecked")
	public void prepareBeanHandler(Class type) {
		getBeanHandler(type);
	}
	
	/**
	 * prepareBeanHandler
	 * 
	 * @param types class type array
	 */
	public void prepareBeanHandler(Class[] types) {
		for (Class type : types) {
			prepareBeanHandler(type);
		}
	}

	/**
	 * prepareBeanHandler
	 * 
	 * @param types class type collection
	 */
	public void prepareBeanHandler(Collection<Class> types) {
		for (Class type : types) {
			prepareBeanHandler(type);
		}
	}

	/**
	 * Register (add) a bean handler for a class
	 * 
	 * @param type - the class
	 * @param handler - the handler instance
	 */
	public void register(Class type, BeanHandler handler) {
		handlers.put(type, handler);
	}
	
	/**
	 * Unregister (remove) a bean handler for a class
	 * 
	 * @param type - the class
	 */
	public void unregister(Class type) {
		handlers.remove(type);
	}
	
	/**
	 * clear bean handlers
	 */
	public void clear() {
		handlers.clear();
	}
	
	/**
	 * getBeanHandler
	 * @param type bean type
	 * @return BeanHandler
	 */
	@SuppressWarnings("unchecked")
	public <T> BeanHandler<T> getBeanHandler(Class<T> type) {
		BeanHandler<T> handler = handlers.get(type);
		if (handler == null) {
			handler = createBeanHandler(type);
			register(type, handler);
		}
		return handler;
	}

	/**
	 * create bean handler
	 * @param type bean type
	 * @return BeanHandler
	 */
	@SuppressWarnings("unchecked")
	protected BeanHandler createBeanHandler(Class type) {
		BeanHandler handler;
		if (type.isArray()) {
			handler = new ArrayBeanHandler(this, type);
		}
		else if (Map.class.isAssignableFrom(type)) {
			handler = new MapBeanHandler(this, type);
		}
		else if (List.class.isAssignableFrom(type)) {
			handler = new ListBeanHandler(this, type);
		}
		else if (isPrimitiveJavaType(type)) {
			handler = new ValueBeanHandler(type); 
		}
		else {
			handler = createJavaBeanHandler(type);
		}
		
		return handler;
	}
	
	/**
	 * create java bean handler
	 * @param type bean type
	 * @return BeanHandler
	 */
	@SuppressWarnings("unchecked")
	protected BeanHandler createJavaBeanHandler(Class type) {
		return new JavaBeanHandler(this, type);
	}

	/**
	 * is primitive java type
	 * @param type class type
	 * @return true if the type is a simple java type
	 */
	protected boolean isPrimitiveJavaType(Class type) {
		return ClassUtils.isPrimitiveOrWrapper(type);
	}
}
