package com.fitbank.autoservices;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Collection;
import java.util.LinkedList;
import java.util.regex.Pattern;

import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;

import static org.apache.maven.plugins.annotations.LifecyclePhase.PROCESS_CLASSES;
import static org.apache.maven.plugins.annotations.ResolutionScope.COMPILE;

/**
 * Genera los archivos META-INF/services/* para los interfaces especificados.
 */
@Mojo(name = "generate", defaultPhase = PROCESS_CLASSES, threadSafe = true, requiresDependencyResolution = COMPILE)
public class AutoServicesMojo extends AbstractMojo {

    /**
     * Output
     */
    @Parameter(defaultValue = "${project.build.outputDirectory}")
    private File outputDirectory;

    /**
     * Clases o interfaces de los que se buscará las subclases.
     */
    @Parameter
    private String[] classes;

    /**
     * The Maven project.
     */
    @Parameter(defaultValue = "${project}", readonly = true)
    private MavenProject project;

    public void execute() throws MojoExecutionException {
        File services = new File(outputDirectory, "META-INF/services");

        if (!services.exists()) {
            services.mkdirs();
        }

        for (String serviceClassName : classes) {
            URLClassLoader dcl;
            try {
                Collection<URL> urls = new LinkedList<URL>();
                for (Object s : project.getCompileClasspathElements()) {
                    urls.add(new File((String) s).toURI().toURL());
                }
                urls.add(outputDirectory.toURI().toURL());
                dcl = new URLClassLoader(urls.toArray(new URL[urls.size()]));
            } catch (MalformedURLException e) {
                throw new MojoExecutionException("Cannot load classpath", e);
            } catch (DependencyResolutionRequiredException e) {
                throw new MojoExecutionException("Cannot load classpath", e);
            }

            Class<?> base;
            try {
                base = dcl.loadClass(serviceClassName);
            } catch (ClassNotFoundException e) {
                throw new MojoExecutionException("Cannot load class "
                        + serviceClassName, e);
            }

            File service = new File(services, serviceClassName);
            FileWriter w = null;
            try {
                w = new FileWriter(service);

                getLog().info(
                        "Buscando subclases para " + base.getName() + " en "
                                + outputDirectory);
                for (String className : getClassNames(outputDirectory,
                        outputDirectory)) {
                    try {
                        Class<?> clase = dcl.loadClass(className);

                        if (base.isAssignableFrom(clase) && !base.equals(clase)) {
                            getLog().info("Encontrado " + clase.getName());
                            w.write(clase.getName() + "\n");
                        }
                    } catch (ClassNotFoundException e) {
                        throw new MojoExecutionException("Cannot load class "
                                + className, e);
                    }
                }
            } catch (IOException e) {
                throw new MojoExecutionException("Error creating file "
                        + service, e);
            } finally {
                if (w != null) {
                    try {
                        w.close();
                    } catch (IOException e) {
                        // ignore
                    }
                }
            }
        }
    }

    private Collection<String> getClassNames(File start, File parent) {
        Collection<String> classNames = new LinkedList<String>();

        for (File f : parent.listFiles()) {
            if (f.isDirectory()) {
                classNames.addAll(getClassNames(start, f));
            } else if (f.getName().endsWith(".class")
                    && !f.getName().matches("\\$.*\\.class")) {
                classNames.add(f.getAbsolutePath().replace(
                        start.getAbsolutePath(), "").replaceAll(".class$", "")
                        .replaceAll(Pattern.quote(File.separator), ".")
                        .substring(1));
            }
        }

        return classNames;
    }
}
