package de.hft.stuttgart.citydoctor.build.plugin;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import org.apache.maven.plugin.logging.Log;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.rauschig.jarchivelib.ArchiveFormat;
import org.rauschig.jarchivelib.Archiver;
import org.rauschig.jarchivelib.ArchiverFactory;
import org.rauschig.jarchivelib.CompressionType;

@Mojo(name = "downloadJre", defaultPhase = LifecyclePhase.INSTALL)
public class DownloadJreMojo implements org.apache.maven.plugin.Mojo {

	private Log log;

	@Parameter(property = "url", required = true, readonly = true)
	private URL url;

	@Parameter(property = "outputFolder", defaultValue = "jre")
	private String outputFolder;

	@Parameter(defaultValue = "${project}", required = true, readonly = true)
	MavenProject project;

	@Override
	public void execute() {
		Path targetDir = Paths.get(project.getBuild().getDirectory());
		Path outputDir = targetDir.resolve(outputFolder);
		if (Files.exists(outputDir)) {
			log.info("Skipping jre download, folder already exists");
			return;
		}
		outputDir.normalize();
		createOutputFolder(outputDir);
		log.info("Downloading runtime");
		if (url.getFile().endsWith(".tar")) {
			// handle tar file
			extractTarFile(outputDir);
		} else if (url.getFile().endsWith(".tar.gz")) {
			// handle tar file with gzip compression
			extractTarGz(outputDir);
		} else if (url.getFile().endsWith(".zip")) {
			// handle zip archive
			extractZipFile(outputDir);
		}
	}

	private void extractTarGz(Path outputDir) {
		Archiver archiver = ArchiverFactory.createArchiver(ArchiveFormat.TAR, CompressionType.GZIP);
		try {
			archiver.extract(url.openConnection().getInputStream(), outputDir.toFile());
		} catch (IOException e) {
			log.error("Could not extract file: " + url, e);
		}
	}

	private void extractTarFile(Path outputDir) {
		Archiver archiver = ArchiverFactory.createArchiver(ArchiveFormat.TAR);
		try {
			archiver.extract(url.openConnection().getInputStream(), outputDir.toFile());
		} catch (IOException e) {
			log.error("Could not extract file: " + url, e);
		}
	}

	private void extractZipFile(Path outputDir) {
		try (ZipInputStream zipInput = new ZipInputStream(url.openConnection().getInputStream())) {
			ZipEntry entry = zipInput.getNextEntry();
			while (entry != null) {
				Path newFile = newFile(outputDir, entry);
				if (entry.isDirectory()) {
					Files.createDirectories(newFile);
				} else {
					// fix for Windows-created archives
					Path parent = newFile.getParent();
					if (!Files.isDirectory(parent)) {
						throw new IOException("Failed to create directory: " + parent);
					}
					Files.createDirectories(parent);

					// write file content
					Files.copy(zipInput, newFile);
				}
				entry = zipInput.getNextEntry();
			}
		} catch (IOException e) {
			log.error("Could not retrieve file from url", e);
		}
	}

	public static Path newFile(Path destinationDir, ZipEntry zipEntry) throws IOException {
		Path destFile = destinationDir.resolve(zipEntry.getName()).normalize();

		String destDirPath = destinationDir.toString();
		String destFilePath = destFile.toString();

		if (!destFilePath.startsWith(destDirPath + File.separator)) {
			throw new IOException("Entry is outside of the target dir: " + zipEntry.getName());
		}

		return destFile;
	}

	private void createOutputFolder(Path jreDir) {
		try {
			Files.createDirectories(jreDir);
		} catch (IOException e) {
			log.error("Could not create output folder", e);
		}
	}

	@Override
	public void setLog(Log log) {
		this.log = log;
	}

	@Override
	public Log getLog() {
		return log;
	}
}
