/*
 * Copyright (c) Mark Derricutt 2010.
 *
 * The use and distribution terms for this software are covered by the Eclipse Public License 1.0
 * (http://opensource.org/licenses/eclipse-1.0.php) which can be found in the file epl-v10.html
 * at the root of this distribution.
 *
 * By using this software in any fashion, you are agreeing to be bound by the terms of this license.
 *
 * You must not remove this notice, or any other, from this software.
 */

package com.theoryinpractise.clojure;

import org.apache.maven.plugin.MojoExecutionException;
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 java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Properties;

import static org.apache.commons.io.IOUtils.copy;

@Mojo(name = "test-with-junit", defaultPhase = LifecyclePhase.TEST, requiresDependencyResolution = ResolutionScope.TEST)
public class ClojureRunTestWithJUnitMojo extends AbstractClojureCompilerMojo {

  /**
   * Flag to allow test compiliation to be skipped.
   *
   * @noinspection UnusedDeclaration
   */
  @Parameter(required = true, property = "maven.test.skip", defaultValue = "false")
  private boolean skip;

  /**
   * Flag to allow test execution to be skipped.
   *
   * @noinspection UnusedDeclaration
   */
  @Parameter(required = true, property = "skipTests", defaultValue = "false")
  private boolean skipTests;

  /**
   * The main clojure script to run
   *
   * @noinspection UnusedDeclaration
   */
  @Parameter
  private String testScript;

  /**
   * Output directory for test results
   *
   * @noinspection UnusedDeclaration
   */
  @Parameter(defaultValue = "${project.build.directory}/test-reports", property = "clojure.testOutputDirectory")
  private String testOutputDirectory;

  /**
   * Whether to XML escape non-report output sent to *out*
   *
   * @noinspection UnusedDeclaration
   */
  @Parameter(defaultValue = "true", property = "clojure.xmlEscapeOutput")
  private boolean xmlEscapeOutput;

  /**
   * Provide a comma separated list of tests to run, instead of trying to run all of the test.
   */
  @Parameter(property = "test")
  private String test;

  public void execute() throws MojoExecutionException {
    if (skip || skipTests) {
      getLog().info("Test execution is skipped");
    } else {
      try {
        final File[] testSourceDirectories = getSourceDirectories(SourceDirectory.TEST);
        final File[] allSourceDirectories = getSourceDirectories(SourceDirectory.TEST, SourceDirectory.COMPILE);
        final File outputFile = new File(testOutputDirectory);
        NamespaceInFile[] ns = new NamespaceDiscovery(getLog(), outputFile, charset, testDeclaredNamespaceOnly).discoverNamespacesIn(testNamespaces, testSourceDirectories);
        if (test != null) {
          ArrayList<NamespaceInFile> filteredNS = new ArrayList<NamespaceInFile>();
          String[] patterns = test.split("\\s*,\\s*");
          for (NamespaceInFile nsinf: ns) {
            for ( String pattern: patterns) {
              if (nsinf.getName().contains(pattern)) {
                filteredNS.add(nsinf);
                break;
              }
            }
          }
          ns = filteredNS.toArray(new NamespaceInFile[filteredNS.size()]);
        }
        File confFile = File.createTempFile("run-test", ".txt");
        confFile.deleteOnExit();
        final PrintWriter confWriter = new PrintWriter(new FileWriter(confFile));
        generateConfig(confWriter, ns);
        confWriter.close();
        String testConf = confFile.getPath();

        // if the test script is supposed to be found on the classpath, skip all file checking
        if (!isClasspathResource(testScript)) {
          if (!isExistingTestScriptFile(testScript)) {
            // Generate test script
            outputFile.mkdir();

            File testFile = File.createTempFile("run-test", ".clj");
            testFile.deleteOnExit();
            final PrintWriter writer = new PrintWriter(new FileWriter(testFile));

            generateTestScript(writer);

            writer.close();

            testScript = testFile.getPath();

          } else {
            File testFile = new File(testScript);

            if (!testFile.exists()) {
              testFile = new File(getWorkingDirectory(), testScript);
            }

            if (!(testFile.exists())) {
              throw new MojoExecutionException("testScript " + testFile.getPath() + " does not exist.");
            }
          }
        }

        getLog().debug("Running clojure:test-with-junit against " + testScript);
        callClojureWith(allSourceDirectories, outputDirectory, testClasspathElements, "clojure.main", new String[] {testScript, testConf});
      } catch (IOException e) {
        throw new MojoExecutionException(e.getMessage(), e);
      }
    }
  }

  protected void generateConfig(PrintWriter writer, NamespaceInFile[] ns) throws IOException {
    Properties props = getProps(ns);
    props.store(writer,"Test Run Properties");
  }

  protected Properties getProps(NamespaceInFile[] ns) {
    Properties props = new Properties();
    for(int i = 0; i < ns.length; i++) {
      props.put("ns."+i, ns[i].getName());
    }
    props.put("junit", "True");
    props.put("outputDir", testOutputDirectory);
    props.put("xmlEscape", String.valueOf(xmlEscapeOutput));
    return props;
  }

  private void generateTestScript(PrintWriter writer) throws IOException {
    copy(ClojureRunTestWithJUnitMojo.class.getResourceAsStream("/default_test_script.clj"), writer);
  }
}
