/*
 * 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.lang.builder;

import static org.junit.Assert.assertEquals;

import org.junit.Test;

/**
 * Unit tests {@link HashCodeBuilder}.
 * 
 */
public class HashCodeBuilderTest {

	// -----------------------------------------------------------------------

	@Test(expected = IllegalArgumentException.class)
	public void testConstructorEx1() {
		new HashCodeBuilder(0, 0);
	}

	@Test(expected = IllegalArgumentException.class)
	public void testConstructorEx2() {
		new HashCodeBuilder(2, 2);
	}

	static class TestObject {
		private int a;

		public TestObject(final int a) {
			this.a = a;
		}

		@Override
		public boolean equals(final Object o) {
			if (o == this) {
				return true;
			}
			if (!(o instanceof TestObject)) {
				return false;
			}
			final TestObject rhs = (TestObject)o;
			return a == rhs.a;
		}

		@Override
		public int hashCode() {
			return a;
		}

		public void setA(final int a) {
			this.a = a;
		}

		public int getA() {
			return a;
		}
	}

	static class TestSubObject extends TestObject {
		private int b;

		@SuppressWarnings("unused")
		transient private int t;

		public TestSubObject() {
			super(0);
		}

		public TestSubObject(final int a, final int b, final int t) {
			super(a);
			this.b = b;
			this.t = t;
		}

		@Override
		public boolean equals(final Object o) {
			if (o == this) {
				return true;
			}
			if (!(o instanceof TestSubObject)) {
				return false;
			}
			final TestSubObject rhs = (TestSubObject)o;
			return super.equals(o) && b == rhs.b;
		}

		@Override
		public int hashCode() {
			return b * 17 + super.hashCode();
		}

	}

	@Test
	public void testSuper() {
		final Object obj = new Object();
		assertEquals(
			17 * 37 + 19 * 41 + obj.hashCode(),
			new HashCodeBuilder(17, 37).appendSuper(
				new HashCodeBuilder(19, 41).append(obj).toHashCode()).toHashCode());
	}

	@Test
	public void testObject() {
		Object obj = null;
		assertEquals(17 * 37, new HashCodeBuilder(17, 37).append(obj).toHashCode());
		obj = new Object();
		assertEquals(17 * 37 + obj.hashCode(), new HashCodeBuilder(17, 37).append(obj).toHashCode());
	}

	@Test
	public void testObjectBuild() {
		Object obj = null;
		assertEquals(17 * 37, new HashCodeBuilder(17, 37).append(obj).build().intValue());
		obj = new Object();
		assertEquals(17 * 37 + obj.hashCode(), new HashCodeBuilder(17, 37).append(obj).build()
			.intValue());
	}

	@Test
	@SuppressWarnings("cast")
	// cast is not really needed, keep for consistency
	public void testLong() {
		assertEquals(17 * 37, new HashCodeBuilder(17, 37).append((long)0L).toHashCode());
		assertEquals(17 * 37 + (int)(123456789L ^ 123456789L >> 32), new HashCodeBuilder(17, 37)
			.append((long)123456789L).toHashCode());
	}

	@Test
	@SuppressWarnings("cast")
	// cast is not really needed, keep for consistency
	public void testInt() {
		assertEquals(17 * 37, new HashCodeBuilder(17, 37).append((int)0).toHashCode());
		assertEquals(17 * 37 + 123456, new HashCodeBuilder(17, 37).append((int)123456).toHashCode());
	}

	@Test
	public void testShort() {
		assertEquals(17 * 37, new HashCodeBuilder(17, 37).append((short)0).toHashCode());
		assertEquals(17 * 37 + 12345, new HashCodeBuilder(17, 37).append((short)12345).toHashCode());
	}

	@Test
	public void testChar() {
		assertEquals(17 * 37, new HashCodeBuilder(17, 37).append((char)0).toHashCode());
		assertEquals(17 * 37 + 1234, new HashCodeBuilder(17, 37).append((char)1234).toHashCode());
	}

	@Test
	public void testByte() {
		assertEquals(17 * 37, new HashCodeBuilder(17, 37).append((byte)0).toHashCode());
		assertEquals(17 * 37 + 123, new HashCodeBuilder(17, 37).append((byte)123).toHashCode());
	}

	@Test
	@SuppressWarnings("cast")
	// cast is not really needed, keep for consistency
	public void testDouble() {
		assertEquals(17 * 37, new HashCodeBuilder(17, 37).append((double)0d).toHashCode());
		final double d = 1234567.89;
		final long l = Double.doubleToLongBits(d);
		assertEquals(17 * 37 + (int)(l ^ l >> 32), new HashCodeBuilder(17, 37).append(d)
			.toHashCode());
	}

	@Test
	@SuppressWarnings("cast")
	// cast is not really needed, keep for consistency
	public void testFloat() {
		assertEquals(17 * 37, new HashCodeBuilder(17, 37).append((float)0f).toHashCode());
		final float f = 1234.89f;
		final int i = Float.floatToIntBits(f);
		assertEquals(17 * 37 + i, new HashCodeBuilder(17, 37).append(f).toHashCode());
	}

	@Test
	public void testBoolean() {
		assertEquals(17 * 37 + 0, new HashCodeBuilder(17, 37).append(true).toHashCode());
		assertEquals(17 * 37 + 1, new HashCodeBuilder(17, 37).append(false).toHashCode());
	}

	@Test
	public void testObjectArray() {
		assertEquals(17 * 37, new HashCodeBuilder(17, 37).append((Object[])null).toHashCode());
		final Object[] obj = new Object[2];
		assertEquals(17 * 37 * 37, new HashCodeBuilder(17, 37).append(obj).toHashCode());
		obj[0] = new Object();
		assertEquals((17 * 37 + obj[0].hashCode()) * 37, new HashCodeBuilder(17, 37).append(obj)
			.toHashCode());
		obj[1] = new Object();
		assertEquals((17 * 37 + obj[0].hashCode()) * 37 + obj[1].hashCode(), new HashCodeBuilder(
			17, 37).append(obj).toHashCode());
	}

	@Test
	public void testObjectArrayAsObject() {
		final Object[] obj = new Object[2];
		assertEquals(17 * 37 * 37, new HashCodeBuilder(17, 37).append((Object)obj).toHashCode());
		obj[0] = new Object();
		assertEquals((17 * 37 + obj[0].hashCode()) * 37,
			new HashCodeBuilder(17, 37).append((Object)obj).toHashCode());
		obj[1] = new Object();
		assertEquals((17 * 37 + obj[0].hashCode()) * 37 + obj[1].hashCode(), new HashCodeBuilder(
			17, 37).append((Object)obj).toHashCode());
	}

	@Test
	public void testLongArray() {
		assertEquals(17 * 37, new HashCodeBuilder(17, 37).append((long[])null).toHashCode());
		final long[] obj = new long[2];
		assertEquals(17 * 37 * 37, new HashCodeBuilder(17, 37).append(obj).toHashCode());
		obj[0] = 5L;
		final int h1 = (int)(5L ^ 5L >> 32);
		assertEquals((17 * 37 + h1) * 37, new HashCodeBuilder(17, 37).append(obj).toHashCode());
		obj[1] = 6L;
		final int h2 = (int)(6L ^ 6L >> 32);
		assertEquals((17 * 37 + h1) * 37 + h2, new HashCodeBuilder(17, 37).append(obj).toHashCode());
	}

	@Test
	public void testLongArrayAsObject() {
		final long[] obj = new long[2];
		assertEquals(17 * 37 * 37, new HashCodeBuilder(17, 37).append((Object)obj).toHashCode());
		obj[0] = 5L;
		final int h1 = (int)(5L ^ 5L >> 32);
		assertEquals((17 * 37 + h1) * 37, new HashCodeBuilder(17, 37).append((Object)obj)
			.toHashCode());
		obj[1] = 6L;
		final int h2 = (int)(6L ^ 6L >> 32);
		assertEquals((17 * 37 + h1) * 37 + h2, new HashCodeBuilder(17, 37).append((Object)obj)
			.toHashCode());
	}

	@Test
	public void testIntArray() {
		assertEquals(17 * 37, new HashCodeBuilder(17, 37).append((int[])null).toHashCode());
		final int[] obj = new int[2];
		assertEquals(17 * 37 * 37, new HashCodeBuilder(17, 37).append(obj).toHashCode());
		obj[0] = 5;
		assertEquals((17 * 37 + 5) * 37, new HashCodeBuilder(17, 37).append(obj).toHashCode());
		obj[1] = 6;
		assertEquals((17 * 37 + 5) * 37 + 6, new HashCodeBuilder(17, 37).append(obj).toHashCode());
	}

	@Test
	public void testIntArrayAsObject() {
		final int[] obj = new int[2];
		assertEquals(17 * 37 * 37, new HashCodeBuilder(17, 37).append((Object)obj).toHashCode());
		obj[0] = 5;
		assertEquals((17 * 37 + 5) * 37, new HashCodeBuilder(17, 37).append((Object)obj)
			.toHashCode());
		obj[1] = 6;
		assertEquals((17 * 37 + 5) * 37 + 6, new HashCodeBuilder(17, 37).append((Object)obj)
			.toHashCode());
	}

	@Test
	public void testShortArray() {
		assertEquals(17 * 37, new HashCodeBuilder(17, 37).append((short[])null).toHashCode());
		final short[] obj = new short[2];
		assertEquals(17 * 37 * 37, new HashCodeBuilder(17, 37).append(obj).toHashCode());
		obj[0] = (short)5;
		assertEquals((17 * 37 + 5) * 37, new HashCodeBuilder(17, 37).append(obj).toHashCode());
		obj[1] = (short)6;
		assertEquals((17 * 37 + 5) * 37 + 6, new HashCodeBuilder(17, 37).append(obj).toHashCode());
	}

	@Test
	public void testShortArrayAsObject() {
		final short[] obj = new short[2];
		assertEquals(17 * 37 * 37, new HashCodeBuilder(17, 37).append((Object)obj).toHashCode());
		obj[0] = (short)5;
		assertEquals((17 * 37 + 5) * 37, new HashCodeBuilder(17, 37).append((Object)obj)
			.toHashCode());
		obj[1] = (short)6;
		assertEquals((17 * 37 + 5) * 37 + 6, new HashCodeBuilder(17, 37).append((Object)obj)
			.toHashCode());
	}

	@Test
	public void testCharArray() {
		assertEquals(17 * 37, new HashCodeBuilder(17, 37).append((char[])null).toHashCode());
		final char[] obj = new char[2];
		assertEquals(17 * 37 * 37, new HashCodeBuilder(17, 37).append(obj).toHashCode());
		obj[0] = (char)5;
		assertEquals((17 * 37 + 5) * 37, new HashCodeBuilder(17, 37).append(obj).toHashCode());
		obj[1] = (char)6;
		assertEquals((17 * 37 + 5) * 37 + 6, new HashCodeBuilder(17, 37).append(obj).toHashCode());
	}

	@Test
	public void testCharArrayAsObject() {
		final char[] obj = new char[2];
		assertEquals(17 * 37 * 37, new HashCodeBuilder(17, 37).append((Object)obj).toHashCode());
		obj[0] = (char)5;
		assertEquals((17 * 37 + 5) * 37, new HashCodeBuilder(17, 37).append((Object)obj)
			.toHashCode());
		obj[1] = (char)6;
		assertEquals((17 * 37 + 5) * 37 + 6, new HashCodeBuilder(17, 37).append((Object)obj)
			.toHashCode());
	}

	@Test
	public void testByteArray() {
		assertEquals(17 * 37, new HashCodeBuilder(17, 37).append((byte[])null).toHashCode());
		final byte[] obj = new byte[2];
		assertEquals(17 * 37 * 37, new HashCodeBuilder(17, 37).append(obj).toHashCode());
		obj[0] = (byte)5;
		assertEquals((17 * 37 + 5) * 37, new HashCodeBuilder(17, 37).append(obj).toHashCode());
		obj[1] = (byte)6;
		assertEquals((17 * 37 + 5) * 37 + 6, new HashCodeBuilder(17, 37).append(obj).toHashCode());
	}

	@Test
	public void testByteArrayAsObject() {
		final byte[] obj = new byte[2];
		assertEquals(17 * 37 * 37, new HashCodeBuilder(17, 37).append((Object)obj).toHashCode());
		obj[0] = (byte)5;
		assertEquals((17 * 37 + 5) * 37, new HashCodeBuilder(17, 37).append((Object)obj)
			.toHashCode());
		obj[1] = (byte)6;
		assertEquals((17 * 37 + 5) * 37 + 6, new HashCodeBuilder(17, 37).append((Object)obj)
			.toHashCode());
	}

	@Test
	public void testDoubleArray() {
		assertEquals(17 * 37, new HashCodeBuilder(17, 37).append((double[])null).toHashCode());
		final double[] obj = new double[2];
		assertEquals(17 * 37 * 37, new HashCodeBuilder(17, 37).append(obj).toHashCode());
		obj[0] = 5.4d;
		final long l1 = Double.doubleToLongBits(5.4d);
		final int h1 = (int)(l1 ^ l1 >> 32);
		assertEquals((17 * 37 + h1) * 37, new HashCodeBuilder(17, 37).append(obj).toHashCode());
		obj[1] = 6.3d;
		final long l2 = Double.doubleToLongBits(6.3d);
		final int h2 = (int)(l2 ^ l2 >> 32);
		assertEquals((17 * 37 + h1) * 37 + h2, new HashCodeBuilder(17, 37).append(obj).toHashCode());
	}

	@Test
	public void testDoubleArrayAsObject() {
		final double[] obj = new double[2];
		assertEquals(17 * 37 * 37, new HashCodeBuilder(17, 37).append((Object)obj).toHashCode());
		obj[0] = 5.4d;
		final long l1 = Double.doubleToLongBits(5.4d);
		final int h1 = (int)(l1 ^ l1 >> 32);
		assertEquals((17 * 37 + h1) * 37, new HashCodeBuilder(17, 37).append((Object)obj)
			.toHashCode());
		obj[1] = 6.3d;
		final long l2 = Double.doubleToLongBits(6.3d);
		final int h2 = (int)(l2 ^ l2 >> 32);
		assertEquals((17 * 37 + h1) * 37 + h2, new HashCodeBuilder(17, 37).append((Object)obj)
			.toHashCode());
	}

	@Test
	public void testFloatArray() {
		assertEquals(17 * 37, new HashCodeBuilder(17, 37).append((float[])null).toHashCode());
		final float[] obj = new float[2];
		assertEquals(17 * 37 * 37, new HashCodeBuilder(17, 37).append(obj).toHashCode());
		obj[0] = 5.4f;
		final int h1 = Float.floatToIntBits(5.4f);
		assertEquals((17 * 37 + h1) * 37, new HashCodeBuilder(17, 37).append(obj).toHashCode());
		obj[1] = 6.3f;
		final int h2 = Float.floatToIntBits(6.3f);
		assertEquals((17 * 37 + h1) * 37 + h2, new HashCodeBuilder(17, 37).append(obj).toHashCode());
	}

	@Test
	public void testFloatArrayAsObject() {
		final float[] obj = new float[2];
		assertEquals(17 * 37 * 37, new HashCodeBuilder(17, 37).append((Object)obj).toHashCode());
		obj[0] = 5.4f;
		final int h1 = Float.floatToIntBits(5.4f);
		assertEquals((17 * 37 + h1) * 37, new HashCodeBuilder(17, 37).append((Object)obj)
			.toHashCode());
		obj[1] = 6.3f;
		final int h2 = Float.floatToIntBits(6.3f);
		assertEquals((17 * 37 + h1) * 37 + h2, new HashCodeBuilder(17, 37).append((Object)obj)
			.toHashCode());
	}

	@Test
	public void testBooleanArray() {
		assertEquals(17 * 37, new HashCodeBuilder(17, 37).append((boolean[])null).toHashCode());
		final boolean[] obj = new boolean[2];
		assertEquals((17 * 37 + 1) * 37 + 1, new HashCodeBuilder(17, 37).append(obj).toHashCode());
		obj[0] = true;
		assertEquals((17 * 37 + 0) * 37 + 1, new HashCodeBuilder(17, 37).append(obj).toHashCode());
		obj[1] = false;
		assertEquals((17 * 37 + 0) * 37 + 1, new HashCodeBuilder(17, 37).append(obj).toHashCode());
	}

	@Test
	public void testBooleanArrayAsObject() {
		final boolean[] obj = new boolean[2];
		assertEquals((17 * 37 + 1) * 37 + 1, new HashCodeBuilder(17, 37).append((Object)obj)
			.toHashCode());
		obj[0] = true;
		assertEquals((17 * 37 + 0) * 37 + 1, new HashCodeBuilder(17, 37).append((Object)obj)
			.toHashCode());
		obj[1] = false;
		assertEquals((17 * 37 + 0) * 37 + 1, new HashCodeBuilder(17, 37).append((Object)obj)
			.toHashCode());
	}

	@Test
	public void testBooleanMultiArray() {
		final boolean[][] obj = new boolean[2][];
		assertEquals(17 * 37 * 37, new HashCodeBuilder(17, 37).append(obj).toHashCode());
		obj[0] = new boolean[0];
		assertEquals(17 * 37, new HashCodeBuilder(17, 37).append(obj).toHashCode());
		obj[0] = new boolean[1];
		assertEquals((17 * 37 + 1) * 37, new HashCodeBuilder(17, 37).append(obj).toHashCode());
		obj[0] = new boolean[2];
		assertEquals(((17 * 37 + 1) * 37 + 1) * 37, new HashCodeBuilder(17, 37).append(obj)
			.toHashCode());
		obj[0][0] = true;
		assertEquals(((17 * 37 + 0) * 37 + 1) * 37, new HashCodeBuilder(17, 37).append(obj)
			.toHashCode());
		obj[1] = new boolean[1];
		assertEquals(((17 * 37 + 0) * 37 + 1) * 37 + 1, new HashCodeBuilder(17, 37).append(obj)
			.toHashCode());
	}

	static class TestObjectWithMultipleFields {
		@SuppressWarnings("unused")
		private int one = 0;

		@SuppressWarnings("unused")
		private int two = 0;

		@SuppressWarnings("unused")
		private int three = 0;

		public TestObjectWithMultipleFields(final int one, final int two, final int three) {
			this.one = one;
			this.two = two;
			this.three = three;
		}
	}

	/**
     */
	@Test
	public void testToHashCodeEqualsHashCode() {
		final HashCodeBuilder hcb = new HashCodeBuilder(17, 37).append(new Object()).append('a');
		assertEquals(
			"hashCode() is no longer returning the same value as toHashCode() - see LANG-520",
			hcb.toHashCode(), hcb.hashCode());
	}

}
