/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF 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 org.apache.tools.ant.taskdefs.optional.extension;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.stream.Collectors;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.types.FileSet;

/**
 * A set of useful methods relating to extensions.
 *
 */
public final class ExtensionUtil {
    /**
     * Class is not meant to be instantiated.
     */
    private ExtensionUtil() {
        //all methods static
    }

    /**
     * Convert a list of extensionAdapter objects to extensions.
     *
     * @param adapters the list of ExtensionAdapters to add to convert
     * @throws BuildException if an error occurs
     */
    static ArrayList<Extension> toExtensions(final List<? extends ExtensionAdapter> adapters)
        throws BuildException {
        return adapters.stream().map(ExtensionAdapter::toExtension)
            .collect(Collectors.toCollection(ArrayList::new));
    }

    /**
     * Generate a list of extensions from a specified fileset.
     *
     * @param libraries the list to add extensions to
     * @param fileset the filesets containing libraries
     * @throws BuildException if an error occurs
     */
    static void extractExtensions(final Project project,
                                   final List<Extension> libraries,
                                   final List<FileSet> fileset)
        throws BuildException {
        if (!fileset.isEmpty()) {
            for (Extension extension : getExtensions(project, fileset)) {
                libraries.add(extension);
            }
        }
    }

    /**
     * Retrieve extensions from the specified libraries.
     *
     * @param libraries the filesets for libraries
     * @return the extensions contained in libraries
     * @throws BuildException if failing to scan libraries
     */
    private static Extension[] getExtensions(final Project project,
                                              final List<FileSet> libraries)
        throws BuildException {
        final List<Extension> extensions = new ArrayList<>();

        for (FileSet fileSet : libraries) {
            boolean includeImpl = true;
            boolean includeURL = true;

            if (fileSet instanceof LibFileSet) {
                LibFileSet libFileSet = (LibFileSet) fileSet;
                includeImpl = libFileSet.isIncludeImpl();
                includeURL = libFileSet.isIncludeURL();
            }

            final DirectoryScanner scanner = fileSet.getDirectoryScanner(project);
            final File basedir = scanner.getBasedir();
            final String[] files = scanner.getIncludedFiles();
            for (int i = 0; i < files.length; i++) {
                final File file = new File(basedir, files[ i ]);
                loadExtensions(file, extensions, includeImpl, includeURL);
            }
        }
        return extensions.toArray(new Extension[extensions.size()]);
    }

    /**
     * Load list of available extensions from specified file.
     *
     * @param file the file
     * @param extensionList the list to add available extensions to
     * @throws BuildException if there is an error
     */
    private static void loadExtensions(final File file,
                                        final List<Extension> extensionList,
                                        final boolean includeImpl,
                                        final boolean includeURL)
        throws BuildException {
        try (JarFile jarFile = new JarFile(file)) {
            for (Extension extension : Extension
                .getAvailable(jarFile.getManifest())) {
                addExtension(extensionList, extension, includeImpl, includeURL);
            }
        } catch (final Exception e) {
            throw new BuildException(e.getMessage(), e);
        }
    }

    /**
     * Add extension to list.
     * If extension should not have implementation details but
     * does strip them. If extension should not have url but does
     * then strip it.
     *
     * @param extensionList the list of extensions to add to
     * @param originalExtension the extension
     * @param includeImpl false to exclude implementation details
     * @param includeURL false to exclude implementation URL
     */
    private static void addExtension(final List<Extension> extensionList,
                                      final Extension originalExtension,
                                      final boolean includeImpl,
                                      final boolean includeURL) {
        Extension extension = originalExtension;
        if (!includeURL
            && null != extension.getImplementationURL()) {
            extension =
                new Extension(extension.getExtensionName(),
                               extension.getSpecificationVersion().toString(),
                               extension.getSpecificationVendor(),
                               extension.getImplementationVersion().toString(),
                               extension.getImplementationVendor(),
                               extension.getImplementationVendorID(),
                               null);
        }

        final boolean hasImplAttributes =
            null != extension.getImplementationURL()
            || null != extension.getImplementationVersion()
            || null != extension.getImplementationVendorID()
            || null != extension.getImplementationVendor();

        if (!includeImpl && hasImplAttributes) {
            extension =
                new Extension(extension.getExtensionName(),
                               extension.getSpecificationVersion().toString(),
                               extension.getSpecificationVendor(),
                               null,
                               null,
                               null,
                               extension.getImplementationURL());
        }

        extensionList.add(extension);
    }

    /**
     * Retrieve manifest for specified file.
     *
     * @param file the file
     * @return the manifest
     * @throws BuildException if error occurs (file doesn't exist,
     *         file not a jar, manifest doesn't exist in file)
     */
    static Manifest getManifest(final File file)
        throws BuildException {
        try (JarFile jarFile = new JarFile(file)) {
            Manifest m = jarFile.getManifest();
            if (m == null) {
                throw new BuildException("%s doesn't have a MANIFEST", file);
            }
            return m;
        } catch (final IOException ioe) {
            throw new BuildException(ioe.getMessage(), ioe);
        }
    }

}
