/*
 * Copyright 2020 ETSI
 * 
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions are met:
 * 1. Redistributions of source code must retain the above copyright notice, 
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice, 
 *    this list of conditions and the following disclaimer in the documentation 
 *    and/or other materials provided with the distribution.
 * 3. Neither the name of the copyright holder nor the names of its contributors 
 *    may be used to endorse or promote products derived from this software without 
 *    specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package fr.mines_stetienne.ci.saref.managers;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.jena.rdf.model.Model;
import org.apache.jena.rdf.model.Resource;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.lib.Ref;
import org.slf4j.Logger;

import fr.mines_stetienne.ci.saref.SAREF;
import fr.mines_stetienne.ci.saref.SAREFErrorLogger;
import fr.mines_stetienne.ci.saref.SAREFPipeline;
import fr.mines_stetienne.ci.saref.SAREFPipelineException;
import fr.mines_stetienne.ci.saref.entities.SAREFExample;
import fr.mines_stetienne.ci.saref.entities.SAREFProject;
import fr.mines_stetienne.ci.saref.entities.SAREFRepository;
import fr.mines_stetienne.ci.saref.entities.SAREFTerm;
import fr.mines_stetienne.ci.saref.entities.SAREFVersion;
import fr.mines_stetienne.ci.saref.entities.SAREFVersionName;
import fr.mines_stetienne.ci.saref.utils.Languages;

public class RepositoryManager extends SAREFErrorLogger {

	private static enum MESSAGE {
		reset, checkout, turtle, ioexception, load_exception;
	}

	private final SAREFRepository repository;
	private String currentBranch;
	private SAREFVersion currentVersion;
	private SAREFVersionName currentVersionName;
	private final boolean isTarget;

	public RepositoryManager(SAREFPipeline pipeline, Logger errorLogger, SAREFRepository repository, boolean isTarget) {
		super(pipeline, errorLogger);
		this.repository = repository;
		this.currentBranch = repository.getOriginalBranch();
		this.currentVersionName = repository.getOriginalVersion();
		this.currentVersion = repository.getVersion(currentVersionName);
		this.isTarget = isTarget;
	}
	
	public SAREFRepository getRepository() {
		return repository;
	}

	public String getCurrentBranch() {
		return currentBranch;
	}
	
	public SAREFVersion getCurrentVersion() {
		return currentVersion;
	}

	public SAREFVersionName getCurrentVersionName() {
		return currentVersionName;
	}

	public boolean isTarget() {
		return isTarget;
	}
	
	public void checkoutVersion(SAREFVersionName versionName) throws SAREFPipelineException {
		if(isTarget) {
			// do not mess with branches for the target repository
			return;
		}
		SAREFVersion version = repository.getVersions().get(versionName);
		Ref ref = sourcesManager.getRef(version);
		if(ref == null) {
			throw new SAREFPipelineException();
		}
		try (Git git = Git.open(repository.getDirectory())) {
			git.checkout().setName(ref.getName()).call();
			currentBranch = ref.getName();
			currentVersion = version;
			currentVersionName = versionName;
		} catch (IOException | GitAPIException ex) {
			String msg = getMessage(MESSAGE.checkout, repository.getProject().getName(), versionName);
			logError(msg, ex);
			throw new SAREFPipelineException(msg, ex);
		}
	}
	
	public void resetCheckout() throws SAREFPipelineException {
		if(isTarget || !repository.isClean()) {
			return;
		}
		String originalBranch = repository.getOriginalBranch();
		if (originalBranch == null) {
			return;
		}
		try (Git git = Git.open(repository.getDirectory())) {
			git.checkout().setName(originalBranch).call();
		} catch (IOException | GitAPIException ex) {
			String msg = getMessage(MESSAGE.reset, originalBranch);
			logError(msg, ex);
			throw new SAREFPipelineException(msg, ex);
		}
	}

	@Override
	public String toString() {
		return "RepositoryManager " + repository.getProject();
	}
	
	
	public void loadRepository() {
		if (isTarget || !repository.isClean()) {
			SAREFVersion originalVersion = repository.getVersions().get(repository.getOriginalVersion());
			loadVersion(originalVersion);
		} else {
			try (Git git = Git.open(repository.getDirectory())) {
				loadVersions(git, repository);
			} catch (IOException | GitAPIException e) {
				logWarning(getMessage(MESSAGE.load_exception, repository.getName()));
			}
		}
	}	

	private void loadVersions(Git git, SAREFRepository repository) throws GitAPIException {
		for (SAREFVersion version : repository.getVersions().values()) {
			Ref ref = sourcesManager.getRef(version);
			if(ref == null) {
				continue;
			}
			git.checkout().setName(ref.getName()).call();
			loadVersion(version);
		}
		git.checkout().setName(repository.getOriginalBranch()).call();
	}

	private void loadVersion(SAREFVersion version) {
		SAREFRepository repository = version.getRepository();
		SAREFProject project = repository.getProject();
		File directory = version.getRepository().getDirectory();
		File ontologyDirectory = new File(directory, "ontology");
		File ontologyFile = new File(ontologyDirectory, project.getOntologyFileName(Languages.TEXT_TURTLE));
		
		final String repositoryName = repository.getName();
		final String versionName;
		if(SAREFVersionName.DEFAULT.equals(version.getVersionName())) {
			versionName = "SNAPSHOT";
		} else {
			versionName = version.getVersionName().toString();
		}
		SAREF.loadModel(version, ontologyFile, pipeline.getLogger(SAREF.getMessage("clause", repositoryName, versionName, "9.4.1")));
		forEachTerm(version.getModel(), (term) -> {
			if(term.getProject().equals(project)) {
				version.definesTerm(term);
				term.isDefinedBy(version);
			} else {
				version.usesTerm(term);
				term.isUsedBy(version);
			}
		});
		File examplesDirectory = new File(directory, "examples");
		try {
			Files.walk(examplesDirectory.toPath(), 1).filter(p -> {
				return SAREF.TTL_MATCHER.matches(p);
			}).forEach(p -> {
				SAREFExample example = new SAREFExample(version, p);
				File exampleFile = p.toFile();			
				SAREF.loadModel(example, exampleFile, pipeline.getLogger(SAREF.getMessage("clause_for", repositoryName, versionName, exampleFile, "9.6.1")));
				version.getExamples().put(example.getName(), example);
				forEachTerm(example.getModel(), (term) -> {
					example.exemplifiesTerm(term);
					term.isExemplifiedBy(example);
				});
			});
		} catch (IOException e) {
			logWarning(getMessage(MESSAGE.ioexception, repositoryName, versionName));
		}
	}

	private void forEachTerm(Model model, Consumer<SAREFTerm> consumer) {
		model.listStatements().forEachRemaining(stmt -> {
			Resource s = stmt.getSubject();
			Resource p = stmt.getPredicate();
			Resource o = stmt.getObject().isResource() ? (Resource) stmt.getObject() : null;
			forEachTerm(s, consumer);
			forEachTerm(p, consumer);
			forEachTerm(o, consumer);
		});
	}

	private void forEachTerm(Resource t, Consumer<SAREFTerm> consumer) {
		if (t == null || !t.isURIResource()) {
			return;
		}
		String iri = t.getURI();
		final Matcher matcher = Pattern.compile(SAREF.REGEX_TERM).matcher(iri);
		if (!matcher.find()) {
			return;
		}
		RepositoryManager otherRepositoryManager = sourcesManager.findRepositoryManager(iri);
		if (otherRepositoryManager == null) {
			sourcesManager.addNoRepositoryManager(t);
			return;
		}
		SAREFRepository otherRepository = otherRepositoryManager.getRepository();
		SAREFTerm term = otherRepository.getTerms().get(iri);
		if (term == null) {
			term = new SAREFTerm(otherRepository, iri);
			otherRepository.getTerms().put(iri, term);
		}
		consumer.accept(term);
	}


}
