/*******************************************************************************
*   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.dimple.test.options;

import static org.junit.Assert.*;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashSet;
import java.util.Set;

import org.junit.Test;

import com.analog.lyric.dimple.environment.DimpleEnvironment;
import com.analog.lyric.dimple.options.DimpleOptionRegistry;
import com.analog.lyric.dimple.options.DimpleOptions;
import com.analog.lyric.dimple.test.DimpleTestBase;
import com.analog.lyric.options.IOptionKey;
import com.analog.lyric.options.OptionKey;
import com.analog.lyric.options.OptionKeys;
import com.google.common.reflect.ClassPath;

/**
 * 
 * @since 0.07
 * @author Christopher Barber
 */
public class TestDimpleOptions extends DimpleTestBase
{
	/**
	 * Test contents of {@link DimpleOptionRegistry} and {@link DimpleOptions} classes.
	 * <p>
	 * Makes sure that options are not accidentally omitted because they have mis-matched
	 * name or are not declared "public static final".
	 * <p>
	 * @since 0.07
	 */
	@Test
	public void test() throws IllegalAccessException
	{
		DimpleOptionRegistry registry = DimpleEnvironment.active().optionRegistry();
		
		Set<IOptionKey<?>> allKeys = new HashSet<IOptionKey<?>>();
		for (IOptionKey<?> key : registry)
		{
			assertSame(key, registry.get(OptionKey.canonicalName(key)));
			assertSame(key, registry.get(OptionKey.qualifiedName(key)));
			assertSame(key, registry.asKey(key));
			assertSame(key, registry.asKey(OptionKey.qualifiedName(key)));
			assertTrue(allKeys.add(key));
		}
		
		Set<Class<?>> optionDeclarers = new HashSet<Class<?>>();
		Set<IOptionKey<?>> allKeys2 = new HashSet<IOptionKey<?>>();
		for (OptionKeys keys : registry.getOptionKeys())
		{
			assertTrue(keys.size() > 0);
			for (IOptionKey<?> key : keys.values())
			{
				assertTrue(allKeys2.add(key));
			}
			
			Class<?> declaringClass = keys.declaringClass();
			optionDeclarers.add(declaringClass);
			for (Field field : declaringClass.getDeclaredFields())
			{
				if (IOptionKey.class.isAssignableFrom(field.getType()))
				{
					final int modifiers = field.getModifiers();
					assertTrue(Modifier.isFinal(modifiers));
					assertTrue(Modifier.isPublic(modifiers));
					assertTrue(Modifier.isStatic(modifiers));
					
					IOptionKey<?> key = (IOptionKey<?>)field.get(declaringClass);
					assertEquals(field.getName(), key.name());
					assertSame(key, registry.get(OptionKey.qualifiedName(key)));
					assertSame(declaringClass, key.getDeclaringClass());
				}
			}
		}
		
		// Look for option declarations that are not automatically included in the registry.
		try
		{
			String errorMessage = "";
			ClassPath dimpleClassPath = ClassPath.from(DimpleOptionRegistry.class.getClassLoader());
			Set<ClassPath.ClassInfo> dimpleClassInfo =
				dimpleClassPath.getTopLevelClassesRecursive("com.analog.lyric.dimple");
			for (ClassPath.ClassInfo info : dimpleClassInfo)
			{
				String packageName = info.getPackageName();
				if (packageName.startsWith("com.analog.lyric.dimple.test") ||
					packageName.startsWith("com.analog.lyric.dimple.jsproxy"))
				{
					// Skip test packages
					continue;
				}
				
				Class<?> dimpleClass = info.load();
				OptionKeys keys = OptionKeys.declaredInClass(dimpleClass);
				if (!keys.isEmpty())
				{
					if (!optionDeclarers.contains(dimpleClass))
					{
						errorMessage += String.format("%s not in DimpleRegistry\n", keys.declaringClass().getName());
					}
				}
			}

			if (!errorMessage.isEmpty())
			{
				// When this fails, either add the class in the constructor of DimpleOptionRegistry or
				// remove the option declarations if they are not used.
				fail(errorMessage);
			}
		}
		catch (IOException ex)
		{
			fail(ex.toString());
		}
		
		// TODO check for missing option keys
		
		assertEquals(allKeys, allKeys2);
	}
}
