/*******************************************************************************
 * Copyright (c) 2014-2015 Red Hat Inc.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Red Hat Inc. - initial API and implementation
 *******************************************************************************/
package org.fedoraproject.p2.osgi.impl;

import java.io.BufferedOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.fedoraproject.xmvn.artifact.Artifact;
import org.fedoraproject.xmvn.artifact.DefaultArtifact;
import org.fedoraproject.xmvn.resolver.ResolutionRequest;
import org.fedoraproject.xmvn.resolver.Resolver;

import org.fedoraproject.p2.osgi.OSGiConfigurator;

/**
 * Configurator implementation that loads bundles listed in a text manifest file
 * that is generated by Tycho. If Tycho is in "bootstrapped" mode, then it
 * instead loads bundles contained withing the "tycho-bundles-external"
 * bootstrap zip-ball.
 */
@Named
@Singleton
public class DefaultOSGiConfigurator implements OSGiConfigurator {
	private final Logger logger = LoggerFactory
			.getLogger(DefaultOSGiConfigurator.class);

	private static final Artifact BUNDLES_EXTERNAL = new DefaultArtifact(
			"org.eclipse.tycho", "tycho-bundles-external", "txt", "manifest",
			"SYSTEM");
	private static final Artifact BUNDLES_EXTERNAL_ZIP = new DefaultArtifact(
			"org.eclipse.tycho", "tycho-bundles-external", "zip", "SYSTEM");

	private final Resolver resolver;

	@Inject
	public DefaultOSGiConfigurator(Resolver resolver) {
		this.resolver = resolver;
	}

	@Override
	public Collection<Path> getBundles() {
		try {
			Set<Path> bundles = new LinkedHashSet<>();

			// attempt to get a bundle list from the text manifest if it present
			// otherwise tycho might be bootstrapped, so let's extract the
			// necessary bundles from the zip file
			List<String> bundleList;
			Path bundlesExternal = resolver.resolve(
					new ResolutionRequest(BUNDLES_EXTERNAL)).getArtifactPath();
			if (bundlesExternal != null) {
				bundleList = Files.readAllLines(bundlesExternal,
						StandardCharsets.UTF_8);
			} else {
				bundlesExternal = resolver.resolve(
						new ResolutionRequest(BUNDLES_EXTERNAL_ZIP))
						.getArtifactPath();
				if (bundlesExternal != null) {
					bundleList = getBundlesFromZip(bundlesExternal);
				} else {
					throw new RuntimeException("Unable to locate "
							+ BUNDLES_EXTERNAL + " or " + BUNDLES_EXTERNAL_ZIP);
				}
			}
			logger.debug("Using bundles from: {}", bundlesExternal);

			for (String line : bundleList) {
				Path file = Paths.get(line).toAbsolutePath();
				if (file.getFileName().toString()
						.startsWith("org.eclipse.osgi_"))
					continue;

				logger.debug("Using external OSGi bundle: {}", file);
				bundles.add(file);
			}

			return Collections.unmodifiableCollection(bundles);
		} catch (IOException e) {
			throw new RuntimeException("Unable to extract Equinox runtime", e);
		}
	}

	/**
	 * Extracts all OSGi bundles contained within the given zip file and returns
	 * them in a list.
	 * 
	 * @param bundlesZip
	 *            path to the zip on the filesystem
	 * @return the list of paths to the extracted bundles
	 * @throws IOException
	 *             if there was an IO error during extraction
	 */
	private List<String> getBundlesFromZip(Path bundlesZip) throws IOException {
		Path dir = Files.createTempDirectory(BUNDLES_EXTERNAL.getArtifactId());
		logger.info("Extracting bundles into: {}", dir);
		List<String> bundleList = new ArrayList<>();
		try (ZipInputStream zin = new ZipInputStream(
				Files.newInputStream(bundlesZip))) {
			ZipEntry entry;
			String name;
			while ((entry = zin.getNextEntry()) != null) {
				name = entry.getName();
				Path path = dir.resolve(Paths.get(name));
				// create dirs as we encounter them, but create dirs for files
				// too since zip files may not always have the dirs first
				if (entry.isDirectory()) {
					Files.createDirectories(path);
					continue;
				} else {
					Files.createDirectories(path.getParent());
				}
				// extract jar files only
				if (path.getFileName().toString().endsWith(".jar")) {
					byte[] buffer = new byte[1024];
					try (BufferedOutputStream out = new BufferedOutputStream(
							Files.newOutputStream(path))) {
						int count = -1;
						while ((count = zin.read(buffer)) != -1) {
							out.write(buffer, 0, count);
						}
					}
					bundleList.add(path.toString());
				}
			}
		}
		return bundleList;
	}

	@Override
	public Collection<String> getExportedPackages() {
		return Arrays.asList("org.fedoraproject.p2.installer", "org.slf4j");
	}
}
