/*******************************************************************************
 * Copyright (c) 2015-2020 The University of York, 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.integration.tests.emf;

import static org.junit.Assert.assertEquals;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;

import org.eclipse.hawk.backend.tests.factories.IGraphDatabaseFactory;
import org.eclipse.hawk.core.graph.IGraphTransaction;
import org.eclipse.hawk.graph.ModelElementNode;
import org.eclipse.hawk.graph.syncValidationListener.SyncValidationListener;
import org.eclipse.hawk.integration.tests.IntegrationTestSuite;
import org.eclipse.hawk.integration.tests.ModelIndexingTest;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runners.Parameterized.Parameters;

/**
 * Integration test case that indexes a tree model and changes it in some way.
 */
public class TreeUpdateTest extends ModelIndexingTest {

	@Rule
	public GraphChangeListenerRule<SyncValidationListener> syncValidation
		= new GraphChangeListenerRule<>(new SyncValidationListener());

	@Rule
	public TemporaryFolder modelFolder = new TemporaryFolder();

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

	public TreeUpdateTest(File baseDir, IGraphDatabaseFactory dbf) {
		super(baseDir, dbf, new EMFModelSupportFactory());
	}

	private Path modelPath;

	public void prepare(String baseModel) throws Throwable {
		modelPath = new File(modelFolder.getRoot(), new File(baseModel).getName()).toPath();
		Files.copy(new File(baseDir, "resources/models/" + baseModel).toPath(), modelPath);

		indexer.registerMetamodels(new File(baseDir, "resources/metamodels/Ecore.ecore"));
		indexer.registerMetamodels(new File(baseDir, "resources/metamodels/XMLType.ecore"));
		indexer.registerMetamodels(new File(baseDir, "resources/metamodels/Tree.ecore"));

		requestFolderIndex(modelFolder.getRoot());

		scheduleAndWait(() -> {
			assertNoErrors(syncValidation.getListener());
			return null;
		});
	}

	@Test
	public void eContainer() throws Throwable {
		prepare("tree/tree.model");

		assertEquals(null, eol("return Tree.all.selectOne(t|t.label='t3').eContainer;"));
		assertEquals("t3", eol("return Tree.all.selectOne(t|t.label='t9000').eContainer.label;"));
	}

	@Test
	public void addModel() throws Throwable {
		// First, set up a Hawk with a folder with no models
		indexer.registerMetamodels(new File(baseDir, "resources/metamodels/Ecore.ecore"));
		indexer.registerMetamodels(new File(baseDir, "resources/metamodels/XMLType.ecore"));
		indexer.registerMetamodels(new File(baseDir, "resources/metamodels/Tree.ecore"));
		requestFolderIndex(modelFolder.getRoot());
		scheduleAndWait(() -> {
			assertNoErrors(syncValidation.getListener());
			return null;
		});

		// Now, copy in the model
		String baseModel = "tree/tree.model";
		modelPath = new File(modelFolder.getRoot(), new File("tree/tree.model").getName()).toPath();
		Files.copy(new File(baseDir, "resources/models/" + baseModel).toPath(), modelPath);
		indexer.requestImmediateSync();
		scheduleAndWait(() -> {
			assertNoErrors(syncValidation.getListener());
			try (IGraphTransaction tx = db.beginTransaction()) {
				assertEquals(2, queryEngine.getAllOf("Tree", ModelElementNode.EDGE_LABEL_OFTYPE).size());
				tx.success();
			}
			return null;
		});
	}

	@Test
	public void addChild() throws Throwable {
		prepare("tree/tree.model");
		replaceWith("changed-trees/add-child.model");
		indexer.requestImmediateSync();
		scheduleAndWait(() -> {
			assertNoErrors(syncValidation.getListener());
			try (IGraphTransaction tx = db.beginTransaction()) {
				assertEquals(3, queryEngine.getAllOf("Tree", ModelElementNode.EDGE_LABEL_OFTYPE).size());
				tx.success();
			}
			return null;
		});
	}

	@Test
	public void removeChild() throws Throwable {
		prepare("tree/tree.model");
		replaceWith("changed-trees/remove-child.model");
		indexer.requestImmediateSync();
		scheduleAndWait(() -> {
			assertNoErrors(syncValidation.getListener());
			try (IGraphTransaction tx = db.beginTransaction()) {
				assertEquals(1, queryEngine.getAllOf("Tree", ModelElementNode.EDGE_LABEL_OFTYPE).size());
				tx.success();
			}
			return null;
		});
	}

	@Test
	public void renameChild() throws Throwable {
		prepare("tree/tree.model");
		replaceWith("changed-trees/rename-child.model");
		indexer.requestImmediateSync();
		scheduleAndWait(() -> {
			assertNoErrors(syncValidation.getListener());
			assertEquals(2, eol("return Tree.all.size;"));
			assertEquals(1, eol("return Tree.all.select(t|t.label='t90001').size;"));
			return null;
		});
	}

	@Test
	public void renameRoot() throws Throwable {
		prepare("tree/tree.model");
		replaceWith("changed-trees/rename-root.model");
		indexer.requestImmediateSync();
		scheduleAndWait(() -> {
			assertNoErrors(syncValidation.getListener());
			try (IGraphTransaction tx = db.beginTransaction()) {
				assertEquals(2, queryEngine.getAllOf("Tree", ModelElementNode.EDGE_LABEL_OFTYPE).size());
				assertEquals(1, queryEngine.query(indexer, "return Tree.all.select(t|t.label='t40').size;", null));
				tx.success();
			}
			return null;
		});
	}

	private void replaceWith(final String replacement) throws IOException {
		final File replacementFile = new File(baseDir, "resources/models/" + replacement);
		Files.copy(replacementFile.toPath(), modelPath,
				StandardCopyOption.REPLACE_EXISTING);
		System.err.println("Copied " + replacementFile + " over " + modelPath);
	}
}
