/*
 * Copyright 2006 Takahiro Nakamura.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package woolpack.typeconvert;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
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 woolpack.test.TestBean;
import woolpack.test.TestUtils;

import junit.framework.TestCase;

public class TypeConvertUtilsTest extends TestCase {
	private String dateString;
	private java.util.Date date;
	private long timestamp;
	private Calendar calendar;
	
	@Override
	protected void setUp() throws ParseException {
		dateString = "20060708";
		date = new SimpleDateFormat("yyyyMMdd").parse(dateString);
		timestamp = date.getTime();
		calendar = Calendar.getInstance();
		calendar.setTimeInMillis(timestamp);
	}
	
	private static void scenario(final Object expected, final Object before, final Class toType, final String name) {
		final ConvertContext context = new ConvertContext();
		context.setToType(toType);
		context.setPropertyName(name);
		context.setValue(before);
		TypeConvertUtils.COLLECTION_CONVERTER.exec(context);
		assertTrue(TestUtils.equals(expected, context.getValue()));
	}

	private static void scenario(final Object expected, final Object before, final Class toType) {
		scenario(expected, before, toType, null);
	}
	
	public void testDate(){
		{
			final Class<?> toType = java.util.Date.class;
			final java.util.Date expected = new java.util.Date(timestamp);
			scenario(expected, dateString, toType);
			scenario(expected, timestamp, toType);
			scenario(expected, calendar, toType);
			scenario(expected, new java.util.Date(timestamp), toType);
			scenario(expected, new java.sql.Date(timestamp), toType);
			scenario(expected, new java.sql.Timestamp(timestamp), toType);
		}
		{
			final Class<?> toType = java.sql.Date.class;
			final java.sql.Date expected = new java.sql.Date(timestamp);
			scenario(expected, dateString, toType);
			scenario(expected, timestamp, toType);
			scenario(expected, calendar, toType);
			scenario(expected, new java.util.Date(timestamp), toType);
			scenario(expected, new java.sql.Date(timestamp), toType);
			scenario(expected, new java.sql.Timestamp(timestamp), toType);
		}
		{
			final Class<?> toType = java.sql.Time.class;
			final java.sql.Time expected = new java.sql.Time(timestamp);
			scenario(expected, dateString, toType);
			scenario(expected, timestamp, toType);
			scenario(expected, calendar, toType);
			scenario(expected, new java.util.Date(timestamp), toType);
			scenario(expected, new java.sql.Date(timestamp), toType);
			scenario(expected, new java.sql.Timestamp(timestamp), toType);
		}
		{
			final Class<?> toType = java.sql.Timestamp.class;
			final java.sql.Timestamp expected = new java.sql.Timestamp(timestamp);
			scenario(expected, dateString, toType);
			scenario(expected, timestamp, toType);
			scenario(expected, calendar, toType);
			scenario(expected, new java.util.Date(timestamp), toType);
			scenario(expected, new java.sql.Date(timestamp), toType);
			scenario(expected, new java.sql.Timestamp(timestamp), toType);
		}
		{
			final Class<?> toType = Calendar.class;
			final Calendar expected = calendar;
			scenario(expected, dateString, toType);
			scenario(expected, timestamp, toType);
			scenario(expected, calendar, toType);
			scenario(expected, new java.util.Date(timestamp), toType);
			scenario(expected, new java.sql.Date(timestamp), toType);
			scenario(expected, new java.sql.Timestamp(timestamp), toType);
		}
		{
			final Class<?> toType = Long.class;
			final Long expected = timestamp;
			//scenario(expected, dateString, toType);
			scenario(expected, timestamp, toType);
			scenario(expected, calendar, toType);
			scenario(expected, new java.util.Date(timestamp), toType);
			scenario(expected, new java.sql.Date(timestamp), toType);
			scenario(expected, new java.sql.Timestamp(timestamp), toType);
		}
	}
	
	public void testString() {
		scenario("c", 'c', String.class);
		scenario("true", Boolean.TRUE, String.class);
		scenario("false", Boolean.FALSE, String.class);
		scenario("20060708", date, String.class);
		scenario("20060708", new java.sql.Date(timestamp), String.class);
		scenario("20060708", new java.sql.Time(timestamp), String.class);
		scenario("20060708", new java.sql.Timestamp(timestamp), String.class);
		scenario("1", (byte) 1, String.class);
		scenario("1,000", (short) 1000, String.class);
		scenario("1,000", (int) 1000, String.class);
		scenario("1,000", (long) 1000, String.class);
		scenario("1,000", (float) 1000, String.class);
		scenario("1,000", (double) 1000, String.class);
		scenario("1,000", BigInteger.valueOf(1000), String.class);
		scenario("1,000", new BigDecimal(1000), String.class);
	}
	
	public void testBoolean() {
		scenario(Boolean.TRUE, "true", Boolean.class);
		scenario(Boolean.FALSE, "false", Boolean.class);
		scenario(Boolean.TRUE, 1, Boolean.class);
		scenario(Boolean.TRUE, 0, Boolean.class);
		scenario(Boolean.TRUE, Boolean.TRUE, Boolean.class);
		scenario(Boolean.FALSE, Boolean.FALSE, Boolean.class);
		scenario(1, Boolean.TRUE, Integer.class);
		scenario(0, Boolean.FALSE, Integer.class);
	}
	
	public void testNullPrimitive() {
		scenario(Boolean.FALSE, null, boolean.class);
		scenario((char) 0, null, char.class);
		scenario((byte) 0, null, byte.class);
		scenario((short) 0, null, short.class);
		scenario((int) 0, null, int.class);
		scenario((long) 0, null, long.class);
		scenario((float) 0, null, float.class);
		scenario((double) 0, null, double.class);
		scenario(null, null, int[].class);
		scenario(new int[]{1, 0}, new String[]{"1", null}, int[].class);
	}
	
	public void testNullWrapper() {
		scenario(null, null, Boolean.class);
		scenario(null, null, Character.class);
		scenario(null, null, Byte.class);
		scenario(null, null, Short.class);
		scenario(null, null, Integer.class);
		scenario(null, null, Long.class);
		scenario(null, null, Float.class);
		scenario(null, null, Double.class);
		scenario(null, null, Integer[].class);
		scenario(new Integer[]{1, null}, new String[]{"1", null}, Integer[].class);
		scenario(null, null, Collection.class);
		scenario(null, null, List.class);
	}
	
	public void testPrimitiveException() {
		scenario((char) 0, "", char.class);
		scenario((byte) 0, "", byte.class);
		scenario((short) 0, "", short.class);
		scenario((int) 0, "", int.class);
		scenario((long) 0, "", long.class);
		scenario((float) 0, "", float.class);
		scenario((double) 0, "", double.class);
		scenario(new int[]{1, 0}, new String[]{"1", "a"}, int[].class);
	}
	
	public void testException() {
		scenario(null, "", Character.class);
		scenario(null, "", Byte.class);
		scenario(null, "", Short.class);
		scenario(null, "", Integer.class);
		scenario(null, "", Long.class);
		scenario(null, "", Float.class);
		scenario(null, "", Double.class);
		scenario(new Integer[]{1, null}, new String[]{"1", "a"}, Integer[].class);
	}
	
	public void testStringToNumber() {
		scenario(Byte.valueOf((byte) 1), "1", Byte.class);
		scenario(Short.valueOf((short) 1), "1", Short.class);
		scenario(Integer.valueOf(1), "1", Integer.class);
		scenario(Long.valueOf(1), "1", Long.class);
		scenario(Float.valueOf(1), "1", Float.class);
		scenario(Double.valueOf(1), "1", Double.class);
		scenario(BigInteger.valueOf(1), "1", BigInteger.class);
		scenario(new BigDecimal(1), "1", BigDecimal.class);
	}
	
	public void testNumberToNumber() {
		scenario(Byte.valueOf((byte) 1), Integer.valueOf(1), Byte.class);
		scenario(Short.valueOf((short) 1), Integer.valueOf(1), Short.class);
		scenario(Integer.valueOf(1), Long.valueOf(1), Integer.class);
		scenario(Long.valueOf(1), Integer.valueOf(1), Long.class);
		scenario(Float.valueOf(1), Integer.valueOf(1), Float.class);
		scenario(Double.valueOf(1), Integer.valueOf(1), Double.class);
		scenario(BigInteger.valueOf(1), Integer.valueOf(1), BigInteger.class);
		scenario(new BigDecimal(1), Integer.valueOf(1), BigDecimal.class);
	}
	
	public void testToObject() {
		{
			final Object o = new Object();
			scenario(o, o, Object.class);
		}
		scenario(null, null, Object.class);
		scenario("1", "1", Object.class);
		scenario(new String[] { "1" }, new String[] { "1" }, Object.class);
		scenario(Arrays.asList("1"), Arrays.asList("1"), Object.class);
	}
	
	public void testToAtom() {
		scenario(Integer.valueOf(1), "1", Integer.class);
		scenario(Integer.valueOf(1), new String[] { "1" }, Integer.class);
		scenario(Integer.valueOf(1), new String[] { "1", "2" }, Integer.class);
		scenario(Integer.valueOf(1), Arrays.asList("1"), Integer.class);
		scenario(Integer.valueOf(1), Arrays.asList("1", "2"), Integer.class);
	}
	
	public void testToPrimitiveArray() {
		scenario(new int[] { 1 }, "1", int[].class);
		scenario(new int[] { 1 }, new String[] { "1" }, int[].class);
		scenario(new int[] { 1, 2 }, new String[] { "1", "2" }, int[].class);
		scenario(new int[] { 1 }, Arrays.asList("1"), int[].class);
		scenario(new int[] { 1, 2 }, Arrays.asList("1", "2"), int[].class);
	}
	
	public void testToWrapperArray() {
		scenario(new Integer[] { 1 }, "1", Integer[].class);
		scenario(new Integer[] { 1 }, new String[] { "1" }, Integer[].class);
		scenario(new Integer[] { 1, 2 }, new String[] { "1", "2" }, Integer[].class);
		scenario(new Integer[] { 1 }, Arrays.asList("1"), Integer[].class);
		scenario(new Integer[] { 1, 2 }, Arrays.asList("1", "2"), Integer[].class);
	}
	
	public void testToCollection() {
		scenario(Arrays.asList("1"), "1", Collection.class);
		scenario(Arrays.asList("1"), new String[] { "1" }, Collection.class);
		scenario(Arrays.asList("1", "2"), new String[] { "1", "2" }, Collection.class);
		scenario(Arrays.asList("1"), Arrays.asList("1"), Collection.class);
		scenario(Arrays.asList("1", "2"), Arrays.asList("1", "2"), Collection.class);
	}
	
	public void testToList() {
		scenario(Arrays.asList("1"), "1", List.class);
		scenario(Arrays.asList("1"), new String[] { "1" }, List.class);
		scenario(Arrays.asList("1", "2"), new String[] { "1", "2" }, List.class);
		scenario(Arrays.asList("1"), Arrays.asList("1"), List.class);
		scenario(Arrays.asList("1", "2"), Arrays.asList("1", "2"), List.class);		
		scenario(Arrays.asList("1"), new HashSet<String>(Arrays.asList("1")), List.class);
		scenario(Arrays.asList("2", "1"), new HashSet<String>(Arrays.asList("1", "2")), List.class);
	}
	
	public void testSet() {
		scenario(new HashSet<String>(Arrays.asList("1")), "1", Set.class);
		scenario(new HashSet<String>(Arrays.asList("1")), new String[] { "1" }, Set.class);
		scenario(new HashSet<String>(Arrays.asList("1", "2")), new String[] { "1", "2" }, Set.class);
		scenario(new HashSet<String>(Arrays.asList("1")), Arrays.asList("1"), Set.class);
		scenario(new HashSet<String>(Arrays.asList("1", "2")), Arrays.asList("1", "2"), Set.class);
	}
	
	public void testDecomposite() {
		scenario(0, new int[]{0}, int.class);
		scenario(0, new Integer[]{null}, int.class);
		scenario(1, new Integer[]{1}, int.class);
	}
	
	public void testBeanToMap() {
		final TestBean bean = new TestBean();
		final ConvertContext context = new ConvertContext();
		context.setPropertyName(null);
		context.setToType(Map.class);
		context.setValue(bean);
		TypeConvertUtils.COLLECTION_CONVERTER.exec(context);
		final Map<String, Object> map = (Map<String, Object>) context.getValue();
		
		bean.setMyInt(3);
		assertEquals(Integer.valueOf(3), map.get("myInt"));

		map.put("myInt", 5);
		assertEquals(5, bean.getMyInt());

		map.put("myIntArray", Arrays.asList("2", "4"));
		assertTrue(TestUtils.equals(new int[]{2, 4}, bean.getMyIntArray()));
	}
	
	public void testMapToMap() {
		final Map<String, Object> map = new HashMap<String, Object>();
		final ConvertContext context = new ConvertContext();
		context.setPropertyName(null);
		context.setToType(Map.class);
		context.setValue(map);
		TypeConvertUtils.COLLECTION_CONVERTER.exec(context);
		assertSame(map, context.getValue());
	}
	
	public void testMapToBean() {
		final Map<String, Object> map = new HashMap<String, Object>();
		map.put("myInt", "3");
		map.put("myIntArray", Arrays.asList("2", "4"));
		
		final ConvertContext context = new ConvertContext();
		context.setPropertyName(null);
		context.setToType(TestBean.class);
		context.setValue(map);
		TypeConvertUtils.COLLECTION_CONVERTER.exec(context);
		final TestBean bean = (TestBean) context.getValue();
		
		assertEquals(3, bean.getMyInt());
		assertTrue(TestUtils.equals(new int[]{2, 4}, bean.getMyIntArray()));
	}

	public void testNormal() {
		scenario(Character.valueOf('a'), "abc", Character.class);
	}
	
	public void testGetPropertyName() {
		final ConvertContext context = new ConvertContext();
		context.setPropertyName("hoge");
		assertEquals("hoge", TypeConvertUtils.GET_PROPERTY_NAME.exec(context));
	}
}
