/*
 * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package jdk.tools.jlink.internal;

import java.lang.module.Configuration;
import java.lang.module.ModuleDescriptor;
import java.lang.module.ModuleFinder;
import java.lang.module.ModuleReader;
import java.lang.module.ModuleReference;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.ByteBuffer;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import jdk.tools.jlink.plugin.PluginException;
import jdk.tools.jlink.plugin.ResourcePool;
import jdk.tools.jlink.plugin.ResourcePoolEntry;
import jdk.tools.jlink.plugin.ResourcePoolModule;

final class ResourcePoolConfiguration {
    private ResourcePoolConfiguration() {}

    private static ModuleDescriptor descriptorOf(ResourcePoolModule mod) {
        ModuleDescriptor md = mod.descriptor();

        // drop hashes
        ModuleDescriptor.Builder builder = ModuleDescriptor.newModule(md.name());
        md.requires().stream()
          .forEach(builder::requires);
        md.exports().stream()
          .forEach(builder::exports);
        md.opens().stream()
          .forEach(builder::opens);
        md.uses().stream()
          .forEach(builder::uses);
        md.provides().stream()
          .forEach(builder::provides);
        builder.packages(md.packages());

        md.version().ifPresent(builder::version);
        md.mainClass().ifPresent(builder::mainClass);

        return builder.build();
    }

    private static ModuleReference moduleReference(ModuleDescriptor desc) {
        return new ModuleReference(desc, null) {
            @Override
            public ModuleReader open() {
                throw new UnsupportedOperationException();
            }
        };
    }

    private static Map<String, ModuleReference> allModRefs(ResourcePool pool) {
        return pool.moduleView().modules().
            collect(Collectors.toMap(ResourcePoolModule::name,
                m -> moduleReference(descriptorOf(m))));
    }

    private static void checkPackages(ResourcePool pool) {
        // check that each resource pool module's packages()
        // returns a set that is consistent with the module
        // descriptor of that module.

        pool.moduleView().modules().forEach(m -> {
            ModuleDescriptor desc = m.descriptor();
            if (!desc.packages().equals(m.packages())) {
                throw new RuntimeException("Module " + m.name() +
                   "'s descriptor indicates the set of packages is : " +
                   desc.packages() + ", but module contains packages: " +
                   m.packages());
            }
        });
    }

    static Configuration validate(ResourcePool pool) {
        checkPackages(pool);
        final Map<String, ModuleReference> nameToModRef = allModRefs(pool);
        final Set<ModuleReference> allRefs = new HashSet<>(nameToModRef.values());

        final ModuleFinder finder = new ModuleFinder() {
            @Override
            public Optional<ModuleReference> find(String name) {
                return Optional.ofNullable(nameToModRef.get(name));
            }

            @Override
            public Set<ModuleReference> findAll() {
                return allRefs;
            }
        };

        return Configuration.empty().resolve(
            finder, ModuleFinder.of(), nameToModRef.keySet());
    }
}