/*
 *
 *   Copyright (c) 2016-2018 Red Hat, Inc.
 *
 *   Red Hat licenses this file to you 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.reactiverse.vertx.maven.plugin.utils;

import io.reactiverse.vertx.maven.plugin.mojos.AbstractVertxMojo;
import org.apache.maven.plugin.logging.Log;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import static org.apache.commons.io.FileUtils.copyInputStreamToFile;

/**
 * @author <a href="http://escoffier.me">Clement Escoffier</a>
 */
public class WebJars {

    /**
     * A regex extracting the library name and version from Zip Entry names.
     */
    private static final Pattern WEBJAR_REGEX = Pattern.compile(".*META-INF/resources/webjars/([^/]+)/([^/]+)/.*");
    /**
     * The directory within jar file where webjar resources are located.
     */
    private static final String WEBJAR_LOCATION = "META-INF/resources/webjars/";

    /**
     * A regex to extract the different part of the path of a file from a library included in a webjar.
     */
    private static final Pattern WEBJAR_INTERNAL_PATH_REGEX = Pattern.compile("([^/]+)/([^/]+)/(.*)");


    /**
     * Checks whether the given file is a WebJar or not (http://www.webjars.org/documentation).
     * The check is based on the presence of {@literal META-INF/resources/webjars/} directory in the jar file.
     *
     * @param file the file.
     * @return {@literal true} if it's a bundle, {@literal false} otherwise.
     */
    public static boolean isWebJar(Log log, File file) {
        if (file == null) {
            return false;
        }
        Set<String> found = new LinkedHashSet<>();
        if (file.isFile() && file.getName().endsWith(".jar")) {
            try (JarFile jar = new JarFile(file)) {

                // Fast return if the base structure is not there
                if (jar.getEntry(WEBJAR_LOCATION) == null) {
                    return false;
                }

                search(found, jar);
            } catch (IOException e) {
                log.error("Cannot check if the file " + file.getName()
                    + " is a webjar, cannot open it", e);
                return false;
            }

            for (String lib : found) {
                log.info("Web Library found in " + file.getName() + " : " + lib);
            }

            return !found.isEmpty();
        }

        return false;
    }

    private static void search(Set<String> found, JarFile jar) {
        Enumeration<JarEntry> entries = jar.entries();
        while (entries.hasMoreElements()) {
            JarEntry entry = entries.nextElement();
            Matcher matcher = WEBJAR_REGEX.matcher(entry.getName());
            if (matcher.matches()) {
                found.add(matcher.group(1) + "-" + matcher.group(2));
            }
        }
    }

    public static void extract(final AbstractVertxMojo mojo, File in, File out, boolean stripVersion) throws IOException {
        try (ZipFile file = new ZipFile(in)) {
            Enumeration<? extends ZipEntry> entries = file.entries();
            while (entries.hasMoreElements()) {
                unzipWebJarFile(mojo, out, stripVersion, file, entries);
            }
        }
    }

    private static void unzipWebJarFile(AbstractVertxMojo mojo, File out, boolean stripVersion, ZipFile file, Enumeration<? extends ZipEntry> entries) throws IOException {
        ZipEntry entry = entries.nextElement();
        if (entry.getName().startsWith(WEBJAR_LOCATION) && !entry.isDirectory()) {
            // Compute destination.
            File output = getOutput(mojo.getLog(), out, stripVersion,
                entry.getName().substring(WEBJAR_LOCATION.length()));
            try (InputStream stream = file.getInputStream(entry)) {
                boolean created = output.getParentFile().mkdirs();
                mojo.getLog().debug(out.getParentFile().getAbsolutePath() + " created? " + created);
                copyInputStreamToFile(stream, output);
            } catch (IOException e) {
                mojo.getLog().error("Cannot unpack " + entry.getName() + " from " + file.getName(), e);
                throw e;
            }
        }
    }

    private static File getOutput(Log log, File out, boolean stripVersion, String path) {
        if (stripVersion) {
            Matcher matcher = WEBJAR_INTERNAL_PATH_REGEX.matcher(path);
            if (matcher.matches()) {
                return new File(out, matcher.group(1) + "/" + matcher.group(3));
            } else {
                log.warn(path + " does not match the regex - did not strip the version for this file");
            }
        }
        return new File(out, path);
    }
}