package de.martido.genny.maven;

import java.util.Arrays;
import java.util.List;

import org.apache.maven.model.FileSet;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;

import de.martido.genny.GeneratorDefinition;
import de.martido.genny.Genny;
import de.martido.genny.GennyConfiguration;
import de.martido.genny.provider.PropertyFileProvider;

/**
 * @goal genny
 * @requiresDependencyResolution runtime
 * @author Martin Dobmeier
 */
public class GennyMojo extends AbstractMojo {

  /**
   * The target class to generate.
   * 
   * @parameter expression="${targetClass}"
   */
  private String targetClass;

  /**
   * The base directory of {@link #targetClass}.
   * 
   * @parameter expression="${baseDirectory}"
   */
  private String baseDirectory;

  /**
   * A list of property files from which to generate the {@link #targetClass}.
   * 
   * @parameter expression="${propertyFiles}"
   */
  private FileSet propertyFiles;

  /**
   * The fully qualified class name of a {@link GennyConfiguration}. Specifying a configuration
   * class takes precedence over {@code #targetClass}, {@code #baseDirectory} and {@code #fileSets}.
   * 
   * @parameter expression="${configurationClass}"
   */
  private String configurationClass;

  /**
   * Turns verbose logging on/off.
   * 
   * @parameter expression="${verbose}"
   */
  private boolean verbose;

  /**
   * Visible for testing.
   */
  protected boolean validate() {

    boolean valid = true;

    if (this.configurationClass == null) {

      if (this.targetClass == null ||
          this.baseDirectory == null ||
          this.propertyFiles.getIncludes().isEmpty()) {
        valid = false;
      }
    }

    return valid;
  }

  @Override
  public void execute() throws MojoExecutionException, MojoFailureException {

    if (!this.validate()) {
      throw new MojoExecutionException("You must provide the name of a configuration class "
          + "or specify a 'targetClass', 'baseDirectory' and a set of source property files.");
    }

    GennyConfiguration conf;
    if (this.configurationClass != null) {
      conf = this.createConfiguration(this.configurationClass);
    }
    else {
      conf = this.createConfiguration();
    }

    try {
      new Genny(this.verbose).generateFrom(conf);
    } catch (Exception ex) {
      throw new MojoExecutionException("", ex);
    }

  }

  /**
   * Reflectively creates an instance of {@code className} which must be assignable to a reference
   * of type {@link GennyConfiguration}.
   * 
   * @param className
   *          <i>mandatory</i> - the name of the Java class to instantiate.
   * @return A {@link GennyConfiguration}.
   * @throws MojoExecutionException
   *           If the configuration class could not be created.
   */
  private GennyConfiguration createConfiguration(String className) throws MojoExecutionException {

    try {
      Class<?> clazz = Class.forName(className);
      if (!GennyConfiguration.class.isAssignableFrom(clazz)) {
        throw new MojoExecutionException("A configuration class must implement "
            + GennyConfiguration.class.getSimpleName());
      }
      return (GennyConfiguration) clazz.newInstance();
    } catch (Exception ex) {
      throw new MojoExecutionException("", ex);
    }
  }

  /**
   * Creates a {@link GennyConfiguration} using {@code #targetClass}, {@code #baseDirectory} and
   * {@code #fileSets}.
   * 
   * @return A {@link GennyConfiguration}.
   */
  private GennyConfiguration createConfiguration() {

    final List<String> fileNames = this.propertyFiles.getIncludes();
    final String[] array = fileNames.toArray(new String[fileNames.size()]);

    return new GennyConfiguration() {

      @Override
      public List<GeneratorDefinition> configure() {
        GeneratorDefinition def = new GeneratorDefinition();
        def.setTargetClass(GennyMojo.this.targetClass);
        def.setBaseDirectory(GennyMojo.this.baseDirectory);

        /**
         * The plugin must be associated with a build phase that is executed prior to 'compile'. If
         * property files are loaded from the classpath, we'll have to make sure that they have been
         * copied to the output directory and that they are available to the plugin's classloader.
         * The preferred phase would be 'generate-sources'. However, the projects' resources won't
         * be copied to the output directoy until the phase 'process-resources'. This does not make
         * them available to the classloader of the plugin. We would have to work around this issue
         * by using a custom {@code ComponentConfigurer}. Therefore, property files will be loaded
         * from the file system.
         */
        def.setFieldProvider(PropertyFileProvider.forFiles(array).fromFileSystem().build());
        return Arrays.asList(def);
      }
    };
  }

}

@@ -504,16 +504,62 @@
 runtime%0A
+ * @configurator include-project-dependencies%0A
  * @auth
@@ -4198,225 +4198,22 @@
 e'. 
-If%0A         * property files are loaded from the classpath, we'll have to make sure that they have been%0A         * copied to the output directory and that they are available to the plugin's classloader.%0A         * The
+The%0A         *
  pre
@@ -4292,16 +4292,19 @@
 es won't
+ be
 %0A       
@@ -4306,19 +4306,16 @@
        *
- be
  copied 
@@ -4339,16 +4339,17 @@
 ecto
+r
 y until 
 the 
@@ -4348,18 +4348,8 @@
 til 
-the phase 
 'pro
@@ -4367,259 +4367,267 @@
 ces'
-. This does not make%0A         * them available to the classloader of the plugin. We would hav
+ and therefore won't be available%0A         * to the plugin's classloader. So we could either use that phas
 e to
- work around this issue%0A         * by using a custom %7B@code ComponentConfigurer%7D. Therefore, property files will be loaded%0A         * from the file system
+gether with a custom%0A         * %7B@code ComponentConfigurer%7D or we could just load property files from the file system. We%0A         * chose the latter
 .%0A  

Make the Maven plugin aware of the project's runtime classpath which may contain Genny configuration classes.