/*******************************************************************************
 * Copyright (c) 2018-2019 Aston University.
 * 
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the Eclipse
 * Public License, v. 2.0 are satisfied: GNU General Public License, version 3.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-3.0
 *
 * Contributors:
 *     Antonio Garcia-Dominguez - initial API and implementation
 ******************************************************************************/
package org.eclipse.hawk.timeaware.tests;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import java.io.File;
import java.io.IOException;

import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.hawk.backend.tests.factories.IGraphDatabaseFactory;
import org.eclipse.hawk.epsilon.emc.EOLQueryEngine;
import org.eclipse.hawk.integration.tests.emf.EMFModelSupportFactory;
import org.eclipse.hawk.integration.tests.mm.Tree.Tree;
import org.eclipse.hawk.integration.tests.mm.Tree.TreeFactory;
import org.eclipse.hawk.integration.tests.mm.Tree.TreePackage;
import org.eclipse.hawk.svn.tests.rules.TemporarySVNRepository;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.tmatesoft.svn.core.SVNException;

/**
 * Tests for derived attributes over the history of a node.
 */
@RunWith(Parameterized.class)
public class DerivedAttributeHistoryTest extends AbstractTimeAwareModelIndexingTest {
	@Rule
	public TemporarySVNRepository svnRepository = new TemporarySVNRepository();
	private Resource rTree;

	@Parameters(name = "{1}")
	public static Iterable<Object[]> params() {
		return TimeAwareTestSuite.caseParams();
	}

	public DerivedAttributeHistoryTest(File baseDir, IGraphDatabaseFactory dbFactory) {
		super(baseDir, dbFactory, new EMFModelSupportFactory());
	}

	@Override
	protected void setUpMetamodels() throws Exception {
		indexer.registerMetamodels(new File(baseDir, TREE_MM_PATH));
	}

	@Test
	public void computedForAllVersions() throws Throwable {
		oneDerivedAttribute();
		requestSVNIndex(svnRepository);
		scheduleAndWait(() -> {
			// .all works on revision 0
			assertTrue((boolean)
				timeAwareEOL("return Tree.latest.all.first.always(v|v.Important.isDefined());"));
			assertTrue((boolean)
					timeAwareEOL("return Tree.latest.all.first.earliest.Important;"));
			assertFalse((boolean)
					timeAwareEOL("return Tree.latest.all.first.latest.Important;"));
		
			assertEquals(1,
				timeAwareEOL("return Tree.latest.all.first.whenAnnotated('Important').versions.size;"));
		
			return null;
		});
	}

	@Test
	public void whenComposability() throws Throwable {
		twoDerivedAttributeRepository();

		scheduleAndWait(() -> {
			// whenAnnotated
			assertEquals(2,
				timeAwareEOL("return Tree.earliest.next.all.first.whenAnnotated('Important').versions.size;"));
			assertEquals(2,
					timeAwareEOL("return Tree.earliest.next.all.first.whenAnnotated('HasChildren').versions.size;"));
			assertEquals(1,
					timeAwareEOL("return Tree.earliest.next.all.first.whenAnnotated('Important').whenAnnotated('HasChildren').versions.size;"));
			assertEquals(1,
					timeAwareEOL("return Tree.earliest.next.all.first.whenAnnotated('HasChildren').whenAnnotated('Important').versions.size;"));
		
			return null;
		});
	}

	@Test
	public void sinceAnnotated() throws Throwable {
		twoDerivedAttributeRepository();

		scheduleAndWait(() -> {
			assertEquals(4, timeAwareEOL("return Tree.earliest.next.all.first.earliest.sinceAnnotated('Important').versions.size;"));
			assertEquals(1, timeAwareEOL("return Tree.earliest.next.all.first.earliest.next.sinceAnnotated('Important').versions.size;"));
			assertEquals(2, timeAwareEOL("return Tree.earliest.next.all.first.earliest.sinceAnnotated('HasChildren').versions.size;"));
		
			return null;
		});
	}

	@Test
	public void afterAnnotated() throws Throwable {
		twoDerivedAttributeRepository();

		scheduleAndWait(() -> {
			assertEquals(3, timeAwareEOL("return Tree.earliest.next.all.first.earliest.afterAnnotated('Important').versions.size;"));
			assertFalse((boolean) timeAwareEOL("return Tree.earliest.next.all.first.earliest.next.afterAnnotated('Important').isDefined();"));
			assertEquals(1, timeAwareEOL("return Tree.earliest.next.all.first.earliest.afterAnnotated('HasChildren').versions.size;"));
		
			return null;
		});
	}

	@Test
	public void untilAnnotated() throws Throwable {
		twoDerivedAttributeRepository();

		scheduleAndWait(() -> {
			assertEquals(1, timeAwareEOL("return Tree.earliest.next.all.first.earliest.untilAnnotated('Important').versions.size;"));
			assertEquals(3, timeAwareEOL("return Tree.earliest.next.all.first.earliest.next.sinceThen.untilAnnotated('Important').versions.size;"));
			assertEquals(3, timeAwareEOL("return Tree.earliest.next.all.first.earliest.untilAnnotated('HasChildren').versions.size;"));
		
			assertEquals(1, timeAwareEOL("return Tree.earliest.next.all.first.earliest.untilAnnotated('HasChildren').untilAnnotated('Important').versions.size;"));
			assertFalse((boolean) timeAwareEOL("return Tree.earliest.next.all.first.earliest.untilAnnotated('Important').untilAnnotated('HasChildren').isDefined();"));
		
			return null;
		});
	}

	@Test
	public void beforeAnnotated() throws Throwable {
		twoDerivedAttributeRepository();

		scheduleAndWait(() -> {
			assertFalse((boolean) timeAwareEOL("return Tree.earliest.next.all.first.earliest.beforeAnnotated('Important').isDefined();"));
			assertEquals(2, timeAwareEOL("return Tree.earliest.next.all.first.earliest.next.sinceThen.beforeAnnotated('Important').versions.size;"));
			assertEquals(2, timeAwareEOL("return Tree.earliest.next.all.first.earliest.beforeAnnotated('HasChildren').versions.size;"));
			return null;
		});
	}

	@Test
	public void revRefNavDerived() throws Throwable {
		indexer.getMetaModelUpdater().addDerivedAttribute(
			TreePackage.eNS_URI, "Tree", "parentLabel", "String",
			false, false, false,
			EOLQueryEngine.TYPE,
			"var parent = self.revRefNav_children.first; "
			+ "if (parent.isDefined()) return parent.label; else return '';",
			indexer
		);

		parentLabelRepository();

		scheduleAndWait(() -> {
			assertEquals("Root2", timeAwareEOL("return Tree.latest.all.selectOne(l|l.latest.label='Child').latest.parentLabel;"));
			assertEquals(3, timeAwareEOL("return Tree.latest.all.selectOne(l|l.latest.label='Child').versions.size;"));
			return null;
		});
	}

	@Test
	public void eContainerDerived() throws Throwable {
		indexer.getMetaModelUpdater().addDerivedAttribute(
			TreePackage.eNS_URI, "Tree", "parentLabel", "String",
			false, false, false,
			EOLQueryEngine.TYPE,
			"var parent = self.eContainer; "
			+ "if (parent.isDefined()) return parent.label; else return '';",
			indexer
		);

		parentLabelRepository();

		scheduleAndWait(() -> {
			assertEquals("Root2", timeAwareEOL("return Tree.latest.all.selectOne(l|l.latest.label='Child').latest.parentLabel;"));
			assertEquals(3, timeAwareEOL("return Tree.latest.all.selectOne(l|l.latest.label='Child').versions.size;"));
			return null;
		});
	}

	private void parentLabelRepository() throws Exception {
		final File fTree = new File(svnRepository.getCheckoutDirectory(), "root.xmi");
		rTree = rsTree.createResource(URI.createFileURI(fTree.getAbsolutePath()));
	
		Tree root = treeFactory.createTree();
		root.setLabel("Root");
		rTree.getContents().add(root);
		rTree.save(null);
		svnRepository.add(fTree);
		svnRepository.commit("First commit");
	
		Tree child = treeFactory.createTree();
		child.setLabel("Child");
		root.getChildren().add(child);
		rTree.save(null);
		svnRepository.commit("Second commit - add child");
	
		root.setLabel("Root1");
		rTree.save(null);
		svnRepository.commit("Third commit - change label 1");
	
		root.setLabel("Root2");
		rTree.save(null);
		svnRepository.commit("Third commit - change label 2");
	
		requestSVNIndex(svnRepository);
	}

	private void twoDerivedAttributeRepository() throws Exception, IOException, SVNException {
		indexer.getMetaModelUpdater().addDerivedAttribute(
			TreePackage.eNS_URI, "Tree", "HasChildren", "Boolean",
			false, false, false,
			EOLQueryEngine.TYPE,
			"return self.children.size > 0;",
			indexer
		);

		Tree tRoot = oneDerivedAttribute();
		Tree tChild = TreeFactory.eINSTANCE.createTree();
		tChild.setLabel("Child");
		tRoot.getChildren().add(tChild);
		rTree.save(null);
		svnRepository.commit("Add child node");

		tRoot.setLabel("NowYouSeeMe");
		rTree.save(null);
		svnRepository.commit("Change label back");

		requestSVNIndex(svnRepository);
	}
	
	private Tree oneDerivedAttribute() throws Exception {
		indexer.getMetaModelUpdater().addDerivedAttribute(
			TreePackage.eNS_URI, "Tree", "Important", "Boolean",
			false, false, false,
			EOLQueryEngine.TYPE,
			"return self.label = 'NowYouSeeMe';",
			indexer
		);

		final File fTree = new File(svnRepository.getCheckoutDirectory(), "root.xmi");
		rTree = rsTree.createResource(URI.createFileURI(fTree.getAbsolutePath()));

		Tree t = treeFactory.createTree();
		t.setLabel("NowYouSeeMe");
		rTree.getContents().add(t);
		rTree.save(null);
		svnRepository.add(fTree);
		svnRepository.commit("First commit");

		t.setLabel("NowYouDoNot");
		rTree.save(null);
		svnRepository.commit("Second commit - change label");

		return t;
	}

}
