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

import java.io.File;
import java.time.Instant;

import org.eclipse.hawk.core.IConsole;
import org.eclipse.hawk.core.ICredentialsStore;
import org.eclipse.hawk.core.IVcsManager;
import org.eclipse.hawk.core.VcsCommit;
import org.eclipse.hawk.core.VcsRepositoryDelta;
import org.eclipse.hawk.core.graph.IGraphTransaction;
import org.eclipse.hawk.core.graph.timeaware.ITimeAwareGraphDatabase;
import org.eclipse.hawk.core.runtime.BaseModelIndexer;
import org.eclipse.hawk.timeaware.graph.VCSManagerIndex.RepositoryNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Indexes all revisions of the models in the repository. Requires using
 * backends and locations that can handle full history (currently Greycat +
 * SVN).
 */
public class TimeAwareIndexer extends BaseModelIndexer {

	private static final Logger LOGGER = LoggerFactory.getLogger(TimeAwareIndexer.class);

	public TimeAwareIndexer(String name, File parentFolder, ICredentialsStore credStore, IConsole c) {
		super(name, parentFolder, credStore, c);
	}

	@Override
	protected void resetRepository(String repoURL) {
		// nothing to do - we keep track of repository information in the graph
	}

	@Override
	protected boolean synchronise(IVcsManager vcsManager) {
		if (!(graph instanceof ITimeAwareGraphDatabase)) {
			LOGGER.error("This indexer requires a time-aware backend, aborting");
			return false;
		}
		ITimeAwareGraphDatabase taGraph = (ITimeAwareGraphDatabase)graph;

		String lastRev;
		try {
			taGraph.setTime(0);
			lastRev = getLastIndexedRevision(vcsManager);
		} catch (Exception e) {
			LOGGER.error("Could not fetch the last indexed revision", e);
			return false;
		}

		try {
			final String currentRevision = vcsManager.getCurrentRevision();
			if (!currentRevision.equals(lastRev)) {
				latestUpdateFoundChanges = true;

				VcsRepositoryDelta delta = vcsManager.getDelta(lastRev, currentRevision);
				for (VcsCommit commit : delta.getCommits()) {
					// Commits might be only milliseconds apart in automated processes
					final Instant instant = commit.getJavaDate().toInstant();
					final long epochMillis = instant.getEpochSecond() * 1000 + instant.getNano() / 1_000_000;

					// Do not allow anyone else to change the time while we are manipulating the graph
					synchronized(taGraph) {
						taGraph.setTime(epochMillis);

						/*
						 * TODO: allow for fixing unresolved proxies in previous versions? Might make
						 * sense if we forgot to add a metamodel in a previous version.
						 */
						final boolean successfulRevision = synchroniseFiles(commit.getRevision(), vcsManager, commit.getItems());
						if (successfulRevision) {
							console.println(String.format("Indexed successfully revision %s (timepoint %d) of %s",
								commit.getRevision(), epochMillis, commit.getDelta().getManager().getLocation()));
						} else {
							console.printerrln(String.format("Failed to index revision %s (timepoint %d) of %s",
								commit.getRevision(), epochMillis, commit.getDelta().getManager().getLocation()));
							return false;
						}

						setIndexedRevision(vcsManager, commit);
					}
				}
			}
		} catch (Exception e) {
			LOGGER.error("Failed to synchronise repository " + vcsManager.getLocation(), e);
			return false;
		} finally {
			synchronized (taGraph) {
				taGraph.setTime(0);
			}
		}

		return true;
	}

	protected String getLastIndexedRevision(IVcsManager vcsManager) throws Exception {
		String lastRev;
		try (IGraphTransaction tx = graph.beginTransaction()) {
			VCSManagerIndex vcsIndex = new VCSManagerIndex((ITimeAwareGraphDatabase) graph);
			RepositoryNode repoNode = vcsIndex.getOrCreateRepositoryNode(vcsManager.getLocation());
			lastRev = repoNode.getLatest().getRevision();
			tx.success();
		}
		return lastRev;
	}

	protected void setIndexedRevision(IVcsManager vcsManager, VcsCommit commit) throws Exception {
		try (IGraphTransaction tx = graph.beginTransaction()) {
			VCSManagerIndex vcsIndex = new VCSManagerIndex((ITimeAwareGraphDatabase) graph);
			RepositoryNode repoNode = vcsIndex.getOrCreateRepositoryNode(vcsManager.getLocation());
			repoNode.setRevision(commit.getRevision());
			repoNode.setMessage(commit.getMessage());
			tx.success();
		}
	}
	
}
