package ca.corefacility.bioinformatics.irida.config.repository;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashSet;
import java.util.Set;

import javax.annotation.PreDestroy;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;

import ca.corefacility.bioinformatics.irida.model.project.ReferenceFile;
import ca.corefacility.bioinformatics.irida.model.sequenceFile.SequenceFile;
import ca.corefacility.bioinformatics.irida.model.sequenceFile.SequenceFileSnapshot;
import ca.corefacility.bioinformatics.irida.model.workflow.analysis.AnalysisOutputFile;
import ca.corefacility.bioinformatics.irida.repositories.filesystem.FilesystemSupplementedRepositoryImpl.RelativePathTranslatorListener;
import ca.corefacility.bioinformatics.irida.util.RecursiveDeleteVisitor;

@Configuration
public class IridaApiFilesystemRepositoryConfig {

	private static final Logger logger = LoggerFactory.getLogger(IridaApiFilesystemRepositoryConfig.class);

	private @Value("${sequence.file.base.directory}") String sequenceFileBaseDirectory;

	private @Value("${reference.file.base.directory}") String referenceFileBaseDirectory;

	private @Value("${output.file.base.directory}") String outputFileBaseDirectory;

	private @Value("${snapshot.file.base.directory}") String snapshotFileBaseDirectory;

	private static final Set<Path> BASE_DIRECTORIES = new HashSet<>();

	// Franklin: I assume that the scope of a configuration bean is the lifetime
	// of the application, so the directory should only get deleted *after* the
	// tests have finished running.
	@PreDestroy
	public void tearDown() throws IOException {
		for (Path b : BASE_DIRECTORIES) {
			Files.walkFileTree(b, new RecursiveDeleteVisitor());
		}
	}
	
	@Bean
	public RelativePathTranslatorListener relativePathTranslatorListener(final @Qualifier("referenceFileBaseDirectory") Path referenceFileBaseDirectory, 
			final @Qualifier("sequenceFileBaseDirectory") Path sequenceFileBaseDirectory,
			final @Qualifier("outputFileBaseDirectory") Path outputFileBaseDirectory,
			final @Qualifier("snapshotFileBaseDirectory") Path snapshotFileBaseDirectory) {
		RelativePathTranslatorListener.addBaseDirectory(SequenceFile.class, sequenceFileBaseDirectory);
		RelativePathTranslatorListener.addBaseDirectory(ReferenceFile.class, referenceFileBaseDirectory);
		RelativePathTranslatorListener.addBaseDirectory(AnalysisOutputFile.class, outputFileBaseDirectory);
		RelativePathTranslatorListener.addBaseDirectory(SequenceFileSnapshot.class, snapshotFileBaseDirectory);
		return new RelativePathTranslatorListener();
	}

	@Profile("prod")
	@Bean(name = "referenceFileBaseDirectory")
	public Path referenceFileBaseDirectoryProd() {
		return getExistingPathOrThrow(referenceFileBaseDirectory);
	}

	@Profile("prod")
	@Bean(name = "sequenceFileBaseDirectory")
	public Path sequenceFileBaseDirectoryProd() {
		return getExistingPathOrThrow(sequenceFileBaseDirectory);
	}

	@Profile("prod")
	@Bean(name = "outputFileBaseDirectory")
	public Path outputFileBaseDirectoryProd() {
		return getExistingPathOrThrow(outputFileBaseDirectory);
	}

	@Profile("prod")
	@Bean(name = "snapshotFileBaseDirectory")
	public Path snapshotFileBaseDirectoryProd() {
		return getExistingPathOrThrow(snapshotFileBaseDirectory);
	}

	@Profile({ "dev", "it", "test" })
	@Bean(name = "referenceFileBaseDirectory")
	public Path referenceFileBaseDirectory() throws IOException {
		return configureDirectory(referenceFileBaseDirectory, "reference-file-dev");
	}

	@Profile({ "dev", "it", "test" })
	@Bean(name = "sequenceFileBaseDirectory")
	public Path sequenceFileBaseDirectory() throws IOException {
		return configureDirectory(sequenceFileBaseDirectory, "sequence-file-dev");
	}

	@Profile({ "dev", "it", "test" })
	@Bean(name = "outputFileBaseDirectory")
	public Path outputFileBaseDirectory() throws IOException {
		return configureDirectory(outputFileBaseDirectory, "output-file-dev");
	}

	@Profile({ "dev", "it", "test" })
	@Bean(name = "snapshotFileBaseDirectory")
	public Path snapshotFileBaseDirectory() throws IOException {
		return configureDirectory(snapshotFileBaseDirectory, "snapshot-file-dev");
	}
	
	private Path getExistingPathOrThrow(String directory) {
		Path baseDirectory = Paths.get(directory);
		if (!Files.exists(baseDirectory)) {
			throw new IllegalStateException(String.format(
					"Cannot continue startup; base directory [%s] does not exist!", baseDirectory.toString()));
		} else {
			logger.info(String
					.format("Using specified existing directory at [%s]. The directory *will not* be removed at shutdown time.",
							baseDirectory.toString()));
		}
		return baseDirectory;
	}

	private Path configureDirectory(String pathName, String defaultDevPathPrefix) throws IOException {
		Path baseDirectory = Paths.get(pathName);
		if (!Files.exists(baseDirectory)) {
			baseDirectory = Files.createDirectories(baseDirectory);
			BASE_DIRECTORIES.add(baseDirectory);
			logger.info(String
					.format("The directory [%s] does not exist, but it looks like you're running in a dev environment, "
							+ "so I created a temporary location at [%s]. This directory *will* be removed at shutdown time.",
							pathName, baseDirectory.toString()));
		}
		return baseDirectory;
	}
}

@@ -1683,16 +1683,26 @@
 t%3CPath%3E 
+TEMPORARY_
 BASE_DIR
@@ -1734,196 +1734,8 @@
 );%0A%0A
-%09// Franklin: I assume that the scope of a configuration bean is the lifetime%0A%09// of the application, so the directory should only get deleted *after* the%0A%09// tests have finished running.%0A
 %09@Pr
@@ -1804,16 +1804,26 @@
 ath b : 
+TEMPORARY_
 BASE_DIR
@@ -5066,16 +5066,26 @@
 ry);%0A%09%09%09
+TEMPORARY_
 BASE_DIR

Rename BASE_DIRECTORIES to TEMPORARY_BASE_DIRECTORIES to avoid confusion of deletion on shut down.