/*
 * 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 com.facebook.presto;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.List;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.util.FileUtils;

import com.facebook.presto.spi.Plugin;
import com.google.common.base.Charsets;
import com.google.common.collect.Lists;
import com.google.common.io.Files;

/**
 * Mojo that generates the default service descriptor for Presto plugins to META-INF/services/com.facebook.presto.spi.Plugin.
 * 
 * @author Jason van Zyl
 *
 */
@Mojo(name = "generate-service-descriptor", defaultPhase = LifecyclePhase.PACKAGE, requiresDependencyResolution = ResolutionScope.COMPILE)
public class ServiceDescriptorGenerator extends AbstractMojo {

  private final static String LS = System.getProperty("line.separator");
  
  @Parameter(defaultValue = "${project.build.outputDirectory}/META-INF/services/com.facebook.presto.spi.Plugin")
  private File servicesFile;

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

  @Parameter(defaultValue = "${project}")
  private MavenProject project;

  public void execute() throws MojoExecutionException, MojoFailureException {
    // If users have already provided their own service file then we will not overwrite it
    if (servicesFile.exists()) {
      return;
    }
    if (!servicesFile.getParentFile().exists()) {
      servicesFile.getParentFile().mkdirs();
    }
    List<Class<?>> pluginClasses;
    try {
      URLClassLoader loader = createClassloaderFromCompileTimeDependencies();
      pluginClasses = findImplementationsOf(Plugin.class, loader);
    } catch (Exception e1) {
      throw new MojoExecutionException(String.format("%n%nError scanning for classes implementing %s.", Plugin.class.getName()));
    }
    if (pluginClasses.size() == 0) {
      throw new MojoExecutionException(String.format("%n%nYou must have at least one class that implements %s.", Plugin.class.getName()));
    }
    if (pluginClasses.size() > 1) {
      StringBuffer sb = new StringBuffer();
      for (Class<?> pluginClass : pluginClasses) {
        sb.append(pluginClass.getName()).append(LS);
      }
      throw new MojoExecutionException(String.format("%n%nYou have more than one class that implements %s:%n%n%s%nYou can only have one per plugin project.", Plugin.class.getName(), sb.toString()));
    }
    try {
      Class<?> pluginClass = pluginClasses.get(0);
      Files.write(pluginClass.getName().getBytes(Charsets.UTF_8), servicesFile);
      getLog().info(String.format("Wrote META-INF/services/com.facebook.presto.spi.Plugin with %s", pluginClass.getName()));
    } catch (IOException e) {
      throw new MojoExecutionException("Failed to write service descriptor.", e);
    }
  }

  private URLClassLoader createClassloaderFromCompileTimeDependencies() throws Exception {
    List<URL> urls = Lists.newArrayList();
    urls.add(classesDirectory.toURI().toURL());
    for (Artifact artifact : project.getArtifacts()) {
      if (artifact.getFile() != null) {
        urls.add(artifact.getFile().toURI().toURL());
      }
    }
    return new URLClassLoader(urls.toArray(new URL[urls.size()]));
  }

  private List<Class<?>> findImplementationsOf(Class<?> implementationTemplate, URLClassLoader searchRealm) throws IOException {
    List<Class<?>> implementations = Lists.newArrayList();
    List<String> classes = FileUtils.getFileNames(classesDirectory, "**/*.class", null, false);
    for (String classPath : classes) {
      String className = classPath.substring(0, classPath.length() - 6).replace('/', '.');
      try {
        Class<?> implementation = searchRealm.loadClass(implementationTemplate.getName());
        Class<?> clazz = searchRealm.loadClass(className);
        if (implementation.isAssignableFrom(clazz)) {
          implementations.add(clazz);
        }
      } catch (ClassNotFoundException e) {
        // won't happen
      }
    }
    return implementations;
  }
}

@@ -596,127 +596,155 @@
 ort 
-java.io.File;%0Aimport java.io.IOException;%0Aimport java.net.URL;%0Aimport java.net.URLClassLoader;%0Aimport java.util.List
+com.facebook.presto.spi.Plugin;%0Aimport com.google.common.base.Charsets;%0Aimport com.google.common.collect.Lists;%0Aimport com.google.common.io.Files
 ;%0A
-%0A
 impo
@@ -1262,153 +1262,124 @@
 ort 
-com.facebook.presto.spi.Plugin;%0Aimport com.google.common.base.Charsets;%0Aimport com.google.common.collect.Lists;%0Aimport com.google.common.io.Files
+java.io.File;%0Aimport java.io.IOException;%0Aimport java.net.URL;%0Aimport java.net.URLClassLoader;%0Aimport java.util.List
 ;%0A%0A/
@@ -1509,17 +1509,16 @@
 ugin.%0A *
- 
 %0A * @aut
@@ -1759,20 +1759,20 @@
 ate 
-final 
 static
+ final
  Str
@@ -1818,18 +1818,16 @@
 ator%22);%0A
-  
 %0A  @Para
@@ -2135,16 +2135,28 @@
 oject;%0A%0A
+  @Override%0A
   public
@@ -2425,16 +2425,23 @@
 %7B%0A      
+mkdirs(
 services
@@ -2464,16 +2464,8 @@
 le()
-.mkdirs(
 );%0A 
@@ -2849,19 +2849,17 @@
 ses.
-size() == 0
+isEmpty()
 ) %7B%0A
@@ -3053,18 +3053,19 @@
 StringBu
-ff
+ild
 er sb = 
@@ -3080,10 +3080,11 @@
 ngBu
-ff
+ild
 er()
@@ -4960,32 +4960,32 @@
 n%0A      %7D%0A    %7D%0A
-
     return imple
@@ -4999,11 +4999,294 @@
 ons;%0A  %7D
+%0A%0A  @SuppressWarnings(%22ResultOfMethodCallIgnored%22)%0A  private static void mkdirs(File file) throws MojoExecutionException%0A  %7B%0A    file.mkdirs();%0A    if (!file.isDirectory()) %7B%0A      throw new MojoExecutionException(String.format(%22%25n%25nFailed to create directory: %25s%22, file));%0A    %7D%0A  %7D
 %0A%7D%0A

Fix warnings