/*
 * Copyright (c) 1997, 2005, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 * 
 * --- end of original header ---
 * 
 * This file was modified for use in the BlueJ program on the 1st September 2011.
 * 
 */

package bluej.doclet.doclets.formats.html;

import java.util.List;

import bluej.doclet.doclets.internal.toolkit.ClassWriter;
import bluej.doclet.doclets.internal.toolkit.builders.MemberSummaryBuilder;
import bluej.doclet.doclets.internal.toolkit.taglets.ParamTaglet;
import bluej.doclet.doclets.internal.toolkit.taglets.TagletOutput;
import bluej.doclet.doclets.internal.toolkit.util.ClassTree;
import bluej.doclet.doclets.internal.toolkit.util.DirectoryManager;
import bluej.doclet.doclets.internal.toolkit.util.DocletAbortException;
import bluej.doclet.doclets.internal.toolkit.util.Util;
import bluej.doclet.doclets.internal.toolkit.util.VisibleMemberMap;

import com.sun.javadoc.ClassDoc;
import com.sun.javadoc.Tag;
import com.sun.javadoc.Type;

/**
 * Generate the Class Information Page.
 * @see bluej.doclet.ClassDoc
 * @see java.util.Collections
 * @see java.util.List
 * @see java.util.ArrayList
 * @see java.util.HashMap
 *
 * @author Atul M Dambalkar
 * @author Robert Field
 */
public class ClassWriterImpl extends SubWriterHolderWriter
        implements ClassWriter {

    protected ClassDoc classDoc;

    protected ClassTree classtree;

    protected ClassDoc prev;

    protected ClassDoc next;

    /**
     * @param classDoc the class being documented.
     * @param prevClass the previous class that was documented.
     * @param nextClass the next class being documented.
     * @param classTree the class tree for the given class.
     */
    public ClassWriterImpl (ClassDoc classDoc,
            ClassDoc prevClass, ClassDoc nextClass, ClassTree classTree)
    throws Exception {
        super(ConfigurationImpl.getInstance(),
              DirectoryManager.getDirectoryPath(classDoc.containingPackage()),
              classDoc.name() + ".html",
              DirectoryManager.getRelativePath(classDoc.containingPackage().name()));
        this.classDoc = classDoc;
        configuration.currentcd = classDoc;
        this.classtree = classTree;
        this.prev = prevClass;
        this.next = nextClass;
    }

    /**
     * Print this package link
     */
    protected void navLinkPackage() {
        navCellStart();
        printHyperLink("package-summary.html", "",
            configuration.getText("doclet.Package"), true, "NavBarFont1");
        navCellEnd();
    }

    /**
     * Print class page indicator
     */
    protected void navLinkClass() {
        navCellRevStart();
        fontStyle("NavBarFont1Rev");
        boldText("doclet.Class");
        fontEnd();
        navCellEnd();
    }

    /**
     * Print class use link
     */
    protected void navLinkClassUse() {
        navCellStart();
        printHyperLink("class-use/" + filename, "",
                       configuration.getText("doclet.navClassUse"), true, "NavBarFont1");
        navCellEnd();
    }

    /**
     * Print previous package link
     */
    protected void navLinkPrevious() {
        if (prev == null) {
            printText("doclet.Prev_Class");
        } else {
            printLink(new LinkInfoImpl(LinkInfoImpl.CONTEXT_CLASS, prev, "",
                configuration.getText("doclet.Prev_Class"), true));
        }
    }

    /**
     * Print next package link
     */
    protected void navLinkNext() {
        if (next == null) {
            printText("doclet.Next_Class");
        } else {
            printLink(new LinkInfoImpl(LinkInfoImpl.CONTEXT_CLASS, next, "",
                configuration.getText("doclet.Next_Class"), true));
        }
    }

    /**
     * {@inheritDoc}
     */
    public void writeHeader(String header) {
        String pkgname = (classDoc.containingPackage() != null)?
            classDoc.containingPackage().name(): "";
        String clname = classDoc.name();
        printHtmlHeader(clname,
            configuration.metakeywords.getMetaKeywords(classDoc), true);
        printTop();
        navLinks(true);
        hr();
        println("<!-- ======== START OF CLASS DATA ======== -->");
        h2();
        if (pkgname.length() > 0) {
            font("-1"); print(pkgname); fontEnd(); br();
        }
        LinkInfoImpl linkInfo = new LinkInfoImpl( LinkInfoImpl.CONTEXT_CLASS_HEADER,
            classDoc, false);
        //Let's not link to ourselves in the header.
        linkInfo.linkToSelf = false;
        print(header + getTypeParameterLinks(linkInfo));
        h2End();
    }

    /**
     * {@inheritDoc}
     */
    public void writeFooter() {
        println("<!-- ========= END OF CLASS DATA ========= -->");
        hr();
        navLinks(false);
        printBottom();
        printBodyHtmlEnd();
    }

    /**
     * {@inheritDoc}
     */
    public void writeClassSignature(String modifiers) {
        boolean isInterface = classDoc.isInterface();
        dl();
        dt();
        preNoNewLine();
        writeAnnotationInfo(classDoc);
        print(modifiers);
        LinkInfoImpl linkInfo = new LinkInfoImpl(
            LinkInfoImpl.CONTEXT_CLASS_SIGNATURE, classDoc, false);
        //Let's not link to ourselves in the signature.
        linkInfo.linkToSelf = false;
        String name = classDoc.name() +
            getTypeParameterLinks(linkInfo);
        if (configuration().linksource) {
            printSrcLink(classDoc, name);
        } else {
            bold(name);
        }
        if (!isInterface) {
            Type superclass = Util.getFirstVisibleSuperClass(classDoc,
                configuration());
            if (superclass != null) {
                dt();
                print("extends ");
                printLink(new LinkInfoImpl(
                    LinkInfoImpl.CONTEXT_CLASS_SIGNATURE_PARENT_NAME,
                    superclass));
            }
        }
        Type[] implIntfacs = classDoc.interfaceTypes();
        if (implIntfacs != null && implIntfacs.length > 0) {
            int counter = 0;
            for (int i = 0; i < implIntfacs.length; i++) {
                ClassDoc classDoc = implIntfacs[i].asClassDoc();
                if (! (classDoc.isPublic() ||
                    Util.isLinkable(classDoc, configuration()))) {
                    continue;
                }
                if (counter == 0) {
                    dt();
                    print(isInterface? "extends " : "implements ");
                } else {
                    print(", ");
                }
                printLink(new LinkInfoImpl(
                    LinkInfoImpl.CONTEXT_CLASS_SIGNATURE_PARENT_NAME,
                    implIntfacs[i]));
                counter++;
            }
        }
        dlEnd();
        preEnd();
        p();
    }

    /**
     * {@inheritDoc}
     */
    public void writeClassDescription() {
        if(!configuration.nocomment) {
            // generate documentation for the class.
            if (classDoc.inlineTags().length > 0) {
                printInlineComment(classDoc);
                p();
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    public void writeClassTagInfo() {
        if(!configuration.nocomment) {
            // Print Information about all the tags here
            printTags(classDoc);
            hr();
            p();
        } else {
            hr();
        }
    }

    /**
     * {@inheritDoc}
     */
    public void writeClassDeprecationInfo() {
        hr();
        Tag[] deprs = classDoc.tags("deprecated");
        if (Util.isDeprecated(classDoc)) {
            boldText("doclet.Deprecated");
            if (deprs.length > 0) {
                Tag[] commentTags = deprs[0].inlineTags();
                if (commentTags.length > 0) {
                    space();
                    printInlineDeprecatedComment(classDoc, deprs[0]);
                }
            }
            p();
        }
    }

    /**
     * Generate the indent and get the line image for the class tree.
     * For user accessibility, the image includes the alt attribute
     * "extended by".  (This method is not intended for a class
     * implementing an interface, where "implemented by" would be required.)
     *
     * indent  integer indicating the number of spaces to indent
     */
    private void writeStep(int indent) {
        print(spaces(4 * indent - 2));
        print("<IMG SRC=\"" + relativepathNoSlash + "/resources/inherit.gif\" " +
              "ALT=\"" + configuration.getText("doclet.extended_by") + " \">");
    }

    /**
     * Print the class hierarchy tree for the given class.
     * @param type the class to print the hierarchy for.
     * @return return the amount that should be indented in
     * the next level of the tree.
     */
    private int writeTreeForClassHelper(Type type) {
        Type sup = Util.getFirstVisibleSuperClass(
            type instanceof ClassDoc ? (ClassDoc) type : type.asClassDoc(),
            configuration());
        int indent = 0;
        if (sup != null) {
            indent = writeTreeForClassHelper(sup);
            writeStep(indent);
        }

        if (type.equals(classDoc)) {
            String typeParameters = getTypeParameterLinks(
                new LinkInfoImpl(
                    LinkInfoImpl.CONTEXT_TREE,
                    classDoc, false));
            if (configuration.shouldExcludeQualifier(
                    classDoc.containingPackage().name())) {
                bold(type.asClassDoc().name() + typeParameters);
            } else {
                bold(type.asClassDoc().qualifiedName() + typeParameters);
            }
        } else {
            print(getLink(new LinkInfoImpl(LinkInfoImpl.CONTEXT_CLASS_TREE_PARENT,
                    type instanceof ClassDoc ? (ClassDoc) type : type,
                    configuration.getClassName(type.asClassDoc()), false)));
        }
        println();
        return indent + 1;
    }

    /**
     * Print the class hierarchy tree for this class only.
     */
    public void writeClassTree() {
        if (! classDoc.isClass()) {
            return;
        }
        pre();
        writeTreeForClassHelper(classDoc);
        preEnd();
    }

    /**
     * Write the type parameter information.
     */
    public void writeTypeParamInfo() {
        if (classDoc.typeParamTags().length > 0) {
            dl();
            dt();
            TagletOutput output = (new ParamTaglet()).getTagletOutput(classDoc,
                getTagletWriterInstance(false));
            print(output.toString());
            dlEnd();
        }
    }

    /**
     * {@inheritDoc}
     */
    public void writeSubClassInfo() {
        if (classDoc.isClass()) {
            if (classDoc.qualifiedName().equals("java.lang.Object") ||
                classDoc.qualifiedName().equals("org.omg.CORBA.Object")) {
                return;    // Don't generate the list, too huge
            }
            List subclasses = classtree.subs(classDoc, false);
            if (subclasses.size() > 0) {
                dl();
                dt();
                boldText("doclet.Subclasses");
                writeClassLinks(LinkInfoImpl.CONTEXT_SUBCLASSES,
                    subclasses);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    public void writeSubInterfacesInfo() {
        if (classDoc.isInterface()) {
            List subInterfaces = classtree.allSubs(classDoc, false);
            if (subInterfaces.size() > 0) {
                dl();
                dt();
                boldText("doclet.Subinterfaces");
                writeClassLinks(LinkInfoImpl.CONTEXT_SUBINTERFACES,
                    subInterfaces);
            }
        }
    }

    /**
     * If this is the interface which are the classes, that implement this?
     */
    public void writeInterfaceUsageInfo () {
        if (! classDoc.isInterface()) {
            return;
        }
        if (classDoc.qualifiedName().equals("java.lang.Cloneable") ||
            classDoc.qualifiedName().equals("java.io.Serializable")) {
            return;   // Don't generate the list, too big
        }
        List implcl = classtree.implementingclasses(classDoc);
        if (implcl.size() > 0) {
            dl();
            dt();
            boldText("doclet.Implementing_Classes");
            writeClassLinks(LinkInfoImpl.CONTEXT_IMPLEMENTED_CLASSES,
                implcl);
        }
    }

    /**
     * {@inheritDoc}
     */
    public void writeImplementedInterfacesInfo() {
        //NOTE:  we really should be using ClassDoc.interfaceTypes() here, but
        //       it doesn't walk up the tree like we want it to.
        List interfaceArray = Util.getAllInterfaces(classDoc, configuration);
        if (classDoc.isClass() && interfaceArray.size() > 0) {
            dl();
            dt();
            boldText("doclet.All_Implemented_Interfaces");
            writeClassLinks(LinkInfoImpl.CONTEXT_IMPLEMENTED_INTERFACES,
                interfaceArray);
        }
    }

    /**
     * {@inheritDoc}
     */
    public void writeSuperInterfacesInfo() {
        //NOTE:  we really should be using ClassDoc.interfaceTypes() here, but
        //       it doesn't walk up the tree like we want it to.
        List interfaceArray = Util.getAllInterfaces(classDoc, configuration);
        if (classDoc.isInterface() && interfaceArray.size() > 0) {
            dl();
            dt();
            boldText("doclet.All_Superinterfaces");
            writeClassLinks(LinkInfoImpl.CONTEXT_SUPER_INTERFACES,
                interfaceArray);
        }
    }

    /**
     * Generate links to the given classes.
     */
    private void writeClassLinks(int context, List list) {
        Object[] typeList = list.toArray();
        //Sort the list to be printed.
        print(' ');
        dd();
        for (int i = 0; i < list.size(); i++) {
            if (i > 0) {
                print(", ");
            }
            if (typeList[i] instanceof ClassDoc) {
                printLink(new LinkInfoImpl(context, (ClassDoc)(typeList[i])));

            } else {
                printLink(new LinkInfoImpl(context, (Type)(typeList[i])));
            }
        }
        ddEnd();
        dlEnd();
    }

    protected void navLinkTree() {
        navCellStart();
        printHyperLink("package-tree.html", "",
            configuration.getText("doclet.Tree"), true, "NavBarFont1");
        navCellEnd();
    }

    protected void printSummaryDetailLinks() {
        try {
            tr();
            tdVAlignClass("top", "NavBarCell3");
            font("-2");
            print("  ");
            navSummaryLinks();
            fontEnd();
            tdEnd();
            tdVAlignClass("top", "NavBarCell3");
            font("-2");
            navDetailLinks();
            fontEnd();
            tdEnd();
            trEnd();
        } catch (Exception e) {
            e.printStackTrace();
            throw new DocletAbortException();
        }
    }

    protected void navSummaryLinks() throws Exception {
        printText("doclet.Summary");
        space();
        MemberSummaryBuilder memberSummaryBuilder = (MemberSummaryBuilder)
            configuration.getBuilderFactory().getMemberSummaryBuilder(this);
        String[] navLinkLabels =  new String[] {
            "doclet.navNested", "doclet.navEnum", "doclet.navField", "doclet.navConstructor",
                "doclet.navMethod"
        };
        for (int i = 0; i < navLinkLabels.length; i++ ) {
            if (i == VisibleMemberMap.ENUM_CONSTANTS && ! classDoc.isEnum()) {
                continue;
            }
            if (i == VisibleMemberMap.CONSTRUCTORS && classDoc.isEnum()) {
                continue;
            }
            AbstractMemberWriter writer =
                ((AbstractMemberWriter) memberSummaryBuilder.
                    getMemberSummaryWriter(i));
            if (writer == null) {
                printText(navLinkLabels[i]);
            } else {
                writer.navSummaryLink(
                    memberSummaryBuilder.members(i),
                    memberSummaryBuilder.getVisibleMemberMap(i));
            }
            if (i < navLinkLabels.length-1) {
                navGap();
            }
        }
    }

    /**
     * Method navDetailLinks
     *
     * @throws   Exception
     *
     */
    protected void navDetailLinks() throws Exception {
        printText("doclet.Detail");
        space();
        MemberSummaryBuilder memberSummaryBuilder = (MemberSummaryBuilder)
            configuration.getBuilderFactory().getMemberSummaryBuilder(this);
        String[] navLinkLabels =  new String[] {
            "doclet.navNested", "doclet.navEnum", "doclet.navField", "doclet.navConstructor",
                "doclet.navMethod"
        };
        for (int i = 1; i < navLinkLabels.length; i++ ) {
            AbstractMemberWriter writer =
                ((AbstractMemberWriter) memberSummaryBuilder.
                    getMemberSummaryWriter(i));
            if (i == VisibleMemberMap.ENUM_CONSTANTS && ! classDoc.isEnum()) {
                continue;
            }
            if (i == VisibleMemberMap.CONSTRUCTORS && classDoc.isEnum()) {
                continue;
            }
            if (writer == null) {
                printText(navLinkLabels[i]);
            } else {
                writer.navDetailLink(memberSummaryBuilder.members(i));
            }
            if (i < navLinkLabels.length - 1) {
                navGap();
            }
        }
    }

    protected void navGap() {
        space();
        print('|');
        space();
    }

    /**
     * If this is an inner class or interface, write the enclosing class or
     * interface.
     */
    public void writeNestedClassInfo() {
        ClassDoc outerClass = classDoc.containingClass();
        if (outerClass != null) {
            dl();
            dt();
            if (outerClass.isInterface()) {
                boldText("doclet.Enclosing_Interface");
            } else {
                boldText("doclet.Enclosing_Class");
            }
            dd();
            printLink(new LinkInfoImpl(LinkInfoImpl.CONTEXT_CLASS, outerClass,
                false));
            ddEnd();
            dlEnd();
        }
    }

    /**
     * Return the classDoc being documented.
     *
     * @return the classDoc being documented.
     */
    public ClassDoc getClassDoc() {
        return classDoc;
    }

    /**
     * {@inheritDoc}
     */
    public void completeMemberSummaryBuild() {
        p();
    }
}
