/*
 * 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 java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
import org.jboss.modules.filter.PathFilter;
import org.jboss.modules.filter.PathFilters;

/**
 * A dependency tree viewer utility.  Prints out the dependency tree for a module.
 */
public final class DependencyTreeViewer {
    private static <I, O extends I> O[] filtered(Class<O[]> oType, I... inputs) {
        final I[] newArray = Arrays.copyOf(inputs, inputs.length);
        int o = 0;
        for (int i = 0; i < inputs.length; i ++) {
            if (oType.getComponentType().isInstance(inputs[i])) {
                newArray[o++] = (O) inputs[i];
            }
        }
        return Arrays.copyOf(newArray, o, oType);
    }

    private static void print(PrintWriter out, String prefix, ModuleSpec spec, FastCopyHashSet<String> visited, File... roots) {
        if (spec instanceof AliasModuleSpec) {
            final AliasModuleSpec aliasModuleSpec = (AliasModuleSpec) spec;
            out.print(" -> ");
            final String aliasTarget = aliasModuleSpec.getAliasName();
            out.println(aliasTarget);
            if (visited.add(aliasTarget)) {
                try {
                    final ModuleSpec moduleSpec = LocalModuleFinder.parseModuleXmlFile(aliasTarget, null, roots);
                    print(out, prefix, moduleSpec, visited);
                } catch (IOException e) {
                    out.println(e);
                } catch (ModuleLoadException e) {
                    out.println(e);
                }
            }
        } else if (spec instanceof ConcreteModuleSpec) {
            out.println();
            final ConcreteModuleSpec concreteModuleSpec = (ConcreteModuleSpec) spec;
            final DependencySpec[] dependencies = filtered(ModuleDependencySpec[].class, concreteModuleSpec.getDependencies());
            for (int i = 0, dependenciesLength = dependencies.length; i < dependenciesLength; i++) {
                print(out, prefix, dependencies[i], visited, i == dependenciesLength - 1, roots);
            }
        } else {
            out.println();
        }
    }

    private static void print(PrintWriter out, String prefix, DependencySpec spec, FastCopyHashSet<String> visited, final boolean last, final File... roots) {
        if (spec instanceof ModuleDependencySpec) {
            final ModuleDependencySpec moduleDependencySpec = (ModuleDependencySpec) spec;
            final String name = moduleDependencySpec.getName();
            out.print(prefix);
            out.print(last ? '└' : '├');
            out.print('─');
            out.print(' ');
            out.print(name);
            if (moduleDependencySpec.isOptional()) {
                out.print(" (optional)");
            }
            final PathFilter exportFilter = moduleDependencySpec.getExportFilter();
            if (! exportFilter.equals(PathFilters.rejectAll())) {
                out.print(" (exported)");
            }
            if (visited.add(name)) {
                print(out, prefix + (last ? "   " : "│  "), name, visited, roots);
            } else {
                out.println();
            }
        }
    }

    private static void print(PrintWriter out, String prefix, String name, FastCopyHashSet<String> visited, final File... roots) {
        final ModuleSpec moduleSpec;
        try {
            moduleSpec = LocalModuleFinder.parseModuleXmlFile(name, null, roots);
            if (moduleSpec == null) {
                out.println(" (not found)");
            } else {
                print(out, prefix, moduleSpec, visited, roots);
            }
        } catch (IOException e) {
            out.print(" (");
            out.print(e);
            out.println(")");
        } catch (ModuleLoadException e) {
            out.print(" (");
            out.print(e);
            out.println(")");
        }
    }

    /**
     * Print the dependency tree for the given module with the given module root list.
     *
     * @param out the output stream to use
     * @param identifier the identifier of the module to examine
     * @param roots the module roots to search
     * @deprecated Use {@link #print(PrintWriter, String, File...)} instead.
     */
    @Deprecated
    public static void print(PrintWriter out, ModuleIdentifier identifier, final File... roots) {
        print(out, identifier.toString(), roots);
    }

    /**
     * Print the dependency tree for the given module with the given module root list.
     *
     * @param out the output stream to use
     * @param name the name of the module to examine
     * @param roots the module roots to search
     */
    public static void print(PrintWriter out, String name, final File... roots) {
        out.print(name);
        print(out, "", name, new FastCopyHashSet<String>(), roots);
        out.flush();
    }
}
