/*
 * The MIT License
 *
 * Copyright (c) 2009 The Broad Institute
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package picard.util;

import com.sun.javadoc.*;
import com.sun.tools.doclets.standard.Standard;
import htsjdk.samtools.metrics.MetricBase;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintStream;
import java.util.SortedMap;
import java.util.TreeMap;

/**
 * Doclet for use with JavaDoc that will find all classes extending MetricBase and
 * output information about the metrics definitions that go along with the classes.
 * <p>
 * Takes a single parameter (-f file) to tell it where to output the resulting
 * documentation file in HTML format.
 *
 * @author Tim Fennell
 */
public class MetricsDoclet extends Standard {
    /**
     * Entry point called by the javadoc command line tool. Loops over all the
     * classes identifying metrics classes and then produces some basic information
     * about each in a single HTML file.
     *
     * @param root the root of the javadoc object hierarchy
     * @return true if completed successfully, false otherwise
     */
    public static boolean start(final RootDoc root) {
        // Build a set of metrics classes sorted by name
        final SortedMap<String, ClassDoc> metricsClasses = new TreeMap<String, ClassDoc>();
        for (final ClassDoc doc : root.classes()) {
            if (isMetricsClass(doc)) {
                System.out.println("Processing " + doc.qualifiedTypeName());
                metricsClasses.put(doc.typeName(), doc);
            }
        }

        // Get a print stream to write to
        final PrintStream out = getOutput(root);
        if (out == null) return false;

        // Write out the TOC
        out.println("<h2>Picard Metrics Definitions</h2>");
        out.println("<section>");
        out.println("<p> Click on a metric to see a description of its fields.</p>");
        out.println("<ol>");
        for (final ClassDoc doc : metricsClasses.values()) {
            out.println("<li><a href=\"#" + doc.name() + "\">" + doc.name() + "</a>: " +
                    firstSentence(doc) + "</li>");
        }
        out.println("</ol>");
        out.println("<p>Note: Metrics labeled as percentages (with 'percent' in the full metric name or 'PCT' " +
                "in the name given in the output file) are actually expressed as fractions. For example, " +
                "'PCT_TARGET_BASES_20X = 0.85' should be interpreted as '85 percent of targeted bases are " +
                "covered to 20X coverage or more'.</p>");
        out.println("</section>");

        // Now print out each class
        for (final ClassDoc doc : metricsClasses.values()) {
            out.println("<a id=\"" + doc.name() + "\"></a>");
            out.println("<h2>" + doc.name() + "</h2>");
            out.println("<section>");
            out.println("<p>" + doc.commentText() + "</p>");
            out.println("<table>");
            out.println("<tr><th>Field</th><th>Description</th></tr>");

            for (final FieldDoc field : doc.fields()) {
                if (field.isPublic() && !field.isStatic()) {
                    out.append("<tr>");
                    out.append("<td>" + field.name() + "</td>");
                    out.append("<td>" + field.commentText() + "</td>");
                    out.append("</tr>");
                }
            }

            out.println("</table>");
            out.println("</section>");
        }

        out.close();
        return true;
    }

    /**
     * Checks to see if the class extends MetricBase using only the JavaDoc
     * metadata provided about the class.
     *
     * @param doc the ClassDoc representing the class to be tested
     * @return true if the class is a metrics class, false otherwise
     */
    protected static boolean isMetricsClass(ClassDoc doc) {
        final String metricBaseFqn = MetricBase.class.getName();
        if (!doc.isClass()) return false;
        if (doc.qualifiedTypeName().contains("personal")) return false;

        do {
            doc = doc.superclass();
            if (doc != null && metricBaseFqn.equals(doc.qualifiedTypeName())) return true;
        }
        while (doc != null);

        return false;
    }

    /**
     * Gets the file output parameter from the RootDoc and then opens an
     * PrintStream to write to the file.
     */
    protected static PrintStream getOutput(final RootDoc root) {
        for (final String[] arg : root.options()) {
            if (arg[0].equals("-d") && arg.length == 2) {
                try {
                    return new PrintStream(new File(arg[1], "picard-metric-definitions.html"));
                } catch (FileNotFoundException fnfe) {
                    root.printError("Could not open destination file: " + arg[1]);
                    fnfe.printStackTrace();
                    return null;
                }
            }
        }

        root.printError("Destination file parameter -d not supplied.");
        return null;
    }

    /**
     * Takes a Doc object and uses the firstSentenceTags() to recreate the first sentence
     * text.
     */
    protected static String firstSentence(final Doc doc) {
        final Tag[] tags = doc.firstSentenceTags();
        final StringBuilder builder = new StringBuilder(128);
        for (final Tag tag : tags) {
            builder.append(tag.text());
        }

        return builder.toString();
    }

}
