/*******************************************************************************
*   Copyright 2014 Analog Devices, Inc.
*
*   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 com.analog.lyric.options.tests;

import static com.analog.lyric.util.test.ExceptionTester.*;
import static org.junit.Assert.*;

import java.io.Serializable;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.util.Arrays;

import org.junit.Test;

import com.analog.lyric.dimple.test.DimpleTestBase;
import com.analog.lyric.options.AbstractOptionValueList;
import com.analog.lyric.options.IOptionValue;
import com.analog.lyric.options.OptionDoubleList;
import com.analog.lyric.options.OptionIntegerList;
import com.analog.lyric.options.OptionStringList;
import com.analog.lyric.options.OptionValidationException;
import com.analog.lyric.util.test.SerializationTester;
import com.google.common.primitives.Primitives;

/**
 * Tests for {@link IOptionValue} implementations in com.analog.lyric.options package.
 * <p>
 * @since 0.07
 * @author Christopher Barber
 */
public class TestOptionValue extends DimpleTestBase
{
	@Test
	public void test()
	{
		testList(new OptionStringList("fee", "fie", "foe"), "fee", "fie", "foe");
		testList(new OptionDoubleList(2.3, -4.2, 23.0), 2.3, -4.2, 23.0);
		testList(new OptionDoubleList(new Double[] { 2.3, -4.2, 23.0} ), 2.3, -4.2, 23.0);
		testList(new OptionIntegerList(3,4,5), 3, 4, 5);
		testList(new OptionIntegerList(new Integer[] { 3,4,5 }), 3, 4, 5);
		testList(new OptionStringList());
	}
	
	@SuppressWarnings("unchecked")
	private <T extends Serializable> void testList(AbstractOptionValueList<T> list, T ... expectedValues)
	{
		assertInvariants(list);
		assertArrayEquals(expectedValues, list.toArray());
	}
	
	private void assertInvariants(IOptionValue value)
	{
		assertEquals(value, value);

		IOptionValue value2 = SerializationTester.clone(value);
		assertEquals(value, value2);
		
		if (value instanceof AbstractOptionValueList)
		{
			assertListInvariants((AbstractOptionValueList<?>)value);
		}
	}
	
	private <T extends Serializable> void assertListInvariants(AbstractOptionValueList<T> list)
	{
		int size = list.size();
		
		Class<? extends T> elementType = list.elementType();
		Object[] values = list.toArray();
		assertEquals(size, values.length);
		@SuppressWarnings("unchecked")
		T[] values2 = list.toArray((T[])Array.newInstance(elementType, 0));
		assertEquals(size, values2.length);
		assertSame(elementType, values2.getClass().getComponentType());
		
		Object[] array = new Object[size];
		Object[] values3 = list.toArray(array);
		assertSame(array, values3);
		assertArrayEquals(values, values3);
		
		array = new Object[size + 3];
		Arrays.fill(array, "crap");
		values3 = list.toArray(array);
		assertSame(array, values3);
		assertNull(values3[size]);
		assertEquals("crap", values3[size + 1]);
		
		Object primitive = list.toPrimitiveArray();
		assertTrue(primitive.getClass().isArray());
		if (Primitives.isWrapperType(elementType))
		{
			assertTrue(primitive.getClass().getComponentType().isPrimitive());
			assertEquals(primitive.getClass().getComponentType(), Primitives.unwrap(elementType));
		}
		else
		{
			assertFalse(primitive.getClass().getComponentType().isPrimitive());
		}
		
		Class<?> listClass = list.getClass();
		try
		{
			final Method fromObject = listClass.getMethod("fromObject", Object.class);
			try
			{
				assertSame(list, fromObject.invoke(listClass, list));
				assertEquals(list, fromObject.invoke(listClass, primitive));
				assertEquals(list, fromObject.invoke(listClass, new Object[] { list.toArray() }));
				if (Primitives.isWrapperType(elementType))
				{
					Object[] a = (Object[])Array.newInstance(elementType, size);
					assertEquals(list, fromObject.invoke(listClass, new Object[] { list.toArray(a) }));
				}
			}
			catch (Exception ex)
			{
				fail(ex.toString());
			}
			
			expectThrow(OptionValidationException.class, listClass, "fromObject", Thread.currentThread());
		}
		catch (NoSuchMethodException ex)
		{
			// ignore
		}
		
		for (int i = 0; i < size; ++i)
		{
			Object value = list.get(i);
			assertEquals(values[i], value);
			assertEquals(values2[i], value);
			assertEquals(values3[i], value);
			assertEquals(value, Array.get(primitive, i));
			assertTrue(elementType.isAssignableFrom(value.getClass()));
		}
		
		String str = "{";
		for (int i = 0; i < size; ++i)
		{
			if (i > 0)
			{
				str = str + ",";
			}
			str = str + values[i].toString();
		}
		str = str + '}';
		
		assertEquals(str, list.toString());
	}
}
