/*******************************************************************************
*   Copyright 2015 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.model.variables;

import static com.analog.lyric.dimple.model.sugar.ModelSyntacticSugar.*;
import static com.analog.lyric.util.test.ExceptionTester.*;
import static org.junit.Assert.*;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;

import org.junit.Test;

import com.analog.lyric.dimple.model.core.FactorGraph;
import com.analog.lyric.dimple.model.core.Ids;
import com.analog.lyric.dimple.model.sugar.ModelSyntacticSugar.CurrentModel;
import com.analog.lyric.dimple.model.variables.Real;
import com.analog.lyric.dimple.model.variables.Variable;
import com.analog.lyric.dimple.model.variables.VariableBlock;
import com.analog.lyric.dimple.test.DimpleTestBase;

/**
 * 
 * @since 0.08
 * @author Christopher Barber
 */
public class TestVariableBlock extends DimpleTestBase
{
	@Test
	public void test()
	{
		FactorGraph root = new FactorGraph("root");

		try (CurrentModel cur = using(root))
		{
			Variable v1 = real("v1");
			Variable v2 = real("v2");
			
			VariableBlock block1 = root.addVariableBlock(v1, v2);
			assertBlock(block1, v1, v2);
			assertEquals(block1, block1);
			assertNotEquals(block1, "foo");
			assertEquals(root, block1.getParentGraph());
			
			VariableBlock block1a = root.addVariableBlock(v1, v2);
			assertSame(block1, block1a);
			
			// Blocks are only the same if the variable order is the same
			VariableBlock block2 = root.addVariableBlock(v2, v1);
			assertNotEquals(block1, block2);
			assertBlock(block2, v2, v1);
			assertNotEquals(block1.hashCode(), block2.hashCode());
			
			assertArrayEquals(new Object[] { block1, block2 }, root.getOwnedVariableBlocks().toArray());
			
			FactorGraph subgraph = root.addGraph(new FactorGraph());
			subgraph.setName("subgraph");
			assertSame(block2, subgraph.getChildByGraphTreeId(block2.getGraphTreeId()));
			
			try (CurrentModel cur2 = using(subgraph))
			{
				Variable v3 = real("v3");
				
				VariableBlock block3 = subgraph.addVariableBlock(v1,v3);
				assertBlock(block3, v1, v3);
			}
		}
		
	}
	
	private void assertBlock(VariableBlock block, Variable ... expectedVariables)
	{
		assertInvariants(block);
		
		final int expectedLength = expectedVariables.length;
		assertEquals(expectedLength, block.size());
		for (int i = expectedLength; --i>=0;)
		{
			assertSame(expectedVariables[i], block.get(i));
		}
	}
	
	private void assertInvariants(VariableBlock block)
	{
		int id = block.getLocalId();
		assertEquals(Ids.VARIABLE_BLOCK_TYPE, Ids.typeIndexFromLocalId(id));
		
		FactorGraph parent = block.getParentGraph();
		assertSame(block, parent.getVariableBlockByLocalId(id));
		assertSame(block, parent.getChildByGraphTreeId(block.getGraphTreeId()));
		assertTrue(parent.ownsDirectly(block));
		
		FactorGraph grandparent = parent.getParentGraph();
		if (grandparent != null)
		{
			assertNotSame(block, grandparent.getVariableBlockByLocalId(id));
			assertSame(block, grandparent.getChildByGraphTreeId(block.getGraphTreeId()));
		}
		
		/*
		 * Test IDimpleEventSource methods
		 */
		
		assertEquals(block.toString(), block.getEventSourceName());
		assertEquals(block.getParentGraph(), block.getModelEventSource());
		block.notifyListenerChanged(); // does nothing
		
		/*
		 * Test List invariants
		 */
		
		assertFalse(block.isEmpty());
		assertTrue(block.size() > 0);
		
		Object[] objs = block.toArray();
		Variable[] vars = block.toArray(new Variable[0]);
		assertEquals(block.size(), objs.length);
		assertArrayEquals(objs, vars);
		
		int i = -1;
		for (Variable var : block)
		{
			++i;
			assertSame(var, block.get(i));
			assertSame(var, vars[i]);
			assertEquals(var.getGraphTreeId(), block.getVariableGraphTreeId(i));
			
			assertEquals(i, block.indexOf(var));
			assertEquals(i, block.lastIndexOf(var));
			assertTrue(block.contains(var));
		}
		
		assertEquals(i+1, block.size());
		
		assertFalse(block.contains("foo"));
		assertEquals(-1, block.indexOf("bogus"));
		assertEquals(-1, block.indexOf(new Real()));
		assertEquals(-1, block.lastIndexOf("bogus"));
		assertEquals(-1, block.lastIndexOf(new Real()));
		
		assertTrue(block.containsAll(Collections.emptyList()));
		assertTrue(block.containsAll(Arrays.asList(vars)));
		assertFalse(block.containsAll(Arrays.asList("foo")));
		
		assertEquals(block.subList(0, block.size()), block);
		// VariableBlock.equals only true if other object is also a VariableBlock
		assertNotEquals(block, block.subList(0, block.size()));
	}
	
	@Test
	public void testErrors()
	{
		FactorGraph fg = new FactorGraph("fg");
		
		expectThrow(IllegalArgumentException.class, "Cannot create empty VariableBlock", fg, "addVariableBlock");
		
		Variable v1 = new Real();
		v1.setName("v1");
		expectThrow(IllegalArgumentException.class, "Variable 'v1' not in graph tree", fg, "addVariableBlock", v1);
		
		fg.addVariables(v1);
		expectThrow(IllegalArgumentException.class, "Variable 'v1' was specified more than once",
			fg, "addVariableBlock", v1, v1);
		
		FactorGraph fg2 = new FactorGraph("fg2");
		expectThrow(IllegalArgumentException.class, "Variable 'v1' not in graph tree", fg2, "addVariableBlock", v1);
		
		VariableBlock block = fg.addVariableBlock(v1);
		assertUnsupported(block, "add", v1);
		assertUnsupported(block, "add", 1, v1);
		assertUnsupported(block, "addAll", new ArrayList<Variable>());
		assertUnsupported(block, "addAll", 42, new ArrayList<Variable>());
		assertUnsupported(block, "clear");
		assertUnsupported(block, "remove", "foo");
		assertUnsupported(block, "remove", 42);
		assertUnsupported(block, "removeAll", Collections.emptyList());
		assertUnsupported(block, "retainAll", Collections.emptyList());
		assertUnsupported(block, "set", 42, v1);

		assertEquals(v1, block.get(0));
		fg.remove(v1);
		expectThrow(IllegalStateException.class, ".*no longer in graph", block, "get", 0);
	}
	
	private void assertUnsupported(VariableBlock block, String methodName, Object ... args)
	{
		expectThrow(UnsupportedOperationException.class, block, methodName, args);
	}
}
