/*
 * Copyright (c) 2017 Stamina Framework developers.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.staminaframework.provisioning.internal;

import io.staminaframework.command.Command;
import io.staminaframework.command.CommandConstants;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.log.LogService;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.net.URL;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.List;

/**
 * This command is responsible for copying bundles / subsystems to the "addons"
 * directory.
 *
 * @author Stamina Framework developers
 */
@Component(service = Command.class, property = CommandConstants.COMMAND + "=install")
public class InstallCommand implements Command {
    @Reference
    private LogService logService;

    @Override
    public void help(PrintStream out) {
        out.println("Install artifacts (such as bundle / subsystem) to the 'addons' directory.");
        out.println("Provision files contains artifact URLs to install, one by line.");
        out.println("All platform supported URL protocols can be used, such as:");
        out.println("  - mvn:groupId/artifactId/version/type");
        out.println("  - http://repo.site/artifact.esa");
        out.println("  - file://extensions/bundle.jar");
        out.println("Lines starting with '#' are not read.");
        out.println("Use flag '--force' to force artifact install.");
        out.println("Use flag '--start' to keep platform running when provisioning is done.");
        out.println("Usage: install [--force] [--start] <provision files>");
    }

    @Override
    public boolean execute(Context context) throws Exception {
        if (context.arguments().length == 0) {
            help(context.out());
            return true;
        }

        final String addonsProp = System.getProperty("stamina.addons");
        if (addonsProp == null) {
            throw new RuntimeException("Missing system property: stamina.addons");
        }
        final Path addonsPath = FileSystems.getDefault().getPath(addonsProp);
        if (!Files.exists(addonsPath) || !Files.isDirectory(addonsPath)) {
            throw new IOException("Missing addons directory: " + addonsPath);
        }

        context.out().println("Starting platform provisioning");

        // Parse command flags.
        boolean forceInstall = false;
        boolean start = false;
        for (final String arg : context.arguments()) {
            if ("--force".equals(arg)) {
                forceInstall = true;
            }
            if ("--start".equals(arg)) {
                start = true;
            }
        }

        for (final String arg : context.arguments()) {
            if (arg.startsWith("--")) {
                continue;
            }
            final Path provisionFile = FileSystems.getDefault().getPath(arg);
            if (!Files.exists(provisionFile)) {
                throw new IllegalArgumentException("Provision file does not exist: " + provisionFile);
            }

            context.out().println(
                    "Reading provision file: " + provisionFile);

            final List<String> provisionLines = Files.readAllLines(provisionFile);
            for (final String provisionLine : provisionLines) {
                final String urlSpec = provisionLine.trim();
                if (urlSpec.startsWith("#") || urlSpec.length() == 0) {
                    // Skip comments & empty lines.
                    continue;
                }

                final URL artifactUrl = new URL(urlSpec);
                final Path target = addonsPath.resolve(toLocalPath(artifactUrl.toExternalForm()));
                if (Files.exists(target) && !forceInstall) {
                    context.out().println(
                            "Artifact already exists: " + artifactUrl);
                    continue;
                }

                final Path tmp = Files.createTempFile("stamina-install-", ".tmp");
                tmp.toFile().deleteOnExit();
                context.out().println(
                        "Downloading artifact: " + artifactUrl);
                try (final InputStream in = new BufferedInputStream(artifactUrl.openStream(), 4096)) {
                    // Download remote artifact and copy it to a temporary file.
                    Files.copy(in, tmp, StandardCopyOption.REPLACE_EXISTING);
                }

                // Move the fully downloaded file to the addons directory.
                context.out().println(
                        "Installing artifact: " + artifactUrl);
                Files.move(tmp, target, StandardCopyOption.ATOMIC_MOVE);
            }
        }

        context.out().println("Platform provisioning done");

        return start;
    }

    private String toLocalPath(String url) throws IOException {
        // Do some clever work to transform an URL to a file name...
        try {
            final MessageDigest md5 = MessageDigest.getInstance("MD5");
            final byte[] hash = md5.digest(url.getBytes("UTF-8"));
            final StringBuilder hex = new StringBuilder(32);
            for (int i = 0; i < hash.length; i++) {
                if ((0xff & hash[i]) < 0x10) {
                    hex.append("0"
                            + Integer.toHexString((0xFF & hash[i])));
                } else {
                    hex.append(Integer.toHexString(0xFF & hash[i]));
                }
            }

            // ArtifactInstaller from FileInstall requires a valid extension:
            // we do our best to guess file type.
            final String ext;
            if (url.toLowerCase().endsWith(".esa") || url.contains("/esa")) {
                ext = ".esa";
            } else {
                ext = ".jar";
            }

            return hex.toString() + ext;
        } catch (NoSuchAlgorithmException e) {
            throw new IOException("Cannot convert URL to local file", e);
        }
    }
}

@@ -1515,16 +1515,26 @@
 AND + %22=
+provision:
 install%22
@@ -2392,16 +2392,26 @@
 %22Usage: 
+provision:
 install 

Fix command prefix