/*******************************************************************************
*   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.collect.tests;

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

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;

import org.junit.Test;

import com.analog.lyric.collect.Supers;
import com.google.common.collect.ImmutableList;

public class TestSupers
{
	static enum E1 { A,B,C; }
	static enum E2 { A,B,C; }
	
	static interface I {}
	static interface J {}
	static interface K extends I {}
	static interface L extends I {}
	static interface M extends K, L {}
	
	static class A {}
	static class B extends A {}
	static class C extends A {}
	static class D extends B {}
	static final class E extends D {}
	static class F extends C implements L {}
	
	@Test
	public void testNarrowArrayOf()
	{
		narrowArrayOfTestCase(A.class, new Object[] {new B(), new C()});
		narrowArrayOfTestCase(E.class, new Object[] {new E(), new E()});
		
		A[] a = (A[]) Supers.narrowArrayOf(A.class, 0, new Object[0]);
		assertEquals(A.class, a.getClass().getComponentType());
		A[] a2 = Supers.narrowArrayOf(A.class, 0, a);
		assertSame(a, a2);
		
		A[] a3 = new A[] { new A(), new B(), new C() };
		assertSame(a3, Supers.narrowArrayOf(A.class, a3));
		assertNull(Supers.narrowArrayOf(B.class, a3));
		Object[] o3 = new Object[] { a3[0], a3[1], a3[2] };
		A[] a4 = Supers.narrowArrayOf(A.class, o3);
		assertArrayEquals(a3, a4);
		assertEquals(A.class, requireNonNull(a4).getClass().getComponentType());
	}
	
	private <T> void narrowArrayOfTestCase(Class<?> expectedComponentType, T[] elements)
	{
		T[] copy = Supers.narrowArrayOf(elements);
		assertArrayEquals(elements, copy);
		assertEquals(expectedComponentType, copy.getClass().getComponentType());
		
		ImmutableList<Class<?>> supers = Supers.superClasses(expectedComponentType);
		for (int depth = 0; depth < supers.size(); ++ depth)
		{
			copy = Supers.narrowArrayOf(Object.class, depth, elements);
			assertArrayEquals(elements, copy);
			assertEquals(supers.get(depth), copy.getClass().getComponentType());
			
			copy = Supers.narrowArrayOf(supers.get(depth), 0, elements);
			assertArrayEquals(elements, copy);
			assertEquals(supers.get(depth), copy.getClass().getComponentType());
		}
		
		copy = Supers.narrowArrayOf(Object.class, supers.size(), elements);
		assertArrayEquals(elements, copy);
		assertEquals(expectedComponentType, copy.getClass().getComponentType());
	}
	
	@Test
	public void testInvokeMethod()
	{
		try
		{
			assertEquals(3, Supers.invokeMethod("foo", "length"));
			assertEquals(3, Supers.invokeMethod("foobar", String.class, "indexOf", "bar"));
			assertEquals("foobar", Supers.invokeMethod(String.class, "format", "foo%s", "bar"));
			assertEquals("foobar", Supers.invokeMethod(String.class, "format", "foo%s", new Object[] {"bar"}));
			assertEquals("foobar", Supers.invokeMethod(String.class, "format", "foobar"));
			assertEquals("foobar", Supers.invokeMethod(String.class, "format", "foobar", new Object[] {}));
// FIXME - Supers.invokeMethod support for auto boxing of primitives
//			Supers.invokeMethod(Arrays.class, "asList", 1,2,3);
			List<?> list = (List<?>)Supers.invokeMethod(Arrays.class, "asList", "1", "2", "3");
			requireNonNull(list);
			assertArrayEquals(new Object[] {"1", "2", "3",}, list.toArray());
			
			// Test invocation of method implemented in a non-public subclass
			assertEquals(42, Supers.invokeMethod(TestSupers2.makePrivateClass1(), "bar"));
			assertEquals(23, Supers.invokeMethod(TestSupers2.makePrivateClass1(), "bar", 23));
			assertEquals(23, Supers.invokeMethod(TestSupers2.makePrivateClass2(), "foo"));
			assertEquals(-12, Supers.invokeMethod(TestSupers2.makePrivateClass2(), "foo", 12));
		}
		catch (Exception ex)
		{
			fail(ex.toString());
		}
	}
	
	/**
	 * Tests {@link Supers#isSubclassOf} and {@link Supers#isStrictSubclassOf}.
	 */
	@Test
	public void testIsSubclassOf()
	{
		assertTrue(Supers.isSubclassOf(A.class, A.class));
		assertFalse(Supers.isStrictSubclassOf(A.class, A.class));
		assertTrue(Supers.isSubclassOf(D.class, A.class));
		assertTrue(Supers.isStrictSubclassOf(D.class, A.class));
		assertFalse(Supers.isSubclassOf(A.class, D.class));
		assertFalse(Supers.isStrictSubclassOf(A.class, D.class));
	}
	
	@Test
	public void testLookupMethod()
	{
		try
		{
			Method m;
			
			try
			{
				m = Supers.lookupMethod("foo", "bar");
				fail("expected NoSuchMethodException");
			}
			catch (NoSuchMethodException ex)
			{
			}
			
			try
			{
				m = Supers.lookupMethod("foo", "indexOf", 2.35);
				fail("expected NoSuchMethodException");
			}
			catch (NoSuchMethodException ex)
			{
			}
			
			m = Supers.lookupMethod("foo", "length");
			assertEquals("length", m.getName());
			m = Supers.lookupMethod("foo", "indexOf", 0);
			assertEquals("indexOf", m.getName());
			assertArrayEquals(new Object[] { Integer.TYPE }, m.getParameterTypes());
			m = Supers.lookupMethod("foo", "indexOf", "f");
			assertEquals("indexOf", m.getName());
			assertArrayEquals(new Object[] { String.class }, m.getParameterTypes());
			m = Supers.lookupMethod("foo", "indexOf", new Object[] { null } );
			assertEquals("indexOf", m.getName());
			assertArrayEquals(new Object[] { String.class }, m.getParameterTypes());
			m = Supers.lookupMethod("foo", "format", "%s", "x");
			assertEquals("format", m.getName());
			assertArrayEquals(new Object[] { String.class, Object[].class }, m.getParameterTypes());
			m = Supers.lookupMethod(String.class, "format", "hi");
			assertEquals("format", m.getName());
			assertArrayEquals(new Object[] { String.class, Object[].class }, m.getParameterTypes());
			m = Supers.lookupMethod(Arrays.class, "asList", 1, 2, 3);
			assertEquals("asList", m.getName());
		}
		catch (Exception ex)
		{
			fail(ex.toString());
		}
		
		expectThrow(NoSuchMethodException.class,
			"No method in String with signature compatible with doesNotExist\\(\\)",
			Supers.class, "lookupMethod", "foo", "doesNotExist");
		expectThrow(NoSuchMethodException.class,
			".*with size\\(null,\\s?int\\)",
			Supers.class, "lookupMethod", "foo", "size", null, 23);
	}
	
	@Test
	public void testNearestCommonSuperclass()
	{
		assertEquals(A.class, Supers.nearestCommonSuperClass(A.class, A.class));
		assertEquals(A.class, Supers.nearestCommonSuperClass(A.class, B.class));
		assertEquals(A.class, Supers.nearestCommonSuperClass(B.class, A.class));
		assertEquals(Integer.TYPE, Supers.nearestCommonSuperClass(Integer.TYPE, Integer.TYPE));
		assertNull(Supers.nearestCommonSuperClass(Integer.TYPE, Long.TYPE));
		assertNull(Supers.nearestCommonSuperClass(Object.class, Integer.TYPE));
		assertEquals(Object.class, Supers.nearestCommonSuperClass(K.class, L.class));
		assertEquals(Object.class, Supers.nearestCommonSuperClass(F.class, M.class));
		assertEquals(A.class, Supers.nearestCommonSuperClass(B.class, C.class));
		assertEquals(A.class, Supers.nearestCommonSuperClass(F.class, D.class));
		assertEquals(Object.class, Supers.nearestCommonSuperClass(D.class, A[].class));
		
		assertEquals(Object.class, Supers.nearestCommonSuperClass(new Integer[0]));
		assertEquals(B.class, Supers.nearestCommonSuperClass(new A[] {new B()} ));
		assertEquals(Object.class, Supers.nearestCommonSuperClass(new Object[] { null, null, null }));
		assertEquals(A.class, Supers.nearestCommonSuperClass(null, null, new A(), new B()));
		assertEquals(A.class, Supers.nearestCommonSuperClass(new B(), new C()));
		assertEquals(F.class, Supers.nearestCommonSuperClass(new I[] {new F(), null}));
		assertEquals(E1.class, Supers.nearestCommonSuperClass(new E1[] { E1.A, E1.B }));
		assertEquals(E.class, Supers.nearestCommonSuperClass(new E[] { new E() }));
		assertEquals(A.class, Supers.nearestCommonSuperClass(new A[] { new A(), new B() }));
	}
	
	@Test
	public void testNumberOfSuperClasses()
	{
		assertEquals(0, Supers.numberOfSuperClasses(Object.class));
		assertEquals(1, Supers.numberOfSuperClasses(Long[].class));
		assertEquals(2, Supers.numberOfSuperClasses(E1.class));
		assertEquals(1, Supers.numberOfSuperClasses(A.class));
		assertEquals(2, Supers.numberOfSuperClasses(B.class));
		assertEquals(4, Supers.numberOfSuperClasses(E.class));
	}

	@Test
	public void testSuperClasses()
	{
		assertEquals(
			ImmutableList.of(),
			Supers.superClasses(Object.class));
		assertEquals(
			ImmutableList.<Class<?>>of(),
			Supers.superClasses(Integer.TYPE));
		assertEquals(
			ImmutableList.of(Object.class),
			Supers.superClasses(A.class));
		assertEquals(
			ImmutableList.of(Object.class, A.class, B.class),
			Supers.superClasses(D.class));
	}
}
