/**
 * Copyright (C) 2001-2005 France Telecom R&D
 */
package org.objectweb.util.ant;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.MatchingTask;
import org.apache.tools.ant.types.DirSet;
import org.apache.tools.ant.types.selectors.SelectSelector;
import org.apache.tools.ant.types.selectors.TypeSelector;
import org.apache.tools.ant.types.selectors.TypeSelector.FileType;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.PushbackReader;
import java.io.Reader;
import java.math.BigDecimal;

/**
 * This task aggregate JUnit reports by creating an index.html page referencing
 * existing JUnit reports. In addition it computes statistics of these sub 
 * JUnit reports.
 *
 * @author P.Dechamboux
 * @author S.Chassande-Barrioz
 */
public class JUnitTestReportAggregator extends MatchingTask {
    
    /**
     * Is the name of the tested area for the JUnit report without sub directory
     */
    private String commonTest = "Common Tests";
    
    /**
     * the location of each JUnit reports (overview-summary.html file for 
     * instance).
     */
    private File srcdir = null;

    private File destdir = null;
    
    DirSet dirset = new DirSet();

    /**
     * It assigns the directory name where the include and exclude clause has
     * been based. (REQUIRED)
     */
    public void setsrcdir(File d) {
        srcdir = d;
    }

    /**
     * It assigns the directory name where the report files are going to be
     * produced. Default value is the srcdir
     * @see #srcdir
     */
    public void setdestdir(File d) {
        destdir = d;
    }
    
    public String getcommonTest() {
        return commonTest;
    }

    public void setcommonTest(String commonTest) {
        this.commonTest = commonTest;
    }

    /**
     * execute method of the Ant task
     */
    public void execute() throws BuildException {
        log("Begin of JUnitTestReportAggregator task ", Project.MSG_DEBUG);
        if (srcdir == null) {
            throw new BuildException("'srcdir' parameter is not defined in the task JUnitTestReportAggregator");
        }
        log("Source dir=" + srcdir, Project.MSG_DEBUG);
        if (destdir == null) {
            log("no dest dir, use the src dir", Project.MSG_DEBUG);
            destdir = srcdir;
        }
        log("Dest dir=" + destdir, Project.MSG_DEBUG);
        TypeSelector ts = new TypeSelector();
        FileType type = new FileType();
        type.setValue(FileType.DIR);
        ts.setType(type);
        SelectSelector ss = new SelectSelector();
        ss.appendSelector(ts);
        super.addSelector(ss);
        String[] in_files = super.getDirectoryScanner(srcdir).getIncludedFiles();
        if (in_files == null || in_files.length == 0) {
            super.createInclude().setName("**");
            in_files = super.getDirectoryScanner(srcdir).getIncludedFiles();
        }
        
        //Compute global statistics for summary
        TestInfo[] infos = new TestInfo[in_files.length + 1];
        //The last element is the summary of the n first.
        infos[in_files.length] = new TestInfo();
        infos[in_files.length].time = new BigDecimal(0);
        infos[in_files.length].time.setScale(3, BigDecimal.ROUND_FLOOR);
        for (int i = 0; i < in_files.length; i++) {
            log("Extract info from " + in_files[i], Project.MSG_DEBUG);
            //Compute meta data for each element
            infos[i] = extractInfos(in_files[i]);
            //update statistics
            infos[in_files.length].nbTests += infos[i].nbTests;
            infos[in_files.length].nbErrors += infos[i].nbErrors;
            infos[in_files.length].nbFailures += infos[i].nbFailures;
            infos[in_files.length].time = infos[in_files.length].time.add(infos[i].time);
            if (!infos[i].parsingOK)  {
                log("Parsing of " + in_files[i] + " failed", Project.MSG_WARN);
            }
        }
            
        PrintWriter pw;
        //create the index.html
        File destfile = new File(destdir, "index.html");
        log("Creating the file " + destfile, Project.MSG_INFO);
        try {
            pw = new PrintWriter(new FileOutputStream(destfile));
        } catch (FileNotFoundException e) {
            log(e.getMessage(), Project.MSG_ERR);
            throw new BuildException(e.getMessage(), e);
        }
        log("Writing HTML code", Project.MSG_DEBUG);
        //Write the HTML code
        printHeader(pw);
        pw.println("<tr valign=\"top\" class=\"Pass\">");
        pw.println("<td>" + infos[in_files.length].nbTests + "</td><td>"
                + infos[in_files.length].nbFailures + "</td><td>"
                + infos[in_files.length].nbErrors + "</td><td>" 
                + infos[in_files.length].getRate()
                + "%</td><td>" + infos[in_files.length].time.toString() + "</td>");
        pw.println("</tr>");
        printMiddle(pw);
        for (int i = 0; i < in_files.length; i++) {
            pw.println("<tr valign=\"top\" class=\"Pass\">");
            if (infos[i].parsingOK) {
                pw.println("<td><a href=\"" + srcdir + File.separator 
                        + infos[i].indexhtml + "\">" + infos[i].testName
                        + "</a></td><td>" + infos[i].nbTests + "</td><td>"
                        + infos[i].nbErrors + "</td><td>"
                        + infos[i].nbFailures + "</td><td>"
                        + infos[i].time.toString() + "</td>");
            } else {
                pw.println("<td colspan=\"4\">Parsing of <a href=\"" 
                        + srcdir + File.separator + infos[i].indexhtml 
                        + "\">" + infos[i].indexhtml + "</a> failed");
            }
            pw.println("</tr>");
        }
        printTrailer(pw);
        pw.flush();
        pw.close();
        log("End of JUnitTestReportAggregator task", Project.MSG_DEBUG);
    }
        
    /**
     * Represents meta data about a summary of test
     */
    private static class TestInfo {
        //the path to the index.html page
        public String indexhtml;
        //the name of the test to print in the generated page
        public String testName;
        //The number of test
        public int nbTests = 0;
        //the number of tests which cause an error 
        public int nbErrors = 0;
        //the number of tests which cause an failure 
        public int nbFailures = 0;
        //the duration of tests 
        public BigDecimal time;
        
        public boolean parsingOK = true;

        public BigDecimal getRate() {
            BigDecimal cent = new BigDecimal(100);
            BigDecimal rate = new BigDecimal(nbErrors + nbFailures);
            rate.setScale(2);
            rate = rate.divide(new BigDecimal(nbTests), 2, BigDecimal.ROUND_FLOOR);
            rate = cent.multiply(rate);
            rate = cent.subtract(rate);
            return rate;
        }
    }

    /**
     * Parse the index.html page corresponding to the specified file 
     * (has to be in the same directory) in order to compute statistics about 
     * the test summurazing by the index.html page.
     */
    private TestInfo extractInfos(String fn) {
        TestInfo res = new TestInfo();
        if (fn.length() == 0 || ".".equals(fn)) {
            //no sub directory
            res.testName = commonTest;
        } else {
            // one sub directory at least
            // test name is computed from the path
            res.testName = fn.replace('/', '.').replace('\\', '.');
        }
        res.indexhtml = fn + File.separatorChar + "index.html";
        //Parse the index.html page to find test statistics
        try {
            PushbackReader fr = new PushbackReader(
                    new FileReader(srcdir + File.separator + fn), 10);
            locateNextString(fr, "<table");
            locateNextString(fr, "<table");
            locateNextString(fr, "<tr");
            locateNextString(fr, "<tr");
            locateNextString(fr, "<td>"); // now positioned on the number of tests
            res.nbTests = Integer.parseInt(getString2ClosingTag(fr));
            locateNextString(fr, "<td>"); // now positioned on the number of errors
            res.nbFailures = Integer.parseInt(getString2ClosingTag(fr));
            locateNextString(fr, "<td>"); // now positioned on the number of failures
            res.nbErrors = Integer.parseInt(getString2ClosingTag(fr));
            locateNextString(fr, "<td>"); // now positioned on the success rate
            locateNextString(fr, "<td>"); // now positioned on the execution time
            res.time = new BigDecimal(getString2ClosingTag(fr));
            res.time.setScale(3);
        } catch (FileNotFoundException e) {
            res.parsingOK = false;
        } catch (IOException e) {
            res.parsingOK = false;
        }
        return res;
    }
    
    /**
     * Search a string in a PushbackReader
     */
    private void locateNextString(PushbackReader fr, String s) throws IOException {
        int next = fr.read();
        char buf[] = null;
        while (next != -1) {
            if (next == s.charAt(0)) {
                if (buf == null) {
                    buf = new char[s.length() - 1];
                }
                int next2 = fr.read(), pos = 1;
                while ((next2 != -1) && (pos < s.length())) {
                    buf[pos - 1] = (char) next2;
                    if (next2 == s.charAt(pos)) {
                        pos++;
                        next2 = fr.read();
                    } else {
                        fr.unread(buf, 0, pos);
                        break;
                    }
                }
                if (pos == s.length()) {
                    buf[0] = (char) next2;
                    fr.unread(buf, 0, 1);
                    return;
                }
            }
            next = fr.read();
        }
        throw new IOException("Wrong format in test result for JORM - string not found: " + s);
    }
    
    /**
     * Returns the String value from the current position of a reader and the
     * next closing tag.
     * @param fr
     * @return
     * @throws IOException
     */
    private String getString2ClosingTag(Reader fr) throws IOException {
        int next = fr.read();
        StringBuffer sb = new StringBuffer();
        while (next != -1) {
            if (next == '<') {
                break;
            }
            sb.append((char) next);
            next = fr.read();
        }
        return sb.toString();
    }
    
    /**
     * Prints in a print writer the header of the html page to generate
     */
    private void printHeader(PrintWriter pw) {
        pw.println("<html>");
        pw.println("<head>");
        pw.println("<META http-equiv=\"Content-Type\" content=\"text/html; charset=US-ASCII\">");
        pw.println("<title>Unit Test Results: Summary</title>");
        pw.println("<link title=\"Style\" type=\"text/css\" rel=\"stylesheet\" href=\"stylesheet.css\">");
        pw.println("</head>");
        //pw.println("<body onload=\"open('allclasses-frame.html','classListFrame')\">");
        pw.println("<body>");
        pw.println("<h1>JORM Unit Test Results</h1>");
        pw.println("<table width=\"100%\">");
        pw.println("<tr>");
        pw.println("<td align=\"left\"></td><td align=\"right\">Designed for use with <a href=\"http://www.junit.org/\">JUnit</a> and <a href=\"http://jakarta.apache.org/\">Ant</a>.</td>");
        pw.println("</tr>");
        pw.println("</table>");
        pw.println("<hr size=\"1\">");
        pw.println("<h2>Summary</h2>");
        pw.println("<table width=\"95%\" cellspacing=\"2\" cellpadding=\"5\" border=\"0\" class=\"details\">");
        pw.println("<tr valign=\"top\">");
        pw.println("<th>Tests</th><th>Failures</th><th>Errors</th><th>Success rate</th><th>Time</th>");
        pw.println("</tr>");
    }

    private void printMiddle(PrintWriter pw) {
        pw.println("</table>");
        pw.println("<table width=\"95%\" border=\"0\">");
        pw.println("<tr>");
        pw.println("<td style=\"text-align: justify;\">");
        pw.println("Note: <em>failures</em> are anticipated and checked for with assertions while <em>errors</em> are unanticipated.");
        pw.println("</td>");
        pw.println("</tr>");
        pw.println("</table>");
        pw.println("<h2>Tested area</h2>");
        pw.println("<table width=\"95%\" cellspacing=\"2\" cellpadding=\"5\" border=\"0\" class=\"details\">");
        pw.println("<tr valign=\"top\">");
        pw.println("<th width=\"80%\">Name</th><th>Tests</th><th>Errors</th><th>Failures</th><th nowrap=\"nowrap\">Time(s)</th>");
        pw.println("</tr>");
    }

    private void printTrailer(PrintWriter pw) {
        pw.println("</table>");
        pw.println("</body>");
        pw.println("</html>");
    }
    
}