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

import static java.util.Objects.*;
import static org.junit.Assert.*;

import java.util.ArrayDeque;
import java.util.Queue;

import org.junit.Test;

import com.analog.lyric.dimple.events.DimpleEventHandler;
import com.analog.lyric.dimple.events.DimpleEventListener;
import com.analog.lyric.dimple.events.FactorGraphEvent;
import com.analog.lyric.dimple.events.FactorGraphFactorEvent;
import com.analog.lyric.dimple.events.FactorGraphSubgraphEvent;
import com.analog.lyric.dimple.events.FactorGraphVariableEvent;
import com.analog.lyric.dimple.factorfunctions.Normal;
import com.analog.lyric.dimple.model.core.BoundaryVariableAddEvent;
import com.analog.lyric.dimple.model.core.BoundaryVariableRemoveEvent;
import com.analog.lyric.dimple.model.core.FactorAddEvent;
import com.analog.lyric.dimple.model.core.FactorGraph;
import com.analog.lyric.dimple.model.core.FactorRemoveEvent;
import com.analog.lyric.dimple.model.core.Node;
import com.analog.lyric.dimple.model.core.SubgraphAddEvent;
import com.analog.lyric.dimple.model.core.SubgraphRemoveEvent;
import com.analog.lyric.dimple.model.core.VariableAddEvent;
import com.analog.lyric.dimple.model.core.VariableRemoveEvent;
import com.analog.lyric.dimple.model.factors.Factor;
import com.analog.lyric.dimple.model.variables.Real;
import com.analog.lyric.dimple.model.variables.Variable;
import com.analog.lyric.dimple.test.DimpleTestBase;
import com.analog.lyric.util.test.SerializationTester;

/**
 * Tests for {@link FactorGraphEvent} generation.
 * 
 * @since 0.06
 * @author Christopher Barber
 */
public class TestFactorGraphEvent extends DimpleTestBase
{
	@Test
	public void test()
	{
		final FactorGraphEventHandler handler = new FactorGraphEventHandler();
		final DimpleEventListener listener = new DimpleEventListener();
		
		FactorGraph root = new FactorGraph();
		root.setName("root");
		
		// Initially listen for all factor graph events
		root.getEnvironment().setEventListener(listener);
		assertFalse(listener.isListeningFor(VariableAddEvent.class, root));
		assertFalse(listener.isListeningFor(VariableRemoveEvent.class, root));
		assertFalse(listener.isListeningFor(BoundaryVariableAddEvent.class, root));
		assertFalse(listener.isListeningFor(BoundaryVariableRemoveEvent.class, root));
		assertFalse(listener.isListeningFor(FactorAddEvent.class, root));
		assertFalse(listener.isListeningFor(FactorRemoveEvent.class, root));
		assertFalse(listener.isListeningFor(SubgraphAddEvent.class, root));
		assertFalse(listener.isListeningFor(SubgraphRemoveEvent.class, root));
		listener.register(handler, FactorGraphEvent.class, true, root);
		root.notifyListenerChanged();
		assertTrue(listener.isListeningFor(VariableAddEvent.class, root));
		assertTrue(listener.isListeningFor(VariableRemoveEvent.class, root));
		assertTrue(listener.isListeningFor(BoundaryVariableAddEvent.class, root));
		assertTrue(listener.isListeningFor(BoundaryVariableRemoveEvent.class, root));
		assertTrue(listener.isListeningFor(FactorAddEvent.class, root));
		assertTrue(listener.isListeningFor(FactorRemoveEvent.class, root));
		assertTrue(listener.isListeningFor(SubgraphAddEvent.class, root));
		assertTrue(listener.isListeningFor(SubgraphRemoveEvent.class, root));
		
		handler.assertNoEvents();
		
		Real a = new Real();
		a.setName("a");
		Real b = new Real();
		b.setName("b");
		root.addVariables(a, b);
		
		VariableAddEvent varAddEvent = handler.assertEvent(VariableAddEvent.class, root, a);
		assertFalse(varAddEvent.wasAbsorbedFromSubgraph());
		handler.assertEvent(VariableAddEvent.class, root, b);
		handler.assertNoEvents();

		Real c = new Real();
		c.setName("c");
		Factor f1 = root.addFactor(new Normal(0.0,  1.0), a, b, c);
		f1.setName("f1");
		varAddEvent = handler.assertEvent(VariableAddEvent.class, root, c);
		assertFalse(varAddEvent.wasAbsorbedFromSubgraph());
		FactorAddEvent factorAddEvent = handler.assertEvent(FactorAddEvent.class, root, f1);
		assertFalse(factorAddEvent.wasAbsorbedFromSubgraph());
		handler.assertNoEvents();
		
		FactorGraph templateGraph = new FactorGraph();
		templateGraph.setName("template");
		Real tx = new Real();
		tx.setName("x");
		Real ty = new Real();
		ty.setName("y");
		Real tz = new Real();
		tz.setName("z");
		templateGraph.addBoundaryVariables(tz);
		Factor tf = templateGraph.addFactor(new Normal(2.0, 2.0), tx ,ty, tz);
		tf.setName("f2");
		
		FactorGraph subgraph = root.addGraph(templateGraph, a);
		SubgraphAddEvent subgraphAddEvent = handler.assertEvent(SubgraphAddEvent.class, root, subgraph);
		assertFalse(subgraphAddEvent.wasAbsorbedFromSubgraph());
		handler.assertNoEvents();
		
		Variable x = subgraph.getVariableByName("x");
		assertNotNull(x);
		Variable y = subgraph.getVariableByName("y");
		assertNotNull(y);
		Variable z = subgraph.getVariableByName("z");
		assertNull(z); // boundary variable from template is not instantiated in generated graph

		Factor f2 = subgraph.getFactorByName("f2");
		assertNotNull(f2);
		
		root.absorbSubgraph(subgraph);
		varAddEvent = handler.assertEvent(VariableAddEvent.class, root, x);
		assertTrue(varAddEvent.wasAbsorbedFromSubgraph());
		handler.assertEvent(VariableAddEvent.class, root, y);
		factorAddEvent = handler.assertEvent(FactorAddEvent.class, root, f2);
		assertTrue(factorAddEvent.wasAbsorbedFromSubgraph());
		handler.assertEvent(SubgraphRemoveEvent.class, root, subgraph);
		handler.assertNoEvents();
		
		root.remove(f1);
		handler.assertEvent(FactorRemoveEvent.class, root, f1);
		handler.assertNoEvents();
		
		root.remove(b);
		handler.assertEvent(VariableRemoveEvent.class, root, b);
		handler.assertNoEvents();
		
		root.addBoundaryVariables(b);
		handler.assertEvent(VariableAddEvent.class, root, b);
		handler.assertEvent(BoundaryVariableAddEvent.class, root, b);
		handler.assertNoEvents();
		
		// FIXME: no BoundaryVariableRemoveEvent yet - see BUG 84
		
		//
		// Test individual listener flags
		//
		
		listener.unregisterAll();
		root.notifyListenerChanged();
		assertFalse(listener.isListeningFor(VariableAddEvent.class, root));
		assertFalse(listener.isListeningFor(VariableRemoveEvent.class, root));
		assertFalse(listener.isListeningFor(BoundaryVariableAddEvent.class, root));
		assertFalse(listener.isListeningFor(BoundaryVariableRemoveEvent.class, root));
		assertFalse(listener.isListeningFor(FactorAddEvent.class, root));
		assertFalse(listener.isListeningFor(FactorRemoveEvent.class, root));
		assertFalse(listener.isListeningFor(SubgraphAddEvent.class, root));
		assertFalse(listener.isListeningFor(SubgraphRemoveEvent.class, root));

		root.remove(c);
		root.addVariables(c);
		assertSame(c, root.getVariableByName("c"));
		handler.assertNoEvents();
	
		listener.register(handler, VariableRemoveEvent.class, false, root);
		assertTrue(requireNonNull(root.getEventListener()).isListeningFor(VariableRemoveEvent.class, root));
		root.notifyListenerChanged();
		root.remove(c);
		handler.assertEvent(VariableRemoveEvent.class, root, c);
		handler.assertNoEvents();
		
		listener.unregisterAll();
		listener.register(handler, VariableAddEvent.class, false, root);
		root.notifyListenerChanged();
		root.addVariables(c);
		handler.assertEvent(VariableAddEvent.class, root, c);
		handler.assertNoEvents();
		
		Factor f3 = root.addFactor(new Normal(), a, b, c);
		root.remove(f3);
		handler.assertNoEvents();
		
		listener.unregisterAll();
		listener.register(handler, FactorAddEvent.class, false, root);
		root.notifyListenerChanged();
		f3 = root.addFactor(new Normal(), a, b, c);
		handler.assertEvent(FactorAddEvent.class, root, f3);
		handler.assertNoEvents();
		
		listener.unregisterAll();
		listener.register(handler, FactorRemoveEvent.class, false, root);
		root.notifyListenerChanged();
		root.remove(f3);
		handler.assertEvent(FactorRemoveEvent.class, root, f3);
		handler.assertNoEvents();
	}

	private static class FactorGraphEventHandler extends DimpleEventHandler<FactorGraphEvent>
	{
		Queue<FactorGraphEvent> _queue = new ArrayDeque<FactorGraphEvent>();
		
		@Override
		public void handleEvent(FactorGraphEvent event)
		{
			_queue.add(event);
			
			assertSame(event.getModelObject(), event.getSource());
			assertNotNull(event.getSource());
		}
		
		private <Event extends FactorGraphEvent> Event assertEvent(Class<Event> eventType, FactorGraph source, Node node)
		{
			@SuppressWarnings("unchecked")
			Event event = (Event) _queue.remove();
			assertTrue(eventType.isAssignableFrom(event.getClass()));
			assertSame(source, event.getSource());
			
			if (event instanceof FactorGraphFactorEvent)
			{
				FactorGraphFactorEvent factorEvent = (FactorGraphFactorEvent)event;
				assertSame(node, factorEvent.getFactor());

				FactorGraphFactorEvent factorEvent2 = SerializationTester.clone(factorEvent);
				assertNotSame(factorEvent2, factorEvent);
				assertNull(factorEvent2.getFactor());
				assertEquals(factorEvent.getFactorName(), factorEvent2.getFactorName());
			}
			else if (event instanceof FactorGraphVariableEvent)
			{
				FactorGraphVariableEvent variableEvent = (FactorGraphVariableEvent)event;
				assertSame(node, variableEvent.getVariable());
				
				FactorGraphVariableEvent variableEvent2 = SerializationTester.clone(variableEvent);
				assertNotSame(variableEvent2, variableEvent);
				assertNull(variableEvent2.getVariable());
				assertEquals(variableEvent.getVariableName(), variableEvent2.getVariableName());
			}
			else if (event instanceof FactorGraphSubgraphEvent)
			{
				FactorGraphSubgraphEvent subgraphEvent = (FactorGraphSubgraphEvent)event;
				assertSame(node, subgraphEvent.getSubgraph());

				FactorGraphSubgraphEvent subgraphEvent2 = SerializationTester.clone(subgraphEvent);
				assertNotSame(subgraphEvent2, subgraphEvent);
				assertNull(subgraphEvent2.getSubgraph());
				assertEquals(subgraphEvent.getSubgraphName(), subgraphEvent2.getSubgraphName());
			}
			
			return event;
		}
		
		private void assertNoEvents()
		{
			assertEquals(null, _queue.peek());
		}
	}
}
