/*******************************************************************************
*   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.util.HashSet;
import java.util.Set;

import org.eclipse.jdt.annotation.Nullable;
import org.junit.Test;

import com.analog.lyric.collect.ReleasableIterator;
import com.analog.lyric.options.AbstractOptionHolder;
import com.analog.lyric.options.IOption;
import com.analog.lyric.options.IOptionHolder;
import com.analog.lyric.options.IOptionKey;
import com.analog.lyric.options.IntegerOptionKey;
import com.analog.lyric.options.LocalOptionHolder;
import com.analog.lyric.options.Option;
import com.analog.lyric.options.StatelessOptionHolder;
import com.analog.lyric.options.StringOptionKey;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;

/**
 * 
 * @since 0.06
 * @author CBarber
 */
public class TestOptionHolder
{
	public final static IntegerOptionKey NOT_SET = new IntegerOptionKey(TestOptionHolder.class, "NOT_SET", 666);
	
	public final static StringOptionKey K = new StringOptionKey(TestOptionHolder.class, "K");
	
	public final static IntegerOptionKey I = new IntegerOptionKey(TestOptionHolder.class, "I");
	
	public final static StringOptionKey L =
		new StringOptionKey(TestOptionHolder.class, "L", "default", IOptionKey.Lookup.LOCAL);
	
	@Test
	public void test()
	{
		IOptionHolder holder = new StatelessOptionHolder(){};
		assertInvariants(holder);
		assertFalse(holder.supportsLocalOptions());
		
		assertNull(holder.getOptionParent());
		holder.clearLocalOptions(); // doesn't do anything
		holder.unsetOption(K); // also doesn't do anything
		assertNull(holder.getLocalOption(K));
		assertNull(holder.getLocalOption(L));
		expectThrow(UnsupportedOperationException.class, holder, "setOption", K, "foo");
		assertEquals(0, Iterables.size(holder.getLocalOptions()));
		
		holder = new LocalOptionHolder();
		assertInvariants(holder);
		assertTrue(holder.supportsLocalOptions());
		assertNull(holder.getOptionParent());
		holder.unsetOption(K);

		assertNull(holder.getLocalOption(K));
		assertNull(holder.getOption(K));
		assertEquals(1, Iterators.size(holder.getOptionDelegates()));
		assertEquals(0, Iterables.size(holder.getLocalOptions()));
		
		holder.setOption(K, "hi");
		assertEquals("hi", holder.getLocalOption(K));
		assertEquals("hi", holder.getOption(K));
		assertInvariants(holder);
		
		holder.setOption(K, "bye");
		assertEquals("bye", holder.getOption(K));
		
		holder.unsetOption(K);
		assertEquals("", holder.getOptionOrDefault(K));
		assertNull(holder.getOption(K));
		
		holder.setOption(K, "k");
		holder.setOption(I, 42);
		assertEquals("k", holder.getOption(K));
		assertEquals((Integer)42, holder.getOption(I));
		assertInvariants(holder);
		assertEquals(2, Iterables.size(holder.getLocalOptions()));
		holder.clearLocalOptions();
		assertNull(holder.getOption(K));
		assertNull(holder.getOption(I));
		assertEquals(0, Iterables.size(holder.getLocalOptions()));
		
		final IOptionHolder parent = holder;
		AbstractOptionHolder child = new LocalOptionHolder() {
			@Override
			public @Nullable IOptionHolder getOptionParent()
			{
				return parent;
			}
		};
		
		IOptionHolder[] source = new IOptionHolder[2];
		
		assertInvariants(child);
		assertNull(child.getOption(K));
		parent.setOption(K, "bar");
		parent.setOption(L,  "bar");
		assertEquals("bar", child.getOption(K));
		assertEquals("bar", child.getOptionAndSource(K, null));
		assertEquals("bar", child.getOptionAndSource(K, new IOptionHolder[0]));
		assertEquals("bar", child.getOptionAndSource(K, source));
		assertEquals(null, child.getOption(L));
		assertEquals("default", child.getOptionOrDefault(L));
		assertEquals("bar", parent.getOption(L));
		assertArrayEquals(new Object[] { parent, null }, source);
		assertInvariants(child);
		
		child.setOption(K, "gag");
		child.setOption(L, "gag");
		assertEquals("gag", child.getOption(K));
		assertEquals("gag", child.getOptionAndSource(K, source));
		assertEquals("gag", child.getOption(L));
		assertArrayEquals(new Object[] { child, null }, source);
		assertInvariants(child);
		
		assertNull(child.getOptionAndSource(NOT_SET, source));
		assertArrayEquals(new Object[] { child, null }, source); // not modified
		source[0] = null;
		assertNull(child.getOptionAndSource(NOT_SET, source));
		assertArrayEquals(new Object[] { null, null }, source);
	}
	
	/**
	 * Unit test for {@link Option} class.
	 * 
	 * @since 0.07
	 */
	@Test
	public void testOption()
	{
		Option<String> strOption = new Option<String>(K);
		assertSame(K, strOption.key());
		assertEquals("", strOption.value());
		assertEquals(strOption, strOption);
		assertEquals(strOption, new Option<String>(K, ""));
		assertEquals(strOption.hashCode(), new Option<String>(K).hashCode());
		assertNotEquals(strOption, "");
		assertNotEquals(strOption, new Option<String>(K, "x"));
		assertNotEquals(strOption, new Option<Integer>(I));
		
		strOption = new Option<String>(K, "foo");
		assertEquals("foo", strOption.value());
		assertEquals("TestOptionHolder.K=foo", strOption.toString());
		
		strOption = new Option<String>(K, null);
		assertNull(strOption.value());
		
		IOptionHolder holder = new LocalOptionHolder();
		Option.setOptions(holder, Option.create(K, "a"), Option.create(I, 42));
		assertEquals(Option.create(K, "a"), Option.lookup(holder, K));
		assertEquals(Option.create(I, 42), Option.lookup(holder, I));
		
		Option.setOptions(holder, Option.create(I, null));
		assertNull(holder.getOption(I));
	}
	
	private void assertInvariants(IOptionHolder holder)
	{
		Set<IOptionKey<?>> seen = new HashSet<IOptionKey<?>>();
		for (IOption<?> option : holder.getLocalOptions())
		{
			assertTrue(seen.add(option.key()));
			assertEquals(option.value(), holder.getLocalOption(option.key()));
			assertEquals(option.value(), holder.getOption(option.key()));
			assertEquals(option.value(), holder.getOptionOrDefault(option.key()));
		}
		
		assertNull(holder.getLocalOption(NOT_SET));
		assertNull(holder.getOption(NOT_SET));
		assertEquals(NOT_SET.defaultValue(), holder.getOptionOrDefault(NOT_SET));
		
		ReleasableIterator<? extends IOptionHolder> delegates = holder.getOptionDelegates();
		assertTrue(delegates.hasNext());
		assertSame(holder, delegates.next());
		while (delegates.hasNext())
		{
			IOptionHolder delegate = delegates.next();
			assertNotSame(delegate, holder);
			for (IOption<?> option : delegate.getLocalOptions())
			{
				final IOptionKey<?> key = option.key();
				if (seen.add(key))
				{
					assertEquals(option.value(), delegate.getLocalOption(key));
					if (option.key().local())
					{
						assertNull(holder.getOption(key));
					}
					else
					{
						assertEquals(option.value(), holder.getOption(key));
					}
				}
				else
				{
					// Option is set, but value will come from previous delegate.
					assertNotNull(holder.getOption(key));
				}
			}
		}
		delegates.release();
	}
}
