/*
 * 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.util.converter;

import nuts.core.lang.ClassUtils;

import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;


public class ConverterFactory {
	protected Map<Class, Converter> converters = new ConcurrentHashMap<Class, Converter>();

	private static ConverterFactory instance = new ConverterFactory();

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

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

	/**
	 * Constructor
	 */
	public ConverterFactory() {
		register(Boolean.TYPE, new PrimitiveTypeConverter.BooleanConverter());
		register(Byte.TYPE, new PrimitiveTypeConverter.ByteConverter());
		register(Character.TYPE, new PrimitiveTypeConverter.CharacterConverter());
		register(Double.TYPE, new PrimitiveTypeConverter.DoubleConverter());
		register(Float.TYPE, new PrimitiveTypeConverter.FloatConverter());
		register(Integer.TYPE, new PrimitiveTypeConverter.IntegerConverter());
		register(Long.TYPE, new PrimitiveTypeConverter.LongConverter());
		register(Short.TYPE, new PrimitiveTypeConverter.ShortConverter());

		register(new PrimitiveWrapConverter.BooleanConverter());
		register(new PrimitiveWrapConverter.ByteConverter());
		register(new PrimitiveWrapConverter.CharacterConverter());
		register(new PrimitiveWrapConverter.DoubleConverter());
		register(new PrimitiveWrapConverter.FloatConverter());
		register(new PrimitiveWrapConverter.IntegerConverter());
		register(new PrimitiveWrapConverter.LongConverter());
		register(new PrimitiveWrapConverter.ShortConverter());

		register(new BigIntegerConverter());
		register(new BigDecimalConverter());

		DateConverter dc = new DateConverter();
		register(dc);
		register(new CalendarConverter(dc));
		
		register(new StringConverter());
	}
	
	/**
	 * prepareConverter
	 * 
	 * @param type class type
	 */
	@SuppressWarnings("unchecked")
	public void prepareConverter(Class type) {
		getConverter(type);
	}
	
	/**
	 * prepareConverter
	 * 
	 * @param types class type array
	 */
	public void prepareConverter(Class[] types) {
		for (Class type : types) {
			prepareConverter(type);
		}
	}

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

	/**
	 * Register (add) a converter for a class
	 * 
	 * @param type - the class
	 * @param handler - the handler instance
	 */
	public void register(AbstractConverter handler) {
		converters.put(handler.getType(), handler);
	}

	/**
	 * Register (add) a converter for a class
	 * 
	 * @param type - the class
	 * @param handler - the handler instance
	 */
	public void register(Class type, Converter handler) {
		converters.put(type, handler);
	}
	
	/**
	 * Unregister (remove) a converter for a class
	 * 
	 * @param type - the class
	 */
	public void unregister(Class type) {
		converters.remove(type);
	}
	
	/**
	 * clear converters
	 */
	public void clear() {
		converters.clear();
	}
	
	/**
	 * getConverter
	 * @param type object type
	 * @return Converter
	 */
	public <T> Converter<T> getConverter(Class<T> type) {
		return getConverter(type, null);
	}
	
	/**
	 * getConverter
	 * @param type object type
	 * @param elementType element type
	 * @return Converter
	 */
	public <T> Converter<T> getConverter(Class<T> type, Class<?> elementType) {
		return getConverter(type, null);
	}
	
	/**
	 * getConverter
	 * @param type object type
	 * @param keyType key element type
	 * @param valType value element type
	 * @return Converter
	 */
	@SuppressWarnings("unchecked")
	public <T> Converter<T> getConverter(Class<T> type, Class<?> keyType, Class<?> valType) {
		if (type == null) {
			return new UnknownTypeConverter(Object.class);
		}

		Converter<T> handler = converters.get(type);
		if (handler == null) {
			if (type.isArray()) {
				handler = new ArrayConverter(type, this);
			}
			else if (Modifier.isAbstract(type.getModifiers())) {
				if (List.class.isAssignableFrom(type)) {
					handler = new CollectionConverter(ArrayList.class, keyType, this);
				}
				else if (Set.class.isAssignableFrom(type)) {
					handler = new CollectionConverter(HashSet.class, keyType, this); 
				}
				else if (Map.class.isAssignableFrom(type)) {
					handler = new MapConverter(HashMap.class, keyType, valType, this);
				}
				else {
					handler = new UnknownTypeConverter(type);
				}
			}
			else if (Map.class.isAssignableFrom(type)) {
				handler = new MapConverter(type, keyType, valType, this);
			}
			else if (Collection.class.isAssignableFrom(type)) {
				handler = new CollectionConverter(type, keyType, this);
			}
			else {
				handler = new UnknownTypeConverter(type);
			}
		}
		return handler;
	}

	/**
	 * 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);
	}
}
