package fr.emse.gitlab.saref.jobs;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.PathMatcher;
import java.util.Arrays;
import java.util.Calendar;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import fr.emse.gitlab.saref.Constants;
import fr.emse.gitlab.saref.SAREFPipelineException;
import fr.emse.gitlab.saref.entities.git.Version;

public class CheckRepositoryStructure extends JobRunner {

	public static final PathMatcher ttlMatcher = FileSystems.getDefault().getPathMatcher("glob:**/*.ttl");

	private final Set<String> gitignoreLines = new HashSet<>(
			Arrays.asList("target", "*~", ".DS_Store", "catalog-v001.xml", "saref-pipeline.jar"));

	private Version version;
	private File directory;

	public CheckRepositoryStructure(String testSuiteName) {
		super(testSuiteName);
	}

	public void check(Version version) throws SAREFPipelineException {
		this.version = version;
		this.directory = version.getRepository().getDirectory();
		checkThereExistsDirectory("diagrams");
		checkThereExistsDirectory("ontology");
		String repoName = checkRepoName();
		if (repoName == null) {
			return;
		}
		String ontologyFile = repoName.equals("saref-core") ? "saref.ttl" : repoName + ".ttl";
		checkOntoloyFolder(ontologyFile);
		checkGitIgnoreFolder();
		checkLICENSE();
		checkExamplesFolder();

	}

	private void checkThereExistsDirectory(String dirName) throws SAREFPipelineException {
		final File dir = new File(directory, dirName);
		try {
			if (!dir.isDirectory()) {
				logger.warn(String.format("There should exist a folder named `%s`", dirName));
			}
		} catch (SecurityException ex) {
			logger.error(String.format("Error while checking if there exists a folder named `%s`", dirName), ex);
			throw new SAREFPipelineException(String.format("Error while checking if there exists a folder named `%s`", dirName), ex);
		}
	}

	private String checkRepoName() throws SAREFPipelineException {
		String REGEX = "(?<ext>saref-core|saref4[a-z][a-z][a-z][a-z])";
		String name = version.getRepositoryName();
		Pattern pattern = Pattern.compile(REGEX);
		Matcher matcher = pattern.matcher(name);
		if (!matcher.find()) {
			logger.error(String.format(
					"The repository directory should contain `saref-core`, or `saref4abcd` (where abcd can be any sequence of four letters). Got: %s",
					name));
			throw new SAREFPipelineException(String.format(
					"The repository directory should contain `saref-core`, or `saref4abcd` (where abcd can be any sequence of four letters). Got: %s",
					name));
		}
		return matcher.group("ext");
	}

	private void checkOntoloyFolder(String ontoName) throws SAREFPipelineException {
		final File ontoDir = new File(directory, "ontology");
		final File ontoFile = new File(ontoDir, ontoName);
		if (!ontoFile.isFile()) {
			logger.error(String.format("The ontology folder must contain an ontology file named `%s`", ontoName));
			throw new SAREFPipelineException(String.format("The ontology folder must contain an ontology file named `%s`", ontoName));
		}
	}

	private void checkGitIgnoreFolder() throws SAREFPipelineException {
		final File gitignore = new File(directory, ".gitignore");
		if (!gitignore.exists()) {
			logger.warn("The directory must contain a file named `.gitignore`");
			return;
		}
		try (BufferedReader br = Files.newBufferedReader(gitignore.toPath())) {
			for (String line = br.readLine(); line != null; line = br.readLine()) {
				gitignoreLines.remove(line);
			}
			if (!gitignoreLines.isEmpty()) {
				logger.warn(String.format("The file `.gitignore` lacks one line for each of the following strings: %s",
						gitignoreLines.toString()));
			}
		} catch (IOException ex) {
			logger.error("Error while checking if the file `.gitignore` contains the appropriate lines: " + gitignoreLines, ex);
			throw new SAREFPipelineException("Error while checking if the file `.gitignore` contains the appropriate lines: " + gitignoreLines, ex);
		}
	}

	private void checkLICENSE() throws SAREFPipelineException {
		final File licenseFile = new File(directory, "LICENSE");
		if (!licenseFile.exists()) {
			logger.warn("The directory must contain a file named `LICENSE`");
			return;
		}
		InputStream licenseModelFile = CheckRepositoryStructure.class.getClassLoader().getResourceAsStream("LICENSE_MODEL");
		try (BufferedReader licenseModelFileReader = new BufferedReader(new InputStreamReader(licenseModelFile));
				BufferedReader licenseFileReader = new BufferedReader(new FileReader(licenseFile));) {

			// first line
			String modelLine = licenseModelFileReader.readLine();
			String licenseLine = licenseFileReader.readLine();
			String regex = modelLine.replaceFirst("YYYY", "(?<year>\\\\d{4})");
			Pattern pattern = Pattern.compile(String.format("^%s$", regex));
			Matcher regexMatcher = pattern.matcher(licenseLine);
			if (!regexMatcher.find()) {
				logger.warn("First line of the `LICENSE` file must match the following string (see details below)",
						"First line of the `LICENSE` file must match the following string, where YYYY is a year. Got: "
								+ licenseLine,
						modelLine);
				return;
			}
			int year = Integer.parseInt(regexMatcher.group("year"));
			int currentYear = Calendar.getInstance().get(Calendar.YEAR);
			if (year < 2015 || year > currentYear) {
				logger.warn(String.format("The year in the `LICENSE` should be between %s and %s", 2015, currentYear));
				return;
			}

			int i = 1;
			modelLine = licenseModelFileReader.readLine();
			licenseLine = licenseFileReader.readLine();
			while (modelLine != null && licenseLine != null) {
				i++;
				if (!modelLine.equals(licenseLine)) {
					logger.warn(String.format("Line %s in the `LICENSE` should be `%s`", i, modelLine));
					return;
				}
				modelLine = licenseModelFileReader.readLine();
				licenseLine = licenseFileReader.readLine();
			}
			if (modelLine == null && licenseLine != null) {
				logger.warn(String.format("The license contains too many lines. Need %s", i));
				return;
			}
			if (modelLine != null && licenseLine == null) {
				logger.warn(String.format("The license is incomplete.", i));
				return;
			}
		} catch (Exception ex) {
			logger.error("Exception while creating the path for the LICENSE model", ex);
			throw new SAREFPipelineException("Exception while creating the path for the LICENSE model", ex);
		}
	}

	private void checkExamplesFolder() throws SAREFPipelineException {
		final File dir = new File(directory, "examples");
		try {
			if (!dir.isDirectory()) {
				logger.warn("There should exist a folder named `examples`");
				return;
			}
		} catch (SecurityException ex) {
			logger.error("Error while checking if there exists a folder named `examples`", ex);
			throw new SAREFPipelineException("Error while checking if there exists a folder named `examples`", ex);
		}
		try {
			boolean containsExample = Files.walk(dir.toPath())
					.anyMatch(p -> {
				return ttlMatcher.matches(p);
			});
			if (!containsExample) {
				logger.warn("The `examples` folder should contain at least one example file (some `.ttl` document)");
			}
		} catch (IOException ex) {
			logger.error("Exception while browsing the `examples` folder", ex);
			throw new SAREFPipelineException("Exception while browsing the `examples` folder", ex);
		}
	}

}
