package org.apache.shale.tiger.utils;

import org.apache.shale.tiger.view.Request;
import org.apache.shale.tiger.view.Session;
import org.apache.shale.tiger.view.View;
import org.apache.shale.tiger.view.faces.LifecycleListener2;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * scans all classes given in the classpath and dumps packages containing shale-tiger annotated classes
 */
public final class DumpScanPackages {

    private static final Class[] markers =
            {View.class, Request.class, Session.class,
                    org.apache.shale.tiger.view.Application.class};

    public static void main(String[] args) throws ClassNotFoundException {
        new DumpScanPackages().process();
    }

    public void process() {

        // collect all classes in classpath
        List<Class> classes = new ArrayList<Class>(2000);

        String classpath = System.getProperty("java.class.path");
        String[] classpathTokens = classpath.split(File.pathSeparator);
        for (String classpathToken : classpathTokens) {
            if (classpathToken.endsWith(".jar")) {
                collectJarClasses(classes, classpathToken);
            } else {
                collectDirClasses(classes, classpathToken);
            }
        }

        // scan for shale-tiger annotations
        Set<String> packageNames = new TreeSet<String>();
        for (Class clazz : classes) {
            if (checkMarkers(clazz)) {
                if (!packageNames.contains((clazz.getPackage().getName()))) {
                    packageNames.add(clazz.getPackage().getName());
                }
            }
        }

        // dump all packages found
        System.out.println("Propsed " + LifecycleListener2.SCAN_PACKAGES + " configuration:");
        System.out.println("<context-param>");
        System.out.println("\t<param-name>" + LifecycleListener2.SCAN_PACKAGES + "</param-name>");
        System.out.print("\t<param-value>");
        boolean first = true;
        for (String packageName : packageNames) {
            if (!first) {
                System.out.println(",");
            } else {
                first = false;
            }
            System.out.print(packageName);
        }
        System.out.println("<param-value>");
        System.out.println("</context-param>");
    }

    /**
     * collects all classes within a directory structure
     */
    private void collectDirClasses(List<Class> classes, String filename) {
        File base = new File(filename);
        collectDirClasses(classes, base, base, null);
    }

    private void collectDirClasses(final List<Class> classes, final File base, final File dir, final String packageName) {
        dir.listFiles(new FileFilter() {

            public boolean accept(File pathname) {
                if (pathname.isDirectory()) {
                    collectDirClasses(classes, base, pathname, buildPackageName(packageName, pathname.getName()));
                    return false;
                }
                if (!pathname.getName().endsWith(".class")) {
                    return false;
                }

                String classname = buildPackageName(packageName, pathname.getName().substring(0, pathname.getName().length() - 6));

                loadAndAddClass(classes, classname);

                return false;
            }
        });
    }

    private String buildPackageName(String packageName, String name) {
        if (packageName == null) {
            return name;
        }
        return packageName + "." + name;
    }

    /**
     * collects all classes within a jar file
     */
    private void collectJarClasses(List<Class> classes, String jarname) {
        JarFile jarFile = null;
        try {
            jarFile = new JarFile(jarname);
        } catch (IOException e) {
            System.err.println(e.getLocalizedMessage());
            return;
        }
        Enumeration<JarEntry> entries = jarFile.entries();
        while (entries.hasMoreElements()) {
            JarEntry entry = entries.nextElement();
            if (!entry.getName().endsWith(".class")) {
                continue;
            }

            String classname = entry.getName().substring(0, entry.getName().length() - 6).replace('/', '.');
            loadAndAddClass(classes, classname);
        }
    }

    /**
     * loads a class and just show a short message in case something went wrong
     */
    private void loadAndAddClass(List<Class> classes, String classname) {
        try {
            classes.add(Class.forName(classname));
        }
        catch (OutOfMemoryError e) {
            throw e;
        } catch (Throwable e) {
            System.err.println(classname + ": " + e.getClass().getName() + " " + e.getLocalizedMessage());
        }
    }

    /**
     * check if the given class has one of the shale-tiger annotations
     */
    private boolean checkMarkers(Class clazz) {
        for (Class marker : markers) {
            if (clazz.getAnnotation(marker) != null) {
                return true;
            }
        }
        return false;
    }
}
