package com.netscape.test;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileWriter;
import java.io.PrintStream;
import java.net.InetAddress;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.junit.runner.Description;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunListener;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Text;

public class TestListener extends RunListener {

    DateFormat dateFormat;

    DocumentBuilderFactory docBuilderFactory;
    DocumentBuilder docBuilder;
    Document document;

    TransformerFactory transFactory;
    Transformer trans;

    String reportsDir;

    Element testSuiteElement;
    long testSuiteStartTime;

    Element testCaseElement;
    long testCaseStartTime;

    String currentTestSuiteName;

    long testCount;
    long successCount;
    long failureCount;

    PrintStream stdOut;
    PrintStream stdErr;

    ByteArrayOutputStream out;
    ByteArrayOutputStream err;

    public TestListener() throws Exception {

        dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
        dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));

        docBuilderFactory = DocumentBuilderFactory.newInstance();
        docBuilder = docBuilderFactory.newDocumentBuilder();

        transFactory = TransformerFactory.newInstance();
        trans = transFactory.newTransformer();
        trans.setOutputProperty(OutputKeys.INDENT, "yes");

        reportsDir = System.getProperty("junit.reports.dir");
    }

    public void testRunFinished(Result result) throws Exception {
        if (currentTestSuiteName != null) {
            finishTestSuite(); // finish last suite
        }
    }

    public void testStarted(Description description) throws Exception {

        String testSuiteName = description.getClassName();

        if (currentTestSuiteName == null) {
            startTestSuite(testSuiteName); // start first suite

        } else if (!currentTestSuiteName.equals(testSuiteName)) {
            finishTestSuite(); // finish old suite
            startTestSuite(testSuiteName); // start new suite
        }

        currentTestSuiteName = testSuiteName;

        startTestCase(description);
    }

    public void testFinished(Description description) throws Exception {
        finishTestCase();
        recordTestCaseSuccess();
    }

    public void testFailure(Failure failure) throws Exception {
        finishTestCase();
        recordTestCaseFailure(failure);
    }

    public void startTestSuite(String testSuiteName) throws Exception {

        testSuiteStartTime = System.currentTimeMillis();

        document = docBuilder.newDocument();

        // test suite
        testSuiteElement = document.createElement("testsuite");
        document.appendChild(testSuiteElement);

        testSuiteElement.setAttribute("name", testSuiteName);
        testSuiteElement.setAttribute("timestamp",
                dateFormat.format(new Date(testSuiteStartTime)));
        testSuiteElement.setAttribute("hostname",
                InetAddress.getLocalHost().getHostName());

        // system properties
        Element propertiesElement = document.createElement("properties");
        testSuiteElement.appendChild(propertiesElement);

        for (String name : System.getProperties().stringPropertyNames()) {
            Element propertyElement = document.createElement("property");
            propertyElement.setAttribute("name", name);
            propertyElement.setAttribute("value", System.getProperty(name));
            propertiesElement.appendChild(propertyElement);
        }

        // reset counters
        testCount = 0;
        successCount = 0;
        failureCount = 0;

        // redirect outputs
        stdOut = System.out;
        out = new ByteArrayOutputStream();
        System.setOut(new PrintStream(out, true));

        stdErr = System.err;
        err = new ByteArrayOutputStream();
        System.setErr(new PrintStream(err, true));
    }

    public void finishTestSuite() throws Exception {

        double time = (System.currentTimeMillis() - testSuiteStartTime) / 1000.0;
        testSuiteElement.setAttribute("time", "" + time);

        // save counters
        long errorCount = testCount - successCount - failureCount;

        testSuiteElement.setAttribute("tests", "" + testCount);
        testSuiteElement.setAttribute("failures", "" + failureCount);
        testSuiteElement.setAttribute("errors", "" + errorCount);

        // save outputs
        System.setOut(stdOut);
        System.setErr(stdErr);

        Element systemOutElement = document.createElement("system-out");
        testSuiteElement.appendChild(systemOutElement);

        systemOutElement.appendChild(
                document.createCDATASection(out.toString())
                );

        Element systemErrElement = document.createElement("system-err");
        testSuiteElement.appendChild(systemErrElement);

        systemErrElement.appendChild(
                document.createCDATASection(err.toString())
                );

        // write to file
        FileWriter fw = new FileWriter(
                reportsDir + File.separator + "TEST-" + currentTestSuiteName + ".xml"
                );
        StreamResult sr = new StreamResult(fw);
        DOMSource source = new DOMSource(document);
        trans.transform(source, sr);
        fw.close();
    }

    public void startTestCase(Description description) throws Exception {

        testCaseStartTime = System.currentTimeMillis();

        testCaseElement = document.createElement("testcase");
        testSuiteElement.appendChild(testCaseElement);

        testCaseElement.setAttribute("classname", description.getClassName());
        testCaseElement.setAttribute("name", description.getMethodName());

        testCount++;
    }

    public void finishTestCase() throws Exception {
        double time = (System.currentTimeMillis() - testCaseStartTime) / 1000.0;
        testCaseElement.setAttribute("time", "" + time);
    }

    public void recordTestCaseSuccess() throws Exception {
        successCount++;
    }

    public void recordTestCaseFailure(Failure failure) throws Exception {

        Element failureElement = document.createElement("failure");
        testCaseElement.appendChild(failureElement);

        Description description = failure.getDescription();
        Throwable exception = failure.getException();
        String exceptionName = exception.getClass().getName();

        failureElement.setAttribute("message", failure.getMessage());
        failureElement.setAttribute("type", exceptionName);

        Text messageElement = document.createTextNode(
                exceptionName + ": " + failure.getMessage() + "\n"
                );

        // print stack trace
        for (StackTraceElement element : exception.getStackTrace()) {
            if (!element.getClassName().equals(description.getClassName()))
                continue;

            String source = "Unknown Source";
            if (element.getFileName() != null && element.getLineNumber() >= 0) {
                source = element.getFileName() + ":" + element.getLineNumber();
            }

            messageElement.appendData("\tat " +
                    element.getClassName() + "." + element.getMethodName() +
                    "(" + source + ")\n"
                    );
        }

        failureElement.appendChild(messageElement);

        failureCount++;
    }
}
