/*
 * Create a graphviz graph based on the classes in the specified java
 * source files.
 *
 * (C) Copyright 2002-2010 Diomidis Spinellis
 *
 * Permission to use, copy, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted,
 * provided that the above copyright notice appear in all copies and that
 * both that copyright notice and this permission notice appear in
 * supporting documentation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 * MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 *
 * $Id$
 *
 */

package org.umlgraph.doclet;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;

import com.sun.javadoc.ClassDoc;
import com.sun.javadoc.Doc;
import com.sun.javadoc.LanguageVersion;
import com.sun.javadoc.RootDoc;

/**
 * Doclet API implementation
 * @depend - - - OptionProvider
 * @depend - - - Options
 * @depend - - - View
 * @depend - - - ClassGraph
 * @depend - - - Version
 *
 * @version $Revision$
 * @author <a href="http://www.spinellis.gr">Diomidis Spinellis</a>
 */
public class UmlGraph {

    private static final String programName = "UmlGraph";
    private static final String docletName = "org.umlgraph.doclet.UmlGraph";

    /** Options used for commenting nodes */
    private static Options commentOptions;

    /** Entry point through javadoc */
    public static boolean start(RootDoc root) throws IOException {
	Options opt = buildOptions(root);
	root.printNotice("UMLGraph doclet version " + Version.VERSION + " started");

	View[] views = buildViews(opt, root, root);
	if(views == null)
	    return false;
	if (views.length == 0)
	    buildGraph(root, opt, null);
	else
	    for (int i = 0; i < views.length; i++)
		buildGraph(root, views[i], null);
	return true;
    }

    public static void main(String args[]) {
	PrintWriter err = new PrintWriter(System.err);
        com.sun.tools.javadoc.Main.execute(programName,
	  err, err, err, docletName, args);
    }

    public static Options getCommentOptions() {
    	return commentOptions;
    }

    /**
     * Creates the base Options object.
     * This contains both the options specified on the command
     * line and the ones specified in the UMLOptions class, if available.
     * Also create the globally accessible commentOptions object.
     */
    public static Options buildOptions(RootDoc root) {
	commentOptions = new Options();
	commentOptions.setOptions(root.options());
	commentOptions.setOptions(findClass(root, "UMLNoteOptions"));
	commentOptions.shape = new Shape("note");

	Options opt = new Options();
	opt.setOptions(root.options());
	opt.setOptions(findClass(root, "UMLOptions"));
	return opt;
    }

    /** Return the ClassDoc for the specified class; null if not found. */
    private static ClassDoc findClass(RootDoc root, String name) {
	ClassDoc[] classes = root.classes();
	for (ClassDoc cd : classes)
	    if(cd.name().equals(name))
		return cd;
	return null;
    }

    /**
     * Builds and outputs a single graph according to the view overrides
     */
    public static void buildGraph(RootDoc root, OptionProvider op, Doc contextDoc) throws IOException {
	if(getCommentOptions() == null)
	    buildOptions(root);
	Options opt = op.getGlobalOptions();
	root.printNotice("Building " + op.getDisplayName());
	ClassDoc[] classes = root.classes();

	ClassGraph c = new ClassGraph(root, op, contextDoc);
	c.prologue();
	for (int i = 0; i < classes.length; i++)
	    c.printClass(classes[i], true);
	for (int i = 0; i < classes.length; i++)
	    c.printRelations(classes[i]);
	if(opt.inferRelationships)
            c.printInferredRelations(classes);
        if(opt.inferDependencies)
            c.printInferredDependencies(classes);

	c.printExtraClasses(root);
	c.epilogue();
    }

    /**
     * Builds the views according to the parameters on the command line
     * @param opt The options
     * @param srcRootDoc The RootDoc for the source classes
     * @param viewRootDoc The RootDoc for the view classes (may be
     *                different, or may be the same as the srcRootDoc)
     */
    public static View[] buildViews(Options opt, RootDoc srcRootDoc, RootDoc viewRootDoc) {
	if (opt.viewName != null) {
	    ClassDoc viewClass = viewRootDoc.classNamed(opt.viewName);
	    if(viewClass == null) {
		System.out.println("View " + opt.viewName + " not found! Exiting without generating any output.");
		return null;
	    }
	    if(viewClass.tags("view").length == 0) {
		System.out.println(viewClass + " is not a view!");
		return null;
	    }
	    if(viewClass.isAbstract()) {
		System.out.println(viewClass + " is an abstract view, no output will be generated!");
		return null;
	    }
	    return new View[] { buildView(srcRootDoc, viewClass, opt) };
	} else if (opt.findViews) {
	    List<View> views = new ArrayList<View>();
	    ClassDoc[] classes = viewRootDoc.classes();

	    // find view classes
	    for (int i = 0; i < classes.length; i++)
		if (classes[i].tags("view").length > 0 && !classes[i].isAbstract())
		    views.add(buildView(srcRootDoc, classes[i], opt));

	    return views.toArray(new View[views.size()]);
	} else
	    return new View[0];
    }

    /**
     * Builds a view along with its parent views, recursively
     */
    private static View buildView(RootDoc root, ClassDoc viewClass, OptionProvider provider) {
	ClassDoc superClass = viewClass.superclass();
	if(superClass == null || superClass.tags("view").length == 0)
	    return new View(root, viewClass, provider);

	return new View(root, viewClass, buildView(root, superClass, provider));
    }

    /** Option checking */
    public static int optionLength(String option) {
	return Options.optionLength(option);
    }

    /** Indicate the language version we support */
    public static LanguageVersion languageVersion() {
	return LanguageVersion.JAVA_1_5;
    }
}
