/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2014 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * 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 org.jboss.modules;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import java.lang.reflect.Field;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.jboss.modules.filter.PathFilters;
import org.jboss.modules.test.ImportedClass;
import org.jboss.modules.util.TestModuleLoader;
import org.jboss.modules.util.TestResourceLoader;
import org.junit.Test;

/**
 * Test to verify the module export dependencies and imports are created correctly.  Each module should have an entry
 * directly to the module that has an exported path.
 *
 * @author John E. Bailey
 */
public class ModuleExportTest extends AbstractModuleTestCase {

    private static final ModuleIdentifier MODULE_A = ModuleIdentifier.fromString("a");
    private static final ModuleIdentifier MODULE_B = ModuleIdentifier.fromString("b");
    private static final ModuleIdentifier MODULE_C = ModuleIdentifier.fromString("c");
    private static final ModuleIdentifier MODULE_D = ModuleIdentifier.fromString("d");

    @Test
    public void testExportDependencies() throws Exception {
        final TestModuleLoader moduleLoader = new TestModuleLoader();

        ModuleSpec.Builder builder = ModuleSpec.build(MODULE_A);
        builder.addDependency(DependencySpec.createModuleDependencySpec(MODULE_B, true, false));
        moduleLoader.addModuleSpec(builder.create());

        builder = ModuleSpec.build(MODULE_B);
        builder.addDependency(DependencySpec.createModuleDependencySpec(MODULE_C, true, false));
        builder.addDependency(DependencySpec.createModuleDependencySpec(MODULE_D));
        moduleLoader.addModuleSpec(builder.create());

        builder = ModuleSpec.build(MODULE_C);
        moduleLoader.addModuleSpec(builder.create());

        builder = ModuleSpec.build(MODULE_D);
        moduleLoader.addModuleSpec(builder.create());

        Module module = moduleLoader.loadModule(MODULE_A);
        final Set<ModuleIdentifier> dependencyExports = new HashSet<ModuleIdentifier>();
        getExportedModuleDeps(module, dependencyExports);
        assertEquals(2, dependencyExports.size());
        assertTrue(dependencyExports.contains(MODULE_B));
        assertTrue(dependencyExports.contains(MODULE_C));

        dependencyExports.clear();
        module = moduleLoader.loadModule(MODULE_B);
        getExportedModuleDeps(module, dependencyExports);
        assertEquals(1, dependencyExports.size());
        assertTrue(dependencyExports.contains(MODULE_C));

        dependencyExports.clear();
        module = moduleLoader.loadModule(MODULE_C);
        getExportedModuleDeps(module, dependencyExports);
        assertEquals(0, dependencyExports.size());

        dependencyExports.clear();
        module = moduleLoader.loadModule(MODULE_D);
        getExportedModuleDeps(module, dependencyExports);
        assertEquals(0, dependencyExports.size());
    }

    private static void getExportedModuleDeps(final Module module, final Set<ModuleIdentifier> dependencyExports) throws ModuleLoadException {
        getExportedModuleDeps(module, new HashSet<Module>(Collections.singleton(module)), dependencyExports);
    }

    private static void getExportedModuleDeps(final Module module, final Set<Module> visited, final Set<ModuleIdentifier> dependencyExports) throws ModuleLoadException {
        for (Dependency dependency : module.getDependenciesInternal()) {
            if (dependency instanceof ModuleDependency && dependency.getExportFilter() != PathFilters.rejectAll()) {
                final ModuleDependency moduleDependency = (ModuleDependency) dependency;
                final Module md = moduleDependency.getModuleLoader().loadModule(moduleDependency.getIdentifier());
                if (md != null && moduleDependency.getExportFilter() != PathFilters.rejectAll()) {
                    if (visited.add(md)) {
                        dependencyExports.add(md.getIdentifier());
                        getExportedModuleDeps(md, visited, dependencyExports);
                    }
                }
            }
        }
    }

    @SuppressWarnings({ "unchecked" })
    @Test
    public void testImportPaths() throws Exception {
        final TestModuleLoader moduleLoader = new TestModuleLoader();

        ModuleSpec.Builder builder = ModuleSpec.build(MODULE_A);
        builder.addDependency(DependencySpec.createModuleDependencySpec(MODULE_B, true));
        moduleLoader.addModuleSpec(builder.create());

        builder = ModuleSpec.build(MODULE_B);
        builder.addDependency(DependencySpec.createModuleDependencySpec(MODULE_C, true));
        builder.addDependency(DependencySpec.createModuleDependencySpec(MODULE_D));
        moduleLoader.addModuleSpec(builder.create());

        builder = ModuleSpec.build(MODULE_C);
        builder.addResourceRoot(ResourceLoaderSpec.createResourceLoaderSpec(TestResourceLoader.build()
            .addClass(ImportedClass.class)
            .create()
        ));
        builder.addDependency(DependencySpec.createLocalDependencySpec());
        moduleLoader.addModuleSpec(builder.create());

        builder = ModuleSpec.build(MODULE_D);
        moduleLoader.addModuleSpec(builder.create());

        Module module = moduleLoader.loadModule(MODULE_A);
        module.getClassLoader().loadClass(ImportedClass.class.getName());

        final Field pathsField = Module.class.getDeclaredField("linkage");
        pathsField.setAccessible(true);
        final Object paths = pathsField.get(module);
        final Field allPathsField = paths.getClass().getDeclaredField("allPaths");
        allPathsField.setAccessible(true);
        final Map<String, List<LocalLoader>> allPaths = (Map<String, List<LocalLoader>>) allPathsField.get(paths);

        Module moduleC = moduleLoader.loadModule(MODULE_C);

        assertEquals(4, allPaths.size());

        for(Map.Entry<String, List<LocalLoader>> entry : allPaths.entrySet()) {
            assertEquals(1, entry.getValue().size());
            assertEquals(moduleC.getClassLoaderPrivate().getLocalLoader(), entry.getValue().get(0));
        }
    }
}
