/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * Copyright (c) 2010, 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.type;

import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.Calendar;
import java.util.Currency;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.SimpleTimeZone;
import java.util.TimeZone;

import junit.framework.TestCase;

import org.hibernate.EntityMode;
import org.hibernate.Session;
import org.hibernate.engine.SessionImplementor;
import org.hibernate.util.ArrayHelper;
import org.hibernate.util.SerializationHelper;

/**
 * TODO : javadoc
 *
 * @author Steve Ebersole
 */
public class TypeTest extends TestCase {
	public void testBigDecimalType() {
		final BigDecimal original = BigDecimal.valueOf( 100 );
		final BigDecimal copy = BigDecimal.valueOf( 100 );
		final BigDecimal different = BigDecimal.valueOf( 999 );

		runBasicTests( BigDecimalType.INSTANCE, original, copy, different );
	}

	public void testBigIntegerType() {
		final BigInteger original = BigInteger.valueOf( 100 );
		final BigInteger copy = BigInteger.valueOf( 100 );
		final BigInteger different = BigInteger.valueOf( 999 );

		runBasicTests( BigIntegerType.INSTANCE, original, copy, different );
	}

	public void testBinaryType() {
		final byte[] original = new byte[] { 1, 2, 3, 4 };
		final byte[] copy = new byte[] { 1, 2, 3, 4 };
		final byte[] different = new byte[] { 4, 3, 2, 1 };

		runBasicTests( BinaryType.INSTANCE, original, copy, different );
		runBasicTests( ImageType.INSTANCE, original, copy, different );
		runBasicTests( MaterializedBlobType.INSTANCE, original, copy, different );
	}

	public void testBooleanType() {
		final Boolean original = Boolean.TRUE;
		final Boolean copy = new Boolean( true );
		final Boolean different = Boolean.FALSE;

		runBasicTests( BooleanType.INSTANCE, original, copy, different );
		runBasicTests( NumericBooleanType.INSTANCE, original, copy, different );
		runBasicTests( YesNoType.INSTANCE, original, copy, different );
		runBasicTests( TrueFalseType.INSTANCE, original, copy, different );
	}

	public void testByteType() {
		final Byte original = 0;
		final Byte copy = new Byte( (byte) 0 );
		final Byte different = 9;

		runBasicTests( ByteType.INSTANCE, original, copy, different );
	}

	public void testCalendarDateType() {
		final Calendar original = new GregorianCalendar();
		final Calendar copy = new GregorianCalendar();
		final Calendar different = new GregorianCalendar();
		different.set( Calendar.MONTH, 9 );
		different.set( Calendar.DAY_OF_MONTH, 9 );
		different.set( Calendar.YEAR, 2999 );

		runBasicTests( CalendarDateType.INSTANCE, original, copy, different );
	}

	public void testCalendarType() {
		final long now = System.currentTimeMillis();
		final Calendar original = new GregorianCalendar();
		original.clear();
		original.setTimeInMillis( now );
		final Calendar copy = new GregorianCalendar();
		copy.clear();
		copy.setTimeInMillis( now );
		final Calendar different = new GregorianCalendar();
		different.setTimeInMillis( now + 9999 );

		runBasicTests( CalendarType.INSTANCE, original, copy, different );
	}

	public void testCharacterArrayType() {
		final Character[] original = new Character[] { 'a', 'b' };
		final Character[] copy = new Character[] { 'a', 'b' };
		final Character[] different = new Character[] { 'a', 'b', 'c' };

		runBasicTests( CharacterArrayType.INSTANCE, original, copy, different );
	}

	public void testCharacterType() {
		final Character original = 'a';
		final Character copy = new Character( 'a' );
		final Character different = 'b';

		runBasicTests( CharacterType.INSTANCE, original, copy, different );
	}

	public void testCharArrayType() {
		final char[] original = new char[] { 'a', 'b' };
		final char[] copy = new char[] { 'a', 'b' };
		final char[] different = new char[] { 'a', 'b', 'c' };

		runBasicTests( CharArrayType.INSTANCE, original, copy, different );
		runBasicTests( CharArrayType.INSTANCE, original, copy, different );
	}

	public void testClassType() {
		final Class original = TypeTest.class;
		final Class copy = (Class) SerializationHelper.clone( original );
		final Class different = TestCase.class;

		runBasicTests( ClassType.INSTANCE, original, copy, different );
	}

	public void testCurrencyType() {
		final Currency original = Currency.getInstance( Locale.US );
		final Currency copy = Currency.getInstance( Locale.US );
		final Currency different = Currency.getInstance( Locale.UK );

		runBasicTests( CurrencyType.INSTANCE, original, copy, different );
	}

	public void testDateType() {
		final long now = System.currentTimeMillis();
		final java.sql.Date original = new java.sql.Date( now );
		final java.sql.Date copy = new java.sql.Date( now );
		Calendar cal = new GregorianCalendar();
		cal.clear();
		cal.setTimeInMillis( now );
		cal.add( Calendar.YEAR, 1 );
		final java.sql.Date different = new java.sql.Date( cal.getTime().getTime() );

		runBasicTests( DateType.INSTANCE, original, copy, different );
	}

	public void testDoubleType() {
		final Double original = Double.valueOf( 100 );
		final Double copy = Double.valueOf( 100 );
		final Double different = Double.valueOf( 999 );

		runBasicTests( DoubleType.INSTANCE, original, copy, different );
	}

	public void testFloatType() {
		final Float original = Float.valueOf( 100 );
		final Float copy = Float.valueOf( 100 );
		final Float different = Float.valueOf( 999 );

		runBasicTests( FloatType.INSTANCE, original, copy, different );
	}

	public void testIntegerType() {
		final Integer original = 100;
		final Integer copy = new Integer( 100 );
		final Integer different = 999;

		runBasicTests( IntegerType.INSTANCE, original, copy, different );
	}

	public void testLocaleType() {
		final Locale original = new Locale( "ab" );
		final Locale copy = new Locale( "ab" );
		final Locale different = new Locale( "yz" );

		runBasicTests( LocaleType.INSTANCE, original, copy, different );
	}

	public void testLongType() {
		final Long original = 100L;
		final Long copy = new Long( 100L );
		final Long different = 999L;

		runBasicTests( LongType.INSTANCE, original, copy, different );
	}

	private static class SerializableImpl implements Serializable {
		private final int number;
		SerializableImpl(int number) {
			this.number = number;
		}
		public boolean equals(Object obj) {
			return this.number == ( (SerializableImpl) obj ).number;
		}
	}
	public void testSerializableType() {
		final SerializableImpl original = new SerializableImpl(1);
		final SerializableImpl copy = new SerializableImpl(1);
		final SerializableImpl different = new SerializableImpl(2);

		runBasicTests( SerializableType.INSTANCE, original, copy, different );
		runBasicTests( new SerializableType<SerializableImpl>( SerializableImpl.class ), original, copy, different );
	}

	public void testShortType() {
		final Short original = 100;
		final Short copy = new Short( (short) 100 );
		final Short different = 999;

		runBasicTests( ShortType.INSTANCE, original, copy, different );
	}

	public void testStringType() {
		final String original = "abc";
		final String copy = new String( original.toCharArray() );
		final String different = "xyz";

		runBasicTests( StringType.INSTANCE, original, copy, different );
		runBasicTests( TextType.INSTANCE, original, copy, different );
		runBasicTests( MaterializedClobType.INSTANCE, original, copy, different );
	}

	public void testTimestampType() {
		final long now = System.currentTimeMillis();
		final Timestamp original = new Timestamp( now );
		final Timestamp copy = new Timestamp( now );
		final Timestamp different = new Timestamp( now + 9999 );

		runBasicTests( TimestampType.INSTANCE, original, copy, different );
	}

	public void testTimeType() {
		final long now = System.currentTimeMillis();
		final Time original = new Time( now );
		final Time copy = new Time( now );
		final Time different = new Time( now + 9999 );

		runBasicTests( TimeType.INSTANCE, original, copy, different );
	}

	public void testDates() {
		final long now = System.currentTimeMillis();
		final java.util.Date original = new java.util.Date( now );
		final java.util.Date copy = new java.util.Date( now );
		final java.util.Date different = new java.util.Date( now + 9999 );
		final java.util.Date different2 = new java.util.Date( now + ( 1000L * 60L * 60L * 24L * 365L ) );

		runBasicTests( TimeType.INSTANCE, original, copy, different );
		runBasicTests( TimestampType.INSTANCE, original, copy, different );
		runBasicTests( DateType.INSTANCE, original, copy, different2 );
	}

	public void testTimeZoneType() {
		final TimeZone original = new SimpleTimeZone( -1, "abc" );
		final TimeZone copy = new SimpleTimeZone( -1, "abc" );
		final TimeZone different = new SimpleTimeZone( -2, "xyz" );

		runBasicTests( TimeZoneType.INSTANCE, original, copy, different );
	}



	protected <T> void runBasicTests(AbstractSingleColumnStandardBasicType<T> type, T original, T copy, T different) {
		final boolean nonCopyable = Class.class.isInstance( original )
				|| Currency.class.isInstance( original );
		if ( ! nonCopyable ) {
			// these checks exclude classes which cannot really be cloned (singetons/enums)
			assertFalse( original == copy );
		}

		assertTrue( original == type.replace( original, copy, null, null, null ) );

		assertTrue( type.isSame( original, copy, EntityMode.POJO ) );
		assertTrue( type.isEqual( original, copy ) );
		assertTrue( type.isEqual( original, copy, EntityMode.POJO ) );
		assertTrue( type.isEqual( original, copy, EntityMode.POJO, null ) );

		assertFalse( type.isSame( original, different, EntityMode.POJO ) );
		assertFalse( type.isEqual( original, different ) );
		assertFalse( type.isEqual( original, different, EntityMode.POJO ) );
		assertFalse( type.isEqual( original, different, EntityMode.POJO, null ) );

		assertFalse( type.isDirty( original, copy , session ) );
		assertFalse( type.isDirty( original, copy , ArrayHelper.FALSE, session ) );
		assertFalse( type.isDirty( original, copy , ArrayHelper.TRUE, session ) );

		assertTrue( type.isDirty( original, different , session ) );
		assertFalse( type.isDirty( original, different , ArrayHelper.FALSE, session ) );
		assertTrue( type.isDirty( original, different , ArrayHelper.TRUE, session ) );

		assertFalse( type.isModified( original, copy, ArrayHelper.FALSE, session ) );
		assertFalse( type.isModified( original, copy, ArrayHelper.TRUE, session ) );

		assertTrue( type.isModified( original, different, ArrayHelper.FALSE, session ) );
		assertTrue( type.isModified( original, different, ArrayHelper.TRUE, session ) );
	}

	private SessionImplementor session;

	@Override
	protected void setUp() throws Exception {
		session = (SessionImplementor) Proxy.newProxyInstance(
				getClass().getClassLoader(),
				new Class[] { Session.class, SessionImplementor.class },
				new SessionProxyHandler()
		);
	}

	public static class SessionProxyHandler implements InvocationHandler {
		public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
			if ( "getEntityMode".equals( method.getName() ) ) {
				return EntityMode.POJO;
			}
			throw new UnsupportedOperationException( "Unexpected method call : " + method.getName() );
		}
	}
}
