package org.testng.reporters;

import org.testng.IReporter;
import org.testng.IResultMap;
import org.testng.ISuite;
import org.testng.ISuiteResult;
import org.testng.ITestContext;
import org.testng.ITestResult;
import org.testng.collections.ListMultiMap;
import org.testng.collections.Maps;
import org.testng.internal.Utils;
import org.testng.xml.XmlSuite;

import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Map;

public class JqReporter implements IReporter {
  private static final String C = "class";
  private static final String D = "div";
  private static final String S = "span";

  private int m_testCount = 0;
  private String m_outputDirectory;
  private Map<String, String> m_testMap = Maps.newHashMap();

  @Override
  public void generateReport(List<XmlSuite> xmlSuites, List<ISuite> suites,
      String outputDirectory) {
    m_outputDirectory = "/Users/cedric/java/misc/jquery";

    XMLStringBuffer xsb = new XMLStringBuffer("  ");
    xsb.push(D, "id", "suites");
    generateSuites(xmlSuites, suites, xsb);
    xsb.pop(D);

    String all;
    try {
      all = Files.readFile(new File("/Users/cedric/java/misc/jquery/head"));
      Utils.writeFile(m_outputDirectory, "index2.html", all + xsb.toXML());
    } catch (IOException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }

  private XMLStringBuffer generateSuites(List<XmlSuite> xmlSuites,
      List<ISuite> suites, XMLStringBuffer main) {
     for (ISuite suite : suites) {
      if (suite.getResults().size() == 0) {
        continue;
      }

      XMLStringBuffer xsb = new XMLStringBuffer(main.getCurrentIndent());
      XMLStringBuffer header = new XMLStringBuffer(main.getCurrentIndent());

      xsb.push(D, C, "suite-content");
      Map<String, ISuiteResult> results = suite.getResults();
      XMLStringBuffer xs1 = new XMLStringBuffer(xsb.getCurrentIndent());
      XMLStringBuffer xs2 = new XMLStringBuffer(xsb.getCurrentIndent());
      XMLStringBuffer xs3 = new XMLStringBuffer(xsb.getCurrentIndent());
      int failed = 0;
      int skipped = 0;
      int passed = 0;
      for (ISuiteResult result : results.values()) {
        ITestContext context = result.getTestContext();
        failed += context.getFailedTests().size();
        generateTests("failed", context.getFailedTests(), context, xs1);
        skipped += context.getSkippedTests().size();
        generateTests("skipped", context.getSkippedTests(), context, xs2);
        passed += context.getPassedTests().size();
        generateTests("passed", context.getPassedTests(), context, xs3);
      }
      xsb.addOptional(D, "Failed" + " tests", C, "result-banner " + "failed");
      xsb.addString(xs1.toXML());
      xsb.addOptional(D, "Skipped" + " tests", C, "result-banner " + "skipped");
      xsb.addString(xs2.toXML());
      xsb.addOptional(D, "Passed" + " tests", C, "result-banner " + "passed");
      xsb.addString(xs3.toXML());


      header.push(D, C, "suite");
      header.push(D, C, "suite-header");
      header.addOptional(S, suite.getName(), C, "suite-name");
      header.push(D, C, "stats");
      int total = failed + skipped + passed;
      String stats = String.format("%s, %d failed, %d skipped, %d passed",
          pluralize(total, "method"), failed, skipped, passed);
      header.push("ul");

      // Method stats
      header.push("li");
      header.addOptional(S, stats, C, "method-stats");
      header.pop("li");

      // Tests
      header.push("li");
      header.addOptional(S, String.format("%s ", pluralize(results.values().size(), "test"),
          C, "test-stats"));
      header.pop("li");

      // List of tests
      header.push("ul");
      for (ISuiteResult tr : results.values()) {
        String testName = tr.getTestContext().getName();
        header.push("li");
        header.addOptional("a", testName, "href", "#" + m_testMap.get(testName));
        header.pop("li");
      }
      header.pop("ul");

      header.pop("ul");
      header.pop(D);

      header.pop(D);

      main.addString(header.toXML());
      main.addString(xsb.toXML());
    }

    return main;
  }

  private String capitalize(String s) {
    return Character.toUpperCase(s.charAt(0)) + s.substring(1);
  }

  private void generateTests(String tagClass, IResultMap tests, ITestContext context,
      XMLStringBuffer xsb) {

    if (tests.getAllMethods().isEmpty()) return;

    xsb.push(D, C, "test" + (tagClass != null ? " " + tagClass : ""));
    ListMultiMap<Class<?>, ITestResult> map = Maps.newListMultiMap();
    for (ITestResult m : tests.getAllResults()) {
      map.put(m.getTestClass().getRealClass(), m);
    }

    String testName = "test-" + (m_testCount++);
    m_testMap.put(context.getName(), testName);
    xsb.push(D, C, "test-name");
    xsb.push("a", "name", testName);
    xsb.addString(context.getName());
    xsb.pop("a");

    // Expand icon
    xsb.push("a", C, "expand", "href", "#");
    xsb.addEmptyElement("img", "src", getStatusImage(tagClass));
    xsb.pop("a");

    xsb.pop(D);

    xsb.push(D, C, "test-content");
    for (Class<?> c : map.getKeys()) {
      xsb.push(D, C, C);
      xsb.push(D, C, "class-header");

      // Passed/failed icon
      xsb.addEmptyElement("img", "src", getImage(tagClass));

      xsb.addOptional(S, c.getName(), C, "class-name");

      xsb.pop(D);
      xsb.push(D, C, "class-content");
      List<ITestResult> l = map.get(c);
      for (ITestResult m : l) {
        generateMethod(tagClass, m, context, xsb);
      }
      xsb.pop(D);
      xsb.pop(D);
    }
    xsb.pop(D);

    xsb.pop(D);
  }

  private static String getStatusImage(String status) {
    return "up.png";
//    if ("passed".equals(status)) return "down.png";
//    else return "up.png";
  }

  private static String getImage(String tagClass) {
    return tagClass + ".png";
  }

  private void generateMethod(String tagClass, ITestResult tr,
      ITestContext context, XMLStringBuffer xsb) {
    long time = tr.getEndMillis() - tr.getStartMillis();
    xsb.push(D, C, "method");
    xsb.push(D, C, "method-content");
    xsb.addOptional(S, tr.getMethod().getMethodName(), C, "method-name");

    // Parameters?
    if (tr.getParameters().length > 0) {
      StringBuilder sb = new StringBuilder();
      boolean first = true;
      for (Object p : tr.getParameters()) {
        if (!first) sb.append(", ");
        first = false;
        sb.append(p != null ? p.toString() : "<NULL>");
      }
      xsb.addOptional(S, "(" + sb.toString() + ")", C, "parameters");
    }

    // Exception?
    if (tr.getThrowable() != null) {
      StringBuilder stackTrace = new StringBuilder();
      for (StackTraceElement str : tr.getThrowable().getStackTrace()) {
        stackTrace.append(str.toString()).append("<br>");
      }
      xsb.addOptional(D, stackTrace.toString() + "\n",
          C, "stack-trace");
    }

    xsb.addOptional(S, " " + Long.toString(time) + " ms", C, "method-time");
    xsb.pop(D);
    xsb.pop(D);
  }

  /**
   * Overridable by subclasses to create different directory names (e.g. with timestamps).
   * @param outputDirectory the output directory specified by the user
   */
  protected String generateOutputDirectoryName(String outputDirectory) {
    return outputDirectory;
  }

  private String pluralize(int count, String singular) {
    return Integer.toString(count) + " "
        + (count > 1 ? (singular.endsWith("s") ? singular + "es" : singular + "s") : singular);
  }

}
