/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * Copyright (c) 2009, Red Hat Inc. or third-party contributors as
 * indicated by the @author tags or express copyright attribution
 * statements applied by the authors.  All third-party contributions are
 * distributed under license by Red Hat Inc.
 *
 * This copyrighted material is made available to anyone wishing to use, modify,
 * copy, or redistribute it subject to the terms and conditions of the GNU
 * Lesser General Public License, as published by the Free Software Foundation.
 *
 * This program 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 Lesser General Public License
 * for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this distribution; if not, write to:
 * Free Software Foundation, Inc.
 * 51 Franklin Street, Fifth Floor
 * Boston, MA  02110-1301  USA
 */
package org.hibernate.ejb.criteria;

import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;

/**
 * Helper for generically dealing with literal values.
 *
 * @author Steve Ebersole
 */
public class ValueHandlerFactory {
	private ValueHandlerFactory() {
	}

	public static interface ValueHandler<T> {
		public T convert(Object value);
		public String render(T value);
	}

	public static abstract class BaseValueHandler<T> implements ValueHandler<T>, Serializable {
		public String render(T value) {
			return value.toString();
		}
	}

	public static class NoOpValueHandler<T> extends BaseValueHandler<T> {
		@SuppressWarnings({ "unchecked" })
		public T convert(Object value) {
			return (T) value;
		}
	}

	public static boolean isCharacter(Class type) {
		return String.class.isAssignableFrom( type )
				|| Character.class.isAssignableFrom( type )
				|| Character.TYPE.equals( type );
	}

	public static boolean isCharacter(Object value) {
		return String.class.isInstance( value )
				|| Character.class.isInstance( value )
				|| Character.TYPE.isInstance( value );
	}

	public static boolean isNumeric(Class type) {
		return Number.class.isAssignableFrom( type )
				|| Byte.TYPE.equals( type )
				|| Short.TYPE.equals( type )
				|| Integer.TYPE.equals( type )
				|| Long.TYPE.equals( type )
				|| Float.TYPE.equals( type )
				|| Double.TYPE.equals( type );
	}

	public static boolean isNumeric(Object value) {
		return Number.class.isInstance( value )
				|| Byte.TYPE.isInstance( value )
				|| Short.TYPE.isInstance( value )
				|| Integer.TYPE.isInstance( value )
				|| Long.TYPE.isInstance( value )
				|| Float.TYPE.isInstance( value )
				|| Double.TYPE.isInstance( value );
	}

	public static class ByteValueHandler extends BaseValueHandler<Byte> implements Serializable {
		public static final ByteValueHandler INSTANCE = new ByteValueHandler();
		@SuppressWarnings({ "UnnecessaryBoxing" })
		public Byte convert(Object value) {
			if ( value == null ) {
				return null;
			}
			if ( Number.class.isInstance( value ) ) {
				return Byte.valueOf( ( (Number) value ).byteValue() );
			}
			else if ( String.class.isInstance( value ) ) {
				return Byte.valueOf( ( (String) value ) );
			}
			throw unknownConversion( value, Byte.class );
		}
	}

	public static class ShortValueHandler extends BaseValueHandler<Short> implements Serializable {
		public static final ShortValueHandler INSTANCE = new ShortValueHandler();
		@SuppressWarnings({ "UnnecessaryBoxing" })
		public Short convert(Object value) {
			if ( value == null ) {
				return null;
			}
			if ( Number.class.isInstance( value ) ) {
				return Short.valueOf( ( (Number) value ).shortValue() );
			}
			else if ( String.class.isInstance( value ) ) {
				return Short.valueOf( ( (String) value ) );
			}
			throw unknownConversion( value, Short.class );
		}
	}

	public static class IntegerValueHandler extends BaseValueHandler<Integer> implements Serializable {
		public static final IntegerValueHandler INSTANCE = new IntegerValueHandler();
		@SuppressWarnings({ "UnnecessaryBoxing" })
		public Integer convert(Object value) {
			if ( value == null ) {
				return null;
			}
			if ( Number.class.isInstance( value ) ) {
				return Integer.valueOf( ( (Number) value ).intValue() );
			}
			else if ( String.class.isInstance( value ) ) {
				return Integer.valueOf( ( (String) value ) );
			}
			throw unknownConversion( value, Integer.class );
		}
	}

	public static class LongValueHandler extends BaseValueHandler<Long> implements Serializable {
		public static final LongValueHandler INSTANCE = new LongValueHandler();
		@SuppressWarnings({ "UnnecessaryBoxing" })
		public Long convert(Object value) {
			if ( value == null ) {
				return null;
			}
			if ( Number.class.isInstance( value ) ) {
				return Long.valueOf( ( (Number) value ).longValue() );
			}
			else if ( String.class.isInstance( value ) ) {
				return Long.valueOf( ( (String) value ) );
			}
			throw unknownConversion( value, Long.class );
		}

		@Override
		public String render(Long value) {
			return value.toString() + 'L';
		}
	}

	public static class FloatValueHandler extends BaseValueHandler<Float> implements Serializable {
		public static final FloatValueHandler INSTANCE = new FloatValueHandler();
		@SuppressWarnings({ "UnnecessaryBoxing" })
		public Float convert(Object value) {
			if ( value == null ) {
				return null;
			}
			if ( Number.class.isInstance( value ) ) {
				return Float.valueOf( ( (Number) value ).floatValue() );
			}
			else if ( String.class.isInstance( value ) ) {
				return Float.valueOf( ( (String) value ) );
			}
			throw unknownConversion( value, Float.class );
		}

		@Override
		public String render(Float value) {
			return value.toString() + 'F';
		}
	}

	public static class DoubleValueHandler extends BaseValueHandler<Double> implements Serializable {
		public static final DoubleValueHandler INSTANCE = new DoubleValueHandler();
		@SuppressWarnings({ "UnnecessaryBoxing" })
		public Double convert(Object value) {
			if ( value == null ) {
				return null;
			}
			if ( Number.class.isInstance( value ) ) {
				return Double.valueOf( ( (Number) value ).doubleValue() );
			}
			else if ( String.class.isInstance( value ) ) {
				return Double.valueOf( ( (String) value ) );
			}
			throw unknownConversion( value, Double.class );
		}

		@Override
		public String render(Double value) {
			return value.toString() + 'D';
		}
	}

	public static class BigIntegerValueHandler extends BaseValueHandler<BigInteger> implements Serializable {
		public static final BigIntegerValueHandler INSTANCE = new BigIntegerValueHandler();
		public BigInteger convert(Object value) {
			if ( value == null ) {
				return null;
			}
			if ( Number.class.isInstance( value ) ) {
				return BigInteger.valueOf( ( (Number) value ).longValue() );
			}
			else if ( String.class.isInstance( value ) ) {
				return new BigInteger( (String) value );
			}
			throw unknownConversion( value, BigInteger.class );
		}

		@Override
		public String render(BigInteger value) {
			return value.toString() + "BI";
		}
	}

	public static class BigDecimalValueHandler extends BaseValueHandler<BigDecimal> implements Serializable {
		public static final BigDecimalValueHandler INSTANCE = new BigDecimalValueHandler();
		public BigDecimal convert(Object value) {
			if ( value == null ) {
				return null;
			}
			if ( BigInteger.class.isInstance( value ) ) {
				return new BigDecimal( (BigInteger) value );
			}
			else if ( Number.class.isInstance( value ) ) {
				return BigDecimal.valueOf( ( (Number) value ).doubleValue() );
			}
			else if ( String.class.isInstance( value ) ) {
				return new BigDecimal( (String) value );
			}
			throw unknownConversion( value, BigDecimal.class );
		}

		@Override
		public String render(BigDecimal value) {
			return value.toString() + "BD";
		}
	}

	public static class StringValueHandler extends BaseValueHandler<String> implements Serializable {
		public static final StringValueHandler INSTANCE = new StringValueHandler();
		public String convert(Object value) {
			return value == null ? null : value.toString();
		}
	}

	private static IllegalArgumentException unknownConversion(Object value, Class type) {
		return new IllegalArgumentException(
				"Unaware how to convert value [" + value + " : " + typeName( value ) + "] to requested type [" + type.getName() + "]"
		);
	}

	private static String typeName(Object value) {
		return value == null ? "???" : value.getClass().getName();
	}

	/**
	 * Convert the given value into the specified target type.
	 *
	 * @param value The value to convert
	 * @param targetType The type to which it should be converted
	 *
	 * @return The converted value.
	 */
	@SuppressWarnings({ "unchecked" })
	public static <T> T convert(Object value, Class<T> targetType) {
		if ( value == null ) {
			return null;
		}
		if ( targetType.equals( value.getClass() ) ) {
			return (T) value;
		}

		ValueHandler<T> valueHandler = determineAppropriateHandler( targetType );
		if ( valueHandler == null ) {
			throw unknownConversion( value, targetType );
		}
		return valueHandler.convert( value );
	}

	/**
	 * Determine the appropriate {@link ValueHandlerFactory.ValueHandler} strategy for converting a value
	 * to the given target type
	 *
	 * @param targetType The target type (to which we want to convert values).
	 * @param <T> parameterized type for the target type.
	 * @return The conversion
	 */
	@SuppressWarnings({ "unchecked" })
	public static <T> ValueHandler<T> determineAppropriateHandler(Class<T> targetType) {
		if ( String.class.equals( targetType ) ) {
			return (ValueHandler<T>) StringValueHandler.INSTANCE;
		}
		if ( Byte.class.equals( targetType ) || Byte.TYPE.equals( targetType ) ) {
			return (ValueHandler<T>) ByteValueHandler.INSTANCE;
		}
		if ( Short.class.equals( targetType ) || Short.TYPE.equals( targetType ) ) {
			return (ValueHandler<T>) ShortValueHandler.INSTANCE;
		}
		if ( Integer.class.equals( targetType ) || Integer.TYPE.equals( targetType ) ) {
			return (ValueHandler<T>) IntegerValueHandler.INSTANCE;
		}
		if ( Long.class.equals( targetType ) || Long.TYPE.equals( targetType ) ) {
			return (ValueHandler<T>) LongValueHandler.INSTANCE;
		}
		if ( Float.class.equals( targetType ) || Float.TYPE.equals( targetType ) ) {
			return (ValueHandler<T>) FloatValueHandler.INSTANCE;
		}
		if ( Double.class.equals( targetType ) || Double.TYPE.equals( targetType ) ) {
			return (ValueHandler<T>) DoubleValueHandler.INSTANCE;
		}
		if ( BigInteger.class.equals( targetType ) ) {
			return (ValueHandler<T>) BigIntegerValueHandler.INSTANCE;
		}
		if ( BigDecimal.class.equals( targetType ) ) {
			return (ValueHandler<T>) BigDecimalValueHandler.INSTANCE;
		}
		return null;
	}
}
