/*******************************************************************************
*   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 org.junit.Assert.*;

import java.lang.reflect.Constructor;
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.ConcurrentLinkedQueue;

import org.junit.Test;

import com.analog.lyric.collect.BinaryHeap;
import com.analog.lyric.collect.ConstructorRegistry;
import com.analog.lyric.collect.IHeap;

/**
 * Test for {@link ConstructorRegistry}
 * 
 * @since 0.07
 * @author Christopher Barber
 */
public class TestConstructorRegistry
{
	@Test
	public void test()
	{
		String lyricCollectPackage = IHeap.class.getPackage().getName();
		
		ConstructorRegistry<IHeap<?>> heapRegistry = new ConstructorRegistry<>(IHeap.class);
		
		assertTrue(heapRegistry.isEmpty());
		assertInvariants(heapRegistry);
		assertNull(heapRegistry.get(42)); // not a string key
		assertNull(heapRegistry.get("DoesNotExist"));
		assertNull(heapRegistry.getClassOrNull("DoesNotExist"));
		assertNull(heapRegistry.instantiateOrNull("DoesNotExist"));
		assertNull(heapRegistry.get("ArrayUtil"));
		expectThrow(RuntimeException.class, heapRegistry, "getClass", "DoesNotExist");
		expectThrow(RuntimeException.class, heapRegistry, "instantiate", "DoesNotExist");
		
		assertArrayEquals(new String[] { lyricCollectPackage }, heapRegistry.getPackages());
		assertSame(BinaryHeap.class, heapRegistry.getClassOrNull("BinaryHeap"));
		assertInvariants(heapRegistry);
		assertEquals(2 * BinaryHeap.class.getConstructors().length, heapRegistry.size());
		assertTrue(heapRegistry.containsKey("BinaryHeap"));
		
		heapRegistry.reset();
		assertArrayEquals(new String[] { lyricCollectPackage }, heapRegistry.getPackages());
		assertTrue(heapRegistry.isEmpty());
		
		ConstructorRegistry<Collection<?>> collectionRegistry =
			new ConstructorRegistry<>(Collection.class, lyricCollectPackage);
		assertTrue(collectionRegistry.isEmpty());
		assertInvariants(collectionRegistry);
		collectionRegistry.loadAll();
		assertFalse(collectionRegistry.isEmpty());
		assertTrue(collectionRegistry.containsKey("BinaryHeap"));
		assertTrue(collectionRegistry.containsKey("UniquePriorityQueue"));
		assertFalse(collectionRegistry.containsKey("Tuple")); // is abstract
		assertTrue(collectionRegistry.containsKey("Tuple2"));
		assertNull(collectionRegistry.get("Tuple2"));
		
		collectionRegistry.addPackage("java.util");
		assertArrayEquals(new String[] { lyricCollectPackage, "java.util" }, collectionRegistry.getPackages());
		assertSame(ArrayDeque.class, collectionRegistry.getClassOrNull("ArrayDeque"));
		assertInvariants(collectionRegistry);
		
		collectionRegistry.addClass(ConcurrentLinkedQueue.class);
		assertSame(ConcurrentLinkedQueue.class, collectionRegistry.getClass("ConcurrentLinkedQueue"));
		// package is not added in this case:
		assertArrayEquals(new String[] { lyricCollectPackage, "java.util" }, collectionRegistry.getPackages());
		assertInvariants(collectionRegistry);
		
		expectThrow(IllegalArgumentException.class, ".*not a subclass.*", collectionRegistry, "addClass", Object.class);
		expectThrow(IllegalArgumentException.class, ".*does not have an accessible constructor.*",
			collectionRegistry, "addClass", CollectionWithoutConstructor.class);
		
		collectionRegistry.reset();
		assertTrue(collectionRegistry.isEmpty());
		assertArrayEquals(new String[] { lyricCollectPackage }, collectionRegistry.getPackages());
		assertInvariants(collectionRegistry);
		
		try
		{
			assertNull(collectionRegistry.get("ConcurrentLinkedDeque"));
			assertEquals(ConcurrentLinkedDeque.class.getConstructor(),
				collectionRegistry.get("java.util.concurrent.ConcurrentLinkedDeque"));
			assertNull(collectionRegistry.get("ConcurrentLinkedDeque"));
		}
		catch (NoSuchMethodException ex)
		{
			fail(ex.toString());
		}
	}
	
	private <T> void assertInvariants(ConstructorRegistry<T> registry)
	{
		Class<? super T> superClass = registry.getSuperClass();
		
		int size = registry.size();
		Set<Map.Entry<String,Constructor<T>>> entries = registry.entrySet();
		Set<String> keys = registry.keySet();
		Collection<Constructor<T>> constructors = registry.values();
		assertEquals(size, entries.size());
		assertTrue(size >= keys.size());
		assertEquals(size, constructors.size());
		
		for (Map.Entry<String, Constructor<T>> entry : entries)
		{
			String name = entry.getKey();
			Constructor<T> constructor  = entry.getValue();
			
			assertTrue(keys.contains(name));
			assertTrue(registry.containsKey(name));
			assertTrue(registry.containsValue(constructor));
			assertSame(constructor, registry.get(name, constructor.getParameterTypes()));
			
			Class<? super T> c = constructor.getDeclaringClass();
			assertTrue(superClass.isAssignableFrom(c));
			assertSame(c, registry.getClass(name));
			if (name.contains("."))
			{
				assertEquals(name, c.getCanonicalName());
			}
			else
			{
				assertEquals(name, c.getSimpleName());
			}
			
			if (constructor.getParameterTypes().length == 0)
			{
				T instance = registry.instantiate(name);
				assertSame(c, instance.getClass());
			}
		}
	}
	
	public abstract static class CollectionWithoutConstructor<T> implements Collection<T>
	{
		public CollectionWithoutConstructor(int bogusArg)
		{
		}
	}
}
