/*
 * $Id$
 *
 * SARL is an general-purpose agent programming language.
 * More details on http://www.sarl.io
 *
 * Copyright (C) 2014-2026 SARL.io, the original authors and main authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *------- FORKED SOURCE CODE:
 *
 * THIS CODE IS FORKED FROM JDK.JAVADOC INTERNAL PACKAGE AND ADAPTED TO THE SARL PURPOSE.
 * THE FORK WAS NECESSARY BECAUSE IT IS IMPOSSIBLE TO SUBCLASS THE TYPES FOR THE.
 * STANDARD HTML DOCLET THAT IS PROVIDED BY JDK.JAVADOC MODULE.
 *
 * Copyright (c) 2003, 2021, 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.
 */

package io.sarl.docs.doclet2.html.framework;

import static com.sun.source.doctree.DocTree.Kind.CODE;

import java.net.URI;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Pattern;

import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.ModuleElement;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.SimpleAnnotationValueVisitor9;
import javax.lang.model.util.SimpleTypeVisitor9;
import javax.tools.Diagnostic;
import javax.tools.Diagnostic.Kind;
import javax.tools.JavaFileObject;

import org.eclipse.xtext.xbase.lib.Procedures.Procedure0;
import org.jsoup.nodes.Comment;
import org.jsoup.nodes.DataNode;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.DocumentType;
import org.jsoup.nodes.Element;
import org.jsoup.nodes.Node;
import org.jsoup.nodes.TextNode;

import com.google.common.base.Strings;
import com.google.common.collect.Iterables;
import com.google.inject.Inject;
import com.sun.source.doctree.AttributeTree;
import com.sun.source.doctree.AttributeTree.ValueKind;
import com.sun.source.doctree.AuthorTree;
import com.sun.source.doctree.CommentTree;
import com.sun.source.doctree.DeprecatedTree;
import com.sun.source.doctree.DocCommentTree;
import com.sun.source.doctree.DocRootTree;
import com.sun.source.doctree.DocTree;
import com.sun.source.doctree.DocTreeVisitor;
import com.sun.source.doctree.EndElementTree;
import com.sun.source.doctree.EntityTree;
import com.sun.source.doctree.ErroneousTree;
import com.sun.source.doctree.IdentifierTree;
import com.sun.source.doctree.InheritDocTree;
import com.sun.source.doctree.LinkTree;
import com.sun.source.doctree.LiteralTree;
import com.sun.source.doctree.ParamTree;
import com.sun.source.doctree.ReferenceTree;
import com.sun.source.doctree.ReturnTree;
import com.sun.source.doctree.SeeTree;
import com.sun.source.doctree.SerialDataTree;
import com.sun.source.doctree.SerialFieldTree;
import com.sun.source.doctree.SerialTree;
import com.sun.source.doctree.SinceTree;
import com.sun.source.doctree.StartElementTree;
import com.sun.source.doctree.TextTree;
import com.sun.source.doctree.ThrowsTree;
import com.sun.source.doctree.UnknownBlockTagTree;
import com.sun.source.doctree.UnknownInlineTagTree;
import com.sun.source.doctree.ValueTree;
import com.sun.source.doctree.VersionTree;

import io.sarl.docs.doclet2.framework.ElementUtils;
import io.sarl.docs.doclet2.framework.ExternalLinkManager;
import io.sarl.docs.doclet2.framework.TagletManager;
import io.sarl.docs.doclet2.html.taglets.SarlTaglet;
import io.sarl.docs.doclet2.html.taglets.inline.CodeTaglet;
import io.sarl.docs.doclet2.html.taglets.inline.DocRootTaglet;
import io.sarl.docs.doclet2.html.taglets.inline.InheritDocTaglet;
import io.sarl.docs.doclet2.html.taglets.inline.LinkTaglet;
import io.sarl.docs.doclet2.html.taglets.inline.LiteralTaglet;
import io.sarl.docs.doclet2.html.taglets.inline.ValueTaglet;
import io.sarl.lang.core.annotation.DefaultValue;
import io.sarl.lang.services.SARLGrammarKeywordAccess;
import jdk.javadoc.doclet.Taglet;
import jdk.javadoc.doclet.Taglet.Location;

/** Builder of HTML elements.
 *
 * @author $Author: sgalland$
 * @version $FullVersion$
 * @mavengroupid $GroupId$
 * @mavenartifactid $ArtifactId$
 * @since 0.13
 */
public class HtmlFactoryImpl implements HtmlFactory, HtmlTags {

	private static final String SPACE = " "; //$NON-NLS-1$

	private HtmlAccessor accessor;

	private PathBuilder pathBuilder;

	private ElementUtils elementUtils;

	private ExternalLinkManager externalLinkManager;

	private TagletManager tagletManager;

	private SARLGrammarKeywordAccess keywords;

	private long nextId = 1;

	private long nextTabContentClass = 1;

	private long nextTabButtonClass = 1;

	/** Compute an unique ID.
	 *
	 * @return the string representation of the ID.
	 */
	protected String computeId() {
		final long id = this.nextId;
		++this.nextId;
		return "ID" + id; //$NON-NLS-1$
	}

	/** Compute an unique tab content class name.
	 *
	 * @return the string representation of the class name.
	 */
	protected String computeTabContentClassname() {
		final long id = this.nextTabContentClass;
		++this.nextTabContentClass;
		return "doclet-tabs-content-instance-" + id; //$NON-NLS-1$
	}

	/** Compute an unique tab button class name.
	 *
	 * @return the string representation of the class name.
	 */
	protected String computeTabButtonClassname() {
		final long id = this.nextTabButtonClass;
		++this.nextTabButtonClass;
		return "doclet-tabs-button-instance-" + id; //$NON-NLS-1$
	}

	/** Change the taglet manager.
	 *
	 * @param manager the manager.
	 */
	@Inject
	public void setTagletManager(TagletManager manager) {
		this.tagletManager = manager;
	}

	/** Replies the taglet manager.
	 *
	 * @return the manager.
	 */
	public TagletManager getTagletManager() {
		return this.tagletManager;
	}

	/** Change the accessor to the SARL keywords.
	 *
	 * @param accessor is the accessor.
	 */
	@Inject
	public void setSARLGrammarKeywordAccess(SARLGrammarKeywordAccess accessor) {
		this.keywords = accessor;
	}

	/** Replies the accessor to the SARL keywords.
	 *
	 * @return the accessor.
	 */
	public SARLGrammarKeywordAccess getSARLGrammarKeywordAccess() {
		return this.keywords;
	}

	/** Change the external link manager.
	 *
	 * @param manager the manager.
	 */
	@Inject
	public void setExternalLinkManager(ExternalLinkManager manager) {
		this.externalLinkManager = manager;
	}

	/** Replies the external link manager.
	 *
	 * @return the manager.
	 */
	public ExternalLinkManager getExternalLinkManager() {
		return this.externalLinkManager;
	}

	/** Change the element utilities.
	 *
	 * @param utils the element utilities.
	 */
	@Inject
	public void setElementUtils(ElementUtils utils) {
		this.elementUtils = utils;
	}

	/** Replies the element utilities.
	 *
	 * @return the utilities.
	 */
	public ElementUtils getElementUtils() {
		return this.elementUtils;
	}

	/** Change the HTML accessor.
	 *
	 * @param accessor the HTML accessor.
	 */
	@Inject
	public void setHtmlAccessor(HtmlAccessor accessor) {
		this.accessor = accessor;
	}

	/** Replies the HTML accessor.
	 *
	 * @return the accessor.
	 */
	public HtmlAccessor getHtmlAccessor() {
		return this.accessor;
	}

	/** Change the builder of paths.
	 *
	 * @param builder the builder.
	 */
	@Inject
	public void setPathBuilder(PathBuilder builder) {
		this.pathBuilder = builder;
	}

	/** Replies the builder of paths.
	 *
	 * @return the builder.
	 */
	public PathBuilder getPathBuilder() {
		return this.pathBuilder;
	}

	@Override
	public DocumentType createDocumentType(String name, String publicId, String systemId) {
		if (!Strings.isNullOrEmpty(name) && !Strings.isNullOrEmpty(publicId) && !Strings.isNullOrEmpty(systemId)) {
			final DocumentType type = new DocumentType(name, publicId, systemId);
			return type;
		}
		return null;
	}

	@Override
	public Document createDocument(DocumentType docType, String charset, HtmlFactoryContext context) {
		Document doc = Document.createShell(context.getBaseUri());
		//
		if (docType != null) {
			doc.insertChildren(0, docType);
		} else {
			doc.insertChildren(0, new DocumentType("html", "", "")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
		}
		//
		Element htmlTag = getHtmlAccessor().getChildNode(doc, HTML_TAG);
		if (htmlTag == null) {
			htmlTag = doc.appendElement(HTML_TAG);
		}
		htmlTag.attr(LANG_ATTR, DEFAULT_LANG_ATTR_VALUE);
		//
		Element headTag = getHtmlAccessor().getChildNode(htmlTag, HEAD_TAG);
		if (headTag == null) {
			headTag = htmlTag.appendElement(HEAD_TAG);
		}
		//
		Element metaTag = getHtmlAccessor().getChildNode(headTag, META_TAG);
		if (metaTag == null) {
			metaTag = headTag.appendElement(META_TAG);
		}
		metaTag.attr(CHARSET_ATTR, Strings.isNullOrEmpty(charset) ? Charset.defaultCharset().name() : charset);
		return doc;
	}

	@Override
	public Element createHeadTag(Element parent) {
		if (parent == null) {
			return new Element(HEAD_TAG);
		}
		Element child = getHtmlAccessor().getChildNode(parent, HEAD_TAG);
		if (child == null) {
			child = parent.appendElement(HEAD_TAG);
		}
		return child;
	}

	@Override
	public Element createTitleTag(Element parent, String title) {
		Element child;
		if (parent == null) {
			child = new Element(TITLE_TAG);
		} else {
			child = getHtmlAccessor().getChildNode(parent, TITLE_TAG);
			if (child == null) {
				child = parent.appendElement(TITLE_TAG);
			}
		}
		child.appendText(title);
		return child;
	}

	@Override
	public String path2UrlPath(Path path, String anchor) {
		final StringBuilder refPath = new StringBuilder();
		if (path != null) {
			final Iterator<Path> iterator = path.iterator();
			while (iterator.hasNext()) {
				final Path elt = iterator.next();
				if (refPath.length() > 0) {
					refPath.append("/"); //$NON-NLS-1$
				}
				refPath.append(elt.toString());
			}
			if (path.isAbsolute()) {
				refPath.insert(0, "/"); //$NON-NLS-1$
			}
		}
		if (!Strings.isNullOrEmpty(anchor)) {
			refPath.append("#"); //$NON-NLS-1$
			refPath.append(anchor);
		}
		return refPath.toString();
	}

	@Override
	public Element createCssLinkTag(Element parent, Path cssStyle) {
		final Element linkElement;
		if (parent == null) {
			linkElement = new Element(LINK_TAG);
		} else {
			linkElement = parent.appendElement(LINK_TAG);
		}
		linkElement.attr(REL_ATTR, REL_ATTR_VALUE);
		linkElement.attr(HREF_ATTR, path2UrlPath(cssStyle));
		return linkElement;
	}
	
	@Override
	public Element createJsLinkTag(Element parent, Path jsScript) {
		final Element scriptElement;
		if (parent == null) {
			scriptElement = new Element(SCRIPT_TAG);
		} else {
			scriptElement = parent.appendElement(SCRIPT_TAG);
		}
		scriptElement.attr(SRC_ATTR, path2UrlPath(jsScript));
		return scriptElement;
	}

	@Override
	public Element createBodyTag(Element parent) {
		Element child;
		if (parent == null) {
			child = new Element(BODY_TAG);
		} else {
			child = getHtmlAccessor().getChildNode(parent, BODY_TAG);
			if (child == null) {
				child = parent.appendElement(BODY_TAG);
			}
		}
		return child;
	}

	@Override
	public Element createFramesetTag(Element parent) {
		Element child;
		if (parent == null) {
			child = new Element(FRAMESET_TAG);
		} else {
			child = parent.appendElement(FRAMESET_TAG);
		}
		return child;
	}

	@Override
	public Element createFrameTag(Element parent) {
		Element child;
		if (parent == null) {
			child = new Element(FRAME_TAG);
		} else {
			child = parent.appendElement(FRAME_TAG);
		}
		return child;
	}

	@Override
	public Element createNoFramesTag(Element parent) {
		Element child;
		if (parent == null) {
			child = new Element(NOFRAMES_TAG);
		} else {
			child = parent.appendElement(NOFRAMES_TAG);
		}
		return child;
	}

	@Override
	public Element createScriptTag(Element parent, String type) {
		Element child;
		if (parent == null) {
			child = new Element(SCRIPT_TAG);
		} else {
			child = parent.appendElement(SCRIPT_TAG);
		}
		if (!Strings.isNullOrEmpty(type)) {
			child.attr(TYPE_ATTR, type);
		}
		return child;
	}

	@Override
	public Element createNoScriptTag(Element parent) {
		Element child;
		if (parent == null) {
			child = new Element(NOSCRIPT_TAG);
		} else {
			child = parent.appendElement(NOSCRIPT_TAG);
		}
		return child;
	}

	@Override
	public Element createH2Tag(Element parent, CssStyles style) {
		final Element elt;
		if (parent == null) {
			elt = new Element(H2_TAG);
		} else {
			elt = parent.appendElement(H2_TAG);
		}
		if (style != null) {
			elt.addClass(style.getCssClassname());
		}
		return elt;
	}

	@Override
	public Element createDivTag(Element parent, CssStyles style) {
		final Element elt;
		if (parent == null) {
			elt = new Element(DIV_TAG);
		} else {
			elt = parent.appendElement(DIV_TAG);
		}
		if (style != null) {
			elt.addClass(style.getCssClassname());
		}
		return elt;
	}

	@Override
	public Element createParagraphTag(Element parent, CssStyles style) {
		final Element elt;
		if (parent == null) {
			elt = new Element(P_TAG);
		} else {
			elt = parent.appendElement(P_TAG);
		}
		if (style != null) {
			elt.addClass(style.getCssClassname());
		}
		return elt;
	}

	@Override
	public Element createDlTag(Element parent, CssStyles style) {
		final Element elt;
		if (parent == null) {
			elt = new Element(DL_TAG);
		} else {
			elt = parent.appendElement(DL_TAG);
		}
		if (style != null) {
			elt.addClass(style.getCssClassname());
		}
		return elt;
	}

	@Override
	public Element createDtTag(Element parent, CssStyles style) {
		final Element elt;
		if (parent == null) {
			elt = new Element(DT_TAG);
		} else {
			elt = parent.appendElement(DT_TAG);
		}
		if (style != null) {
			elt.addClass(style.getCssClassname());
		}
		return elt;
	}

	@Override
	public Element createDdTag(Element parent, CssStyles style) {
		final Element elt;
		if (parent == null) {
			elt = new Element(DD_TAG);
		} else {
			elt = parent.appendElement(DD_TAG);
		}
		if (style != null) {
			elt.addClass(style.getCssClassname());
		}
		return elt;
	}

	@Override
	public Element createSpanTag(Element parent, CssStyles style) {
		final Element elt;
		if (parent == null) {
			elt = new Element(SPAN_TAG);
		} else {
			elt = parent.appendElement(SPAN_TAG);
		}
		if (style != null) {
			elt.addClass(style.getCssClassname());
		}
		return elt;
	}

	@Override
	public Element createPreTag(Element parent, CssStyles style) {
		final Element elt;
		if (parent == null) {
			elt = new Element(PRE_TAG);
		} else {
			elt = parent.appendElement(PRE_TAG);
		}
		if (style != null) {
			elt.addClass(style.getCssClassname());
		}
		return elt;
	}

	@Override
	public Element createUlTag(Element parent, CssStyles style) {
		final Element elt;
		if (parent == null) {
			elt = new Element(UL_TAG);
		} else {
			elt = parent.appendElement(UL_TAG);
		}
		if (style != null) {
			elt.addClass(style.getCssClassname());
		}
		return elt;
	}

	@Override
	public Element createLiTag(Element parent, CssStyles style) {
		final Element elt;
		if (parent == null) {
			elt = new Element(LI_TAG);
		} else {
			elt = parent.appendElement(LI_TAG);
		}
		if (style != null) {
			elt.addClass(style.getCssClassname());
		}
		return elt;
	}

	@Override
	public Node createUnsecableSpace(Element parent) {
		if (parent == null) {
			return new DataNode(SPACE_ENTITY);
		}
		return parent.append(SPACE_ENTITY);
	}

	@Override
	public Node createSecableSpace(Element parent) {
		if (parent == null) {
			return new TextNode(" "); //$NON-NLS-1$
		}
		return parent.appendText(" "); //$NON-NLS-1$
	}

	/** Append the given child to the given parent. This function ignores the pseudo tags
	 * and adds the children of a pseudo tag directly (not the pseudo tag itself).
	 *
	 * @param parent the receiver.
	 * @param child the node to add into the parent node.
	 * @see #appendChildren(Element, Iterable)
	 * @deprecated see {@link #appendChild(Element, Node)}
	 */
	@Deprecated(since = "0.15", forRemoval = true)
	protected final void appendChildren(Element parent, Node child) {
		appendChild(parent, child);
	}

	/** Append the given child to the given parent. This function ignores the pseudo tags
	 * and adds the children of a pseudo tag directly (not the pseudo tag itself).
	 *
	 * @param parent the receiver.
	 * @param child the node to add into the parent node.
	 * @see #appendChildren(Element, Iterable)
	 * @since 0.15
	 */
	@SuppressWarnings("static-method")
	protected void appendChild(Element parent, Node child) {
		assert parent != null : "parent argument must not be null"; //$NON-NLS-1$
		assert child != null : "child argument must not be null"; //$NON-NLS-1$
		if (HtmlTags.isPseudoTag(child.nodeName()) && child instanceof Element) {
			for (final Node chld : child.childNodes()) {
				parent.appendChild(chld.clone());
			}
		} else {
			parent.appendChild(child.clone());
		}
	}

	/** Append the given child to the given parent. This function ignores the pseudo tags
	 * and adds the children of a pseudo tag directly (not the pseudo tag itself).
	 *
	 * @param parent the receiver.
	 * @param children the nodes to add into the parent node.
	 * @see #appendChild(Element, Node)
	 */
	protected void appendChildren(Element parent, Iterable<? extends Node> children) {
		assert parent != null : "parent argument must not be null"; //$NON-NLS-1$
		assert children != null : "children argument must not be null"; //$NON-NLS-1$
		for (final Node child : children) {
			if (child != null) {
				appendChild(parent, child);
			}
		}
	}

	@Override
	public List<Node> createLink(Path path, String anchor, List<Node> label, CssStyles style) {
		final Element tag = new Element(A_TAG);
		tag.attr(HREF_ATTR, path2UrlPath(path, anchor));
		appendChildren(tag, label);
		if (style != null) {
			tag.addClass(style.getCssClassname());
		}
		final List<Node> list = new ArrayList<>();
		list.add(tag);
		return list;
	}

	@Override
	public List<Node> createLink(Path path, String anchor, String label, CssStyles style) {
		final Element tag = new Element(A_TAG);
		tag.attr(HREF_ATTR, path2UrlPath(path, anchor));
		tag.appendText(label);
		if (style != null) {
			tag.addClass(style.getCssClassname());
		}
		final List<Node> list = new ArrayList<>();
		list.add(tag);
		return list;
	}

	@Override
	public List<Node> createLink(URL path, List<Node> label, CssStyles style) {
		final Element tag = new Element(A_TAG);
		tag.attr(HREF_ATTR, path.toExternalForm());
		appendChildren(tag, label);
		if (style != null) {
			tag.addClass(style.getCssClassname());
		}
		final List<Node> list = new ArrayList<>();
		list.add(tag);
		return list;
	}

	@Override
	public List<Node> createLink(URL path, String label, CssStyles style) {
		final Element tag = new Element(A_TAG);
		tag.attr(HREF_ATTR, path.toExternalForm());
		tag.appendText(label);
		if (style != null) {
			tag.addClass(style.getCssClassname());
		}
		final List<Node> list = new ArrayList<>();
		list.add(tag);
		return list;
	}

	@Override
	public List<Node> createModuleLink(ModuleElement module, List<Node> label, CssStyles style, HtmlFactoryContext context) {
		final boolean included = context.getEnvironment().isIncluded(module);
		if (included) {
			final Path modulePath = getPathBuilder().moduleSummary(module);
			final Path linkModulePath = context.getPathToRoot().resolve(modulePath);
			return createLink(linkModulePath, label, style);
		}
		return label;
	}

	@Override
	public List<Node> createModuleLink(ModuleElement module, String label, CssStyles style, HtmlFactoryContext context) {
		final boolean included = context.getEnvironment().isIncluded(module);
		if (included) {
			final String rlabel;
			if (label == null || label.isBlank()) {
				rlabel = Messages.HtmlFactoryImpl_0;
			} else {
				rlabel = label;
			}
			final Path modulePath = getPathBuilder().moduleSummary(module);
			final Path linkModulePath = context.getPathToRoot().resolve(modulePath);
			return createLink(linkModulePath, rlabel, style);
		}
		return new ArrayList<>();
	}

	@Override
	public List<Node> createPackageLink(PackageElement pkg, List<Node> label, CssStyles style, HtmlFactoryContext context) {
		final boolean included = context.getEnvironment().isIncluded(pkg);
		if (included) {
			final Path packagePath = getPathBuilder().packageSummary(pkg);
			final Path linkPackagePath = context.getPathToRoot().resolve(packagePath);
			return createLink(linkPackagePath, label, style);
		}
		return label;
	}

	@Override
	public List<Node> createPackageLink(PackageElement pkg, String label, CssStyles style, HtmlFactoryContext context) {
		final boolean included = context.getEnvironment().isIncluded(pkg);
		if (included) {
			final String rlabel;
			if (label == null || label.isBlank()) {
				rlabel = Messages.HtmlFactoryImpl_1;
			} else {
				rlabel = label;
			}
			final Path packagePath = getPathBuilder().packageSummary(pkg);
			final Path linkPackagePath = context.getPathToRoot().resolve(packagePath);
			return createLink(linkPackagePath, rlabel, style);
		}
		return new ArrayList<>();
	}

	@Override
	public List<Node> createTypeLink(TypeElement element, String anchor, List<Node> label, CssStyles style, HtmlFactoryContext context) {
		if (getElementUtils().isExternal(element, context.getEnvironment())) {
			final URL externalLink = getExternalLinkManager().getExternalURL(element, anchor, context);
			if (externalLink != null) {
				return createLink(externalLink, label, style);
			}
		} else if (context.getEnvironment().isIncluded(element)) {
			final Path typePath = getPathBuilder().typeIndex(element);
			final Path linkTypePath = context.getPathToRoot().resolve(typePath);
			return createLink(linkTypePath, anchor, label, style);
		} else {
			context.getReporter().print(Kind.MANDATORY_WARNING, MessageFormat.format(Messages.HtmlFactoryImpl_12, element.getQualifiedName().toString()));
		}
		final Element tag = new Element(SPAN_TAG);
		appendChildren(tag, label);
		if (style != null) {
			tag.addClass(style.getCssClassname());
		}
		final List<Node> list = new ArrayList<>();
		list.add(tag);
		return list; 
	}

	@Override
	public List<Node> createTypeLink(TypeElement element, String anchor, List<Node> linkLabel, Node rawLabel, CssStyles style, HtmlFactoryContext context) {
		if (getElementUtils().isExternal(element, context.getEnvironment())) {
			final URL externalLink = getExternalLinkManager().getExternalURL(element, anchor, context);
			if (externalLink != null) {
				return createLink(externalLink, linkLabel, style);
			}
		} else if (context.getEnvironment().isIncluded(element)) {
			final Path typePath = getPathBuilder().typeIndex(element);
			final Path linkTypePath = context.getPathToRoot().resolve(typePath);
			return createLink(linkTypePath, anchor, linkLabel, style);
		} else {
			context.getReporter().print(Kind.MANDATORY_WARNING, MessageFormat.format(Messages.HtmlFactoryImpl_12, element.getQualifiedName().toString()));
		}
		final Element tag = new Element(SPAN_TAG);
		appendChildren(tag, rawLabel);
		if (style != null) {
			tag.addClass(style.getCssClassname());
		}
		final List<Node> list = new ArrayList<>();
		list.add(tag);
		return list; 
	}

	@Override
	public List<Node> createTypeLink(TypeElement element, String anchor, String linkLabel, String rawLabel, CssStyles style, HtmlFactoryContext context) {
		if (getElementUtils().isExternal(element, context.getEnvironment())) {
			final URL externalLink = getExternalLinkManager().getExternalURL(element, anchor, context);
			if (externalLink != null) {
				return createLink(externalLink, linkLabel, style);
			}
		} else if (context.getEnvironment().isIncluded(element)) {
			final Path typePath = getPathBuilder().typeIndex(element);
			final Path linkTypePath = context.getPathToRoot().resolve(typePath);
			return createLink(linkTypePath, anchor, linkLabel, style);
		} else {
			context.getReporter().print(Kind.MANDATORY_WARNING, MessageFormat.format(Messages.HtmlFactoryImpl_12, element.getQualifiedName().toString()));
		}
		final Element tag = new Element(SPAN_TAG);
		tag.appendText(rawLabel);
		if (style != null) {
			tag.addClass(style.getCssClassname());
		}
		final List<Node> list = new ArrayList<>();
		list.add(tag);
		return list; 
	}

	@Override
	public List<Node> createTypeLink(TypeMirror type, String anchor, List<Node> label, CssStyles style, HtmlFactoryContext context) {
		final List<Node> baseType = filterType(type);
		if (baseType != null) {
			return baseType;
		}
		final TypeElement element = getElementUtils().asTypeElement(type, context.getEnvironment().getTypeUtils());
		if (element == null) {
			final List<Node> list = new ArrayList<>();
			list.add(new TextNode(type.toString()));
			return list; 
		}
		return createTypeLink(element, anchor, label, style, context);
	}

	@Override
	public List<Node> createTypeLink(TypeMirror type, String anchor, String label, CssStyles style, HtmlFactoryContext context) {
		final List<Node> baseType = filterType(type);
		if (baseType != null) {
			return baseType;
		}
		final TypeElement element = getElementUtils().asTypeElement(type, context.getEnvironment().getTypeUtils());
		if (element == null) {
			final List<Node> list = new ArrayList<>();
			list.add(new TextNode(type.toString()));
			return list; 
		}
		return createTypeLink(element, anchor, label, label, style, context);
	}

	@Override
	public List<Node> createTypeLink(TypeElement element, String anchor, boolean addTypeParameters, CssStyles style, HtmlFactoryContext context) {
		final List<Node> link = createTypeLink(element, anchor,
				element.getSimpleName().toString(),
				element.getQualifiedName().toString(),
				style,
				context);
		if (addTypeParameters) {
			addTypeParameters(element, link);
		}
		return link;
	}

	private void addTypeParameters(TypeElement element, List<? super Node> link) {
		if (!element.getTypeParameters().isEmpty()) {
			link.add(new TextNode(getSARLGrammarKeywordAccess().getLessThanSignKeyword()));
			boolean first = true;
			for (final TypeParameterElement parameter : element.getTypeParameters()) {
				if (first) {
					first = false;
				} else {
					link.add(new TextNode(getSARLGrammarKeywordAccess().getCommaKeyword()));
				}
				link.add(new TextNode(parameter.getSimpleName().toString()));
			}
			link.add(new TextNode(getSARLGrammarKeywordAccess().getGreaterThanSignKeyword()));
		}
	}

	private static boolean isVoidType(TypeMirror type) {
		return type == null || type.getKind() == TypeKind.VOID || type.getKind() == TypeKind.NULL;
	}
	
	private List<Node> filterType(TypeMirror type) {
		if (isVoidType(type)) {
			final List<Node> list = new ArrayList<>();
			list.add(new TextNode(getSARLGrammarKeywordAccess().getVoidKeyword()));
			return list; 
		}
		if (type.getKind().isPrimitive() || type.getKind() == TypeKind.TYPEVAR) {
			final List<Node> list = new ArrayList<>();
			list.add(new TextNode(type.toString()));
			return list; 
		}
		return null;
	}
	
	@Override
	public List<Node> createTypeLink(TypeMirror type, String anchor, boolean addTypeParameters, CssStyles style, HtmlFactoryContext context) {
		final List<Node> baseType = filterType(type);
		if (baseType != null) {
			return baseType;
		}
		final TypeElement element = getElementUtils().asTypeElement(type, context.getEnvironment().getTypeUtils());
		if (element == null) {
			final List<Node> list = new ArrayList<>();
			list.add(new TextNode(type.toString()));
			return list; 
		}
		return createTypeLink(element, anchor, addTypeParameters, style, context);
	}

	/** Replies the name of the anchor for the given member variable.
	 *
	 * @param element the member.
	 * @return the anchor name.
	 */
	@Override
	public String toVariableAnchor(VariableElement element) {
		return element.getSimpleName().toString();
	}

	@Override
	public List<Node> createVariableLink(VariableElement variable, List<Node> label, CssStyles style, HtmlFactoryContext context) {
		final String anchorName = toVariableAnchor(variable);
		if (getElementUtils().isExternal(variable, context.getEnvironment())) {
			final URL externalLink = getExternalLinkManager().getExternalURL(variable.getEnclosingElement(), anchorName, context);
			if (externalLink != null) {
				return createLink(externalLink, label, style);
			}
		} else if (context.getEnvironment().isIncluded(variable)) {
			final Path typePath = getPathBuilder().typeIndex((TypeElement) variable.getEnclosingElement());
			final Path linkTypePath = context.getPathToRoot().resolve(typePath);
			return createLink(linkTypePath, anchorName, label, style);
		} else {
			context.getReporter().print(Kind.MANDATORY_WARNING, MessageFormat.format(Messages.HtmlFactoryImpl_12, getElementUtils().getFullyQualifiedName(variable, true)));
		}
		final Element tag = new Element(SPAN_TAG);
		tag.appendChildren(label);
		if (style != null) {
			tag.addClass(style.getCssClassname());
		}
		final List<Node> list = new ArrayList<>();
		list.add(tag);
		return list; 
	}

	@Override
	public List<Node> createVariableLink(VariableElement variable, String label, CssStyles style, HtmlFactoryContext context) {
		final String anchorName = toVariableAnchor(variable);
		if (getElementUtils().isExternal(variable, context.getEnvironment())) {
			final URL externalLink = getExternalLinkManager().getExternalURL(variable.getEnclosingElement(), anchorName, context);
			if (externalLink != null) {
				return createLink(externalLink, label, style);
			}
		} else if (context.getEnvironment().isIncluded(variable)) {
			final Path typePath = getPathBuilder().typeIndex((TypeElement) variable.getEnclosingElement());
			final Path linkTypePath = context.getPathToRoot().resolve(typePath);
			return createLink(linkTypePath, anchorName, label, style);
		} else {
			context.getReporter().print(Kind.MANDATORY_WARNING, MessageFormat.format(Messages.HtmlFactoryImpl_12, getElementUtils().getFullyQualifiedName(variable, true)));
		}
		final Element tag = new Element(SPAN_TAG);
		tag.appendText(label);
		if (style != null) {
			tag.addClass(style.getCssClassname());
		}
		final List<Node> list = new ArrayList<>();
		list.add(tag);
		return list; 
	}

	@Override
	public String toExecutableAnchor(ExecutableElement element) {
		final String simpleName = element.getSimpleName().toString();
		final String parameters = toFlatParameterList(element.getParameters());
		final StringBuilder buf = new StringBuilder();
		buf.append(simpleName);
		buf.append("("); //$NON-NLS-1$
		buf.append(parameters);
		buf.append(")"); //$NON-NLS-1$
		return buf.toString();
	}

	@Override
	public String toEventHandlerAnchor(ExecutableElement element) {
		final String simpleName = element.getSimpleName().toString();
		final String parameters = toFlatParameterList(element.getParameters());
