/*
 * $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.types;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.file.Path;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.function.Consumer;

import javax.lang.model.AnnotatedConstruct;
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.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.SimpleAnnotationValueVisitor14;
import javax.lang.model.util.SimpleAnnotationValueVisitor9;
import javax.lang.model.util.SimpleElementVisitor9;
import javax.tools.Diagnostic.Kind;

import com.google.common.base.Strings;
import com.google.common.collect.Iterables;
import com.sun.source.doctree.DocTree;
import jdk.javadoc.doclet.Reporter;
import jdk.javadoc.doclet.Taglet.Location;
import org.jsoup.nodes.Element;
import org.jsoup.nodes.Node;
import org.jsoup.nodes.TextNode;

import io.sarl.docs.doclet2.framework.SarlDocletEnvironment;
import io.sarl.docs.doclet2.html.framework.AbstractDocumentationGenerator;
import io.sarl.docs.doclet2.html.framework.CssStyles;
import io.sarl.docs.doclet2.html.framework.DocletOptions;
import io.sarl.docs.doclet2.html.framework.Navigation;
import io.sarl.docs.doclet2.html.framework.Navigation.NavigationKind;
import io.sarl.docs.doclet2.html.taglets.inline.ValueTaglet;
import io.sarl.lang.core.annotation.FiredEvent;
import io.sarl.lang.core.annotation.ImportedCapacityFeature;
import io.sarl.lang.core.annotation.PerceptGuardEvaluator;

/** Abstract implementation of a generator for the documentation for a type.
 *
 * @author $Author: sgalland$
 * @version $FullVersion$
 * @mavengroupid $GroupId$
 * @mavenartifactid $ArtifactId$
 * @since 0.13
 */
public abstract class AbstractTypeDocumentationGenerator extends AbstractDocumentationGenerator implements TypeDocumentationGenerator {

	/** Id for the event handler details' box.
	 */
	public static final String ID_EVENT_HANDLER_DETAILS = "event.handlers.details.box"; //$NON-NLS-1$

	/** Id for the action details' box.
	 */
	public static final String ID_ACTION_DETAILS = "action.details.box"; //$NON-NLS-1$

	/** Id for the constructor details' box.
	 */
	public static final String ID_CONSTRUCTOR_DETAILS = "constructors.details.box"; //$NON-NLS-1$

	/** Id for the field details' box.
	 */
	public static final String ID_FIELD_DETAILS = "fields.details.box"; //$NON-NLS-1$

	/** Id for the property details' box.
	 */
	public static final String ID_PROPERTY_DETAILS = "properties.details.box"; //$NON-NLS-1$

	/** Id for the enumeration constant details' box.
	 */
	public static final String ID_ENUM_CONSTANT_DETAILS = "enumeration.constants.details.box"; //$NON-NLS-1$

	/** Id for the capacity use summary' box.
	 */
	public static final String ID_CAPACITY_USE_SUMMARY = "capacity.uses.summary.box"; //$NON-NLS-1$

	/** Id for the property summary' box.
	 */
	public static final String ID_PROPERTY_SUMMARY = "properties.summary.box"; //$NON-NLS-1$

	/** Id for the field summary' box.
	 */
	public static final String ID_FIELD_SUMMARY = "fields.summary.box"; //$NON-NLS-1$

	/** Id for the field summary' box.
	 */
	public static final String ID_ENUM_CONSTANT_SUMMARY = "enumeration.constants.summary.box"; //$NON-NLS-1$

	/** Id for the nested class summary' box.
	 */
	public static final String ID_NESTED_CLASS_SUMMARY = "nested.classes.summary.box"; //$NON-NLS-1$

	/** Id for the constructor summary' box.
	 */
	public static final String ID_CONSTRUCTOR_SUMMARY = "constructors.summary.box"; //$NON-NLS-1$

	/** Id for the action summary' box.
	 */
	public static final String ID_ACTION_SUMMARY = "actions.summary.box"; //$NON-NLS-1$

	/** Id for the event handler summary' box.
	 */
	public static final String ID_EVENT_HANDLER_SUMMARY = "event.handlers.summary.box"; //$NON-NLS-1$

	@Override
	protected void initNavigation(Navigation navigation) {
		navigation.setKind(NavigationKind.TYPE);
	}

	@Override
	protected String getDocumentTitleFor(String elementName) {
		return MessageFormat.format(Messages.AbstractTypeDocumentationGenerator_3, elementName);
	}

	@Override
	public final void generate(TypeElement type, Collection<Path> cssStylesheets, Collection<Path> jsScripts, SarlDocletEnvironment environment, DocletOptions cliOptions, Reporter reporter) throws Exception {
		try {
			initGenerator(cssStylesheets, jsScripts, reporter, environment, cliOptions);
			getReporter().print(Kind.NOTE, MessageFormat.format(Messages.AbstractTypeDocumentationGenerator_0, type.getQualifiedName().toString()));
			//
			computePaths(type.getQualifiedName().toString(), true);
			final var outputPath = cliOptions.getOutputDirectory().resolve(getRelativePath());
			//
			final var document = getHtmlFactory().createDocument(cliOptions.getCharset(), this);
			final var title = getDocumentTitleFor(type.getSimpleName().toString());
			setLastTitle(title);
			final var htmlTag = getHtmlAccessor().getRootElement(document);
			//
			generateTypeDocumentation(type, environment, htmlTag);
			//
			if (!cliOptions.isFakeOutput()) {
				getReporter().print(Kind.NOTE, MessageFormat.format(Messages.AbstractTypeDocumentationGenerator_1, outputPath.toString()));
				writeDocument(outputPath, document);
			}
		} catch (Exception ex) {
			final var rep = getReporter();
			if (rep == null) {
				throw new RuntimeException(ex);
			}
			final var writer = new StringWriter();
			try (final var printWriter = new PrintWriter(writer)) {
				var msg = ex.getLocalizedMessage();
				if (Strings.isNullOrEmpty(msg)) {
					msg = ex.getMessage();
				}
				if (Strings.isNullOrEmpty(msg)) {
					msg = ex.getClass().getName();
				}
				printWriter.print(msg);
				printWriter.print(" ("); //$NON-NLS-1$
				printWriter.print(type.getQualifiedName().toString());
				printWriter.println(")"); //$NON-NLS-1$
				ex.printStackTrace(printWriter);
			}
			rep.print(Kind.ERROR, writer.toString());
		}
	}

	/** Generate the type documentation.
	 *
	 * @param type is the type for which the documentation must be generated.
	 * @param environment the generation environment.
	 * @param htmlTag the root HTML tag.
	 * @throws Exception if the documentation cannot be generated.
	 */
	protected void generateTypeDocumentation(TypeElement type, SarlDocletEnvironment environment, Element htmlTag) throws Exception {
		final var linkContent = getHtmlFactory().createModuleLink(
				getEnvironment().getElementUtils().getModuleOf(type), Messages.AbstractTypeDocumentationGenerator_2, 
				CssStyles.NAVIGATION, this);
		getNavigation().setModuleLink(linkContent);
		//
		generateHtmlHeader(htmlTag, type);
		generateHtmlBody(htmlTag, type);
	}

	/** Generate the HTML header.
	 *
	 * @param htmlTag the container.
	 * @param typeElement the type element.
	 * @return the header.
	 */
	protected Element generateHtmlHeader(Element htmlTag, TypeElement typeElement) {
		final var headerTree = getHtmlFactory().createHeadTag(htmlTag);
		getHtmlFactory().createTitleTag(headerTree, getLastTitle());
		final var pathToRoot = getPathToRoot();
		for (final var cssStyle : getCssStylesheets()) {
			getHtmlFactory().createCssLinkTag(headerTree, pathToRoot.resolve(cssStyle));
		}
		for (final var jsScript : getJsScripts()) {
			getHtmlFactory().createJsLinkTag(headerTree, pathToRoot.resolve(jsScript));
		}
		return headerTree;
	}

	/** Generate the body of the documentation.
	 *
	 * @param htmlTag the container.
	 * @param typeElement the type element.
	 * @return the body.
	 */
	protected Element generateHtmlBody(Element htmlTag, TypeElement typeElement) {
		final var bodyTag = getHtmlFactory().createBodyTag(htmlTag);
		generateBodyHeader(bodyTag, typeElement);
		final var contentTag = getHtmlFactory().createDivTag(bodyTag, CssStyles.CONTENT);
		generateTypeIntroduction(contentTag, typeElement);
		generateTypeTree(contentTag, typeElement);
		generateTypeInfo(contentTag, typeElement);
		generateMemberSummary(contentTag, typeElement);
		generateMemberDetails(contentTag, typeElement);
		generateBodyFooter(bodyTag, typeElement);
		getNavigation().generateNavigationBars(typeElement, this);
		return bodyTag;
	}

	/** Generate the member details defined in the given type.
	 *
	 * @param parent the container.
	 * @param typeElement the type element.
	 */
	protected void generateMemberDetails(Element parent, TypeElement typeElement) {
		generateEnumConstantsDetails(parent, typeElement);
        generatePropertiesDetails(parent, typeElement);
        generateFieldsDetails(parent, typeElement);
        generateConstructorsDetails(parent, typeElement);
        generateActionsDetails(parent, typeElement);
		generateEventHandlersDetails(parent, typeElement);
	}

	private AnnotationValue getPeceptGuardEvaluatorAnnotationValue(AnnotatedConstruct element) {
		if (element != null) {
			for (final var annotationMirror : element.getAnnotationMirrors()) {
				final var type = getElementUtils().asTypeElement(annotationMirror.getAnnotationType(), getEnvironment().getTypeUtils());
				if (PerceptGuardEvaluator.class.getName().equals(type.getQualifiedName().toString()) && !annotationMirror.getElementValues().isEmpty()) {
					for (final var entry : annotationMirror.getElementValues().entrySet()) {
						final var method = entry.getKey();
						final var returnType = method.getReturnType();
						if ("typeParameters".equals(method.getSimpleName().toString()) && returnType.getKind() == TypeKind.ARRAY) { //$NON-NLS-1$
							return entry.getValue();
						}
					}
				}
			}
		}
		return null;
	}
	
	private void extractTypeParameterBounds(AnnotatedConstruct element, Consumer<List<TypeMirror>> handler) {
		if (handler != null) {
			final var annotationValue = getPeceptGuardEvaluatorAnnotationValue(element);
			if (annotationValue != null) {
				annotationValue.accept(new SimpleAnnotationValueVisitor14<Void, Void>(null) {
	                @Override
	                public Void visitArray(List<? extends AnnotationValue> values, Void additionalParameter0) {
	                    if (!values.isEmpty()) {
	                    	final var types = new ArrayList<TypeMirror>();
	                    	for (final var value : values) {
	                    		value.accept(new SimpleAnnotationValueVisitor14<Void, Void>(null) {
	                    			@Override
	                    			public Void visitType(TypeMirror type, Void additionalParamter1) {
	                    				types.add(type);
	                    				return null;
	                    			}
	                    		}, null);
	                    	}
	                    	if (!types.isEmpty()) {
	                    		handler.accept(types);
	                    	}
	                    }
	                    return null;
	                }
	                @Override
	                public Void visitType(TypeMirror t, Void p) {
                    	handler.accept(Collections.singletonList(t));
                    	return null;
	                }
	            }, null);
			}
		}
	}
	
	/** Generate details of the constructors of the given type.
	 *
	 * @param parent the container.
	 * @param typeElement the type element.
	 */
	protected void generateEventHandlersDetails(Element parent, TypeElement typeElement) {
		if (!getElementUtils().isEventHandlerContainer(typeElement)) {
			return;
		}
		final var source = Iterables.filter(typeElement.getEnclosedElements(), ExecutableElement.class);
		final var eventHandlers = Iterables.filter(source,
				it -> it.getKind() == ElementKind.METHOD && !getElementUtils().isStatic(it)
				&& it.getAnnotation(PerceptGuardEvaluator.class) != null);
		//
		createDetailBox(Messages.AbstractTypeDocumentationGenerator_58,
				ID_EVENT_HANDLER_DETAILS, parent, eventHandlers,
				getElementUtils().getExecutableElementComparator(),
				element -> getHtmlFactory().toExecutableAnchor(element), 
				element -> {
					final var nodes = new ArrayList<Node>();
					nodes.add(new TextNode(getSARLGrammarKeywordAccess().getOnKeyword()));
					nodes.add(getHtmlFactory().createSecableSpace(null));
					final var eventType = element.getParameters().get(0).asType();
					final var eventTypeElement = getElementUtils().asTypeElement(eventType, getEnvironment().getTypeUtils());
					nodes.add(new TextNode(eventTypeElement.getSimpleName().toString()));
					extractTypeParameterBounds(element, bounds -> {
						nodes.add(new TextNode(getSARLGrammarKeywordAccess().getLessThanSignKeyword()));
						var first = true;
						for (final var bound : bounds) {
							if (first) {
								first = false;
							} else {
								nodes.add(new TextNode(getSARLGrammarKeywordAccess().getCommaKeyword()));
							}
							final var boundType = getElementUtils().asTypeElement(bound, getEnvironment().getTypeUtils());
							nodes.add(new TextNode(boundType.getSimpleName().toString()));
						}
						nodes.add(new TextNode(getSARLGrammarKeywordAccess().getGreaterThanSignKeyword()));
					});
					return nodes;
				},
				element -> {
					final var nodes = new ArrayList<Node>();
					final var prototype = getHtmlFactory().createPreTag(null, null);
					getHtmlFactory().keyword(prototype, getSARLGrammarKeywordAccess().getOnKeyword());
					getHtmlFactory().createSecableSpace(prototype);
					final var eventType = element.getParameters().get(0).asType();
					prototype.appendChildren(getHtmlFactory().createTypeLink(eventType, false, null, this));
					extractTypeParameterBounds(element, bounds -> {
						prototype.appendText(getSARLGrammarKeywordAccess().getLessThanSignKeyword());
						var first = true;
						for (final var bound : bounds) {
							if (first) {
								first = false;
							} else {
								prototype.appendText(getSARLGrammarKeywordAccess().getCommaKeyword());
							}
							final var boundType = getElementUtils().asTypeElement(bound, getEnvironment().getTypeUtils());
							prototype.appendText(boundType.getSimpleName().toString());
						}
						prototype.appendText(getSARLGrammarKeywordAccess().getGreaterThanSignKeyword());
					});
					nodes.add(prototype);
					//
					createFullDescriptionBody(element, nodes, false, true);
					//
					createBlockTagsFor(element, nodes, Location.METHOD, CssStyles.DETAIL_BOX_TAG);
					return nodes;
				});
	}

	/** Generate the syntax for the specification of a return type.
	 *
	 * @param element the element.
	 * @param receiver is the element that will receive the return type.
	 */
	protected void generateReturnTypeConstruct(ExecutableElement element, Element receiver) {
		final var rtype = element.getReturnType();
		if (rtype != null && rtype.getKind() != TypeKind.VOID) {
			final var type = getHtmlFactory().createTypeLink(rtype, true, null, this);
			if (type != null) {
				getHtmlFactory().createSecableSpace(receiver);
				receiver.appendText(getSARLGrammarKeywordAccess().getColonKeyword());
				getHtmlFactory().createSecableSpace(receiver);
				receiver.appendChildren(type);
			}
		}
	}
	
	/** Generate the syntax for the specification of type parameters.
	 *
	 * @param element the element.
	 * @param receiver is the element that will receive the return type.
	 * @param addNewLine if {@code true}, add a new line before the {@code with} construct.
	 */
	protected void generateTypeParameterConstruct(ExecutableElement element, Element receiver, boolean addNewLine) {
		final var parameters = element.getTypeParameters();
		if (parameters != null && !parameters.isEmpty()) {
			var realReceiver = receiver;
			if (addNewLine) {
				realReceiver = getHtmlFactory().createParagraphTag(receiver, CssStyles.PRE_INDENT);
			}
			getHtmlFactory().createSecableSpace(realReceiver);
			getHtmlFactory().keyword(realReceiver, getSARLGrammarKeywordAccess().getWithKeyword());
			var first = true;
			for (final var typeParam : parameters) {
				if (first) {
					first = false;
				} else {
					realReceiver.appendText(getSARLGrammarKeywordAccess().getCommaKeyword());
				}
				getHtmlFactory().createSecableSpace(realReceiver);
				realReceiver.appendText(typeParam.getSimpleName().toString());
			    final var bounds = typeParam.getBounds();
			    if (bounds != null && !bounds.isEmpty()) {
			    	final var boundTypes = Iterables.filter(bounds, it -> {
			    		final var te = getElementUtils().asTypeElement(it, getEnvironment().getTypeUtils());
			    		return te == null || !Object.class.getName().equals(getElementUtils().getFullyQualifiedName(te, true));
			    	});
			    	if (boundTypes.iterator().hasNext()) {
						getHtmlFactory().createSecableSpace(realReceiver);
						getHtmlFactory().keyword(realReceiver, getSARLGrammarKeywordAccess().getExtendsKeyword());
						var first0 = true;
				    	for (final var tm : boundTypes) {
							getHtmlFactory().createSecableSpace(realReceiver);
				    		if (first0) {
				    			first0 = false;
				    		} else {
				    			realReceiver.appendText(getSARLGrammarKeywordAccess().getAmpersandKeyword());
				    			getHtmlFactory().createSecableSpace(realReceiver);
				    		}
				    		final var typeRef = getHtmlFactory().createTypeLink(tm, true, null, this);
				    		realReceiver.appendChildren(typeRef);
				    	}
			    	}
			    }
			}
		}
	}

	/** Generate the syntax for the specification of thrown exceptions.
	 *
	 * @param element the element.
	 * @param receiver is the element that will receive the return type.
	 * @param addNewLine if {@code true}, add a new line before the {@code throws} construct.
	 */
	protected void generateThrowsConstruct(ExecutableElement element, Element receiver, boolean addNewLine) {
		final var exceptions = element.getThrownTypes();
		if (exceptions != null && !exceptions.isEmpty()) {
			var realReceiver = receiver;
			if (addNewLine) {
				realReceiver = getHtmlFactory().createParagraphTag(receiver, CssStyles.PRE_INDENT);
			}
			getHtmlFactory().createSecableSpace(realReceiver);
			getHtmlFactory().keyword(realReceiver, getSARLGrammarKeywordAccess().getThrowsKeyword());
			var first = true;
			for (final var exception : exceptions) {
				final var type = getHtmlFactory().createTypeLink(exception, true, null, this);
				if (type != null) {
					if (first) {
						first = false;
					} else {
						realReceiver.appendText(getSARLGrammarKeywordAccess().getCommaKeyword());
					}
					getHtmlFactory().createSecableSpace(realReceiver);
					realReceiver.appendChildren(type);
				}
			}
		}
	}

	/** Generate the syntax for the specification of event fires.
	 *
	 * @param element the element.
	 * @param receiver is the element that will receive the return type.
	 * @param addNewLine if {@code true}, add a new line before the {@code fires} construct.
	 */
	protected void generateFiresConstruct(ExecutableElement element, Element receiver, boolean addNewLine) {
		final var annotations = Iterables.filter(element.getAnnotationMirrors(), it -> {
			final var dt = it.getAnnotationType();
			final var qn = getElementUtils().getFullyQualifiedName(dt.asElement(), true);
			if (FiredEvent.class.getName().equals(qn)) {
				final var values = it.getElementValues();
				if (values.size() == 1) {
					final var value = values.values().iterator().next();
					if (value != null) {
						var rvalue = new SimpleAnnotationValueVisitor9<Boolean, Void>() {
							@Override
							public Boolean visitArray(List<? extends AnnotationValue> vals, Void p) {
								return Boolean.valueOf(!vals.isEmpty());
							}
							@Override
							protected Boolean defaultAction(Object o, Void p) {
								return Boolean.FALSE;
							}
						}.visit(value).booleanValue();
						return rvalue;
					}
				}
			}
			return false;
		});
		if (annotations.iterator().hasNext()) {
			final var realReceiver = addNewLine ? getHtmlFactory().createParagraphTag(receiver, CssStyles.PRE_INDENT) : receiver;
			getHtmlFactory().createSecableSpace(realReceiver);
			getHtmlFactory().keyword(realReceiver, getSARLGrammarKeywordAccess().getFiresKeyword());
			final var first = new boolean[] { true };
			for (final var annotation : annotations) {
				final var value = annotation.getElementValues().values().iterator().next();
				new SimpleAnnotationValueVisitor9<Void, Void>() {
					@Override
					public Void visitArray(List<? extends AnnotationValue> vals, Void p) {
						for (final var eventType : vals) {
							final var tm = new SimpleAnnotationValueVisitor9<TypeMirror, Void>() {
								@Override
								public TypeMirror visitType(TypeMirror t, Void p) {
									return t;
								}
								@Override
								protected TypeMirror defaultAction(Object o, Void p) {
									return null;
								}
							}.visit(eventType);
							if (tm != null) {
								if (first[0]) {
									first[0] = false;
								} else {
									realReceiver.appendText(getSARLGrammarKeywordAccess().getCommaKeyword());
								}
								getHtmlFactory().createSecableSpace(realReceiver);
								final var typeNodes = getHtmlFactory().createTypeLink(tm, true, null, AbstractTypeDocumentationGenerator.this);
								realReceiver.appendChildren(typeNodes);
							}
						}
						return null;
					}
					@Override
					protected Void defaultAction(Object o, Void p) {
						return null;
					}
				}.visit(value);
			}
		}
	}

	/** Generate details of the actions of the given type.
	 *
	 * @param parent the container.
	 * @param typeElement the type element.
	 */
	protected void generateActionsDetails(Element parent, TypeElement typeElement) {
		final var source = Iterables.filter(typeElement.getEnclosedElements(), ExecutableElement.class);
		final var actions = Iterables.filter(source,
				it -> it.getKind() == ElementKind.METHOD && getEnvironment().isIncluded(it));
		//
		createDetailBox(Messages.AbstractTypeDocumentationGenerator_54,
				ID_ACTION_DETAILS, parent, actions,
				getElementUtils().getExecutableElementComparator(),
				element -> getHtmlFactory().toExecutableAnchor(element), 
				element -> {
					final var nodes = new ArrayList<Node>();
					final var simpleName = element.getSimpleName().toString();
					final var constructorPrototype = getHtmlFactory().getExecutablePrototype(element, simpleName, this);
					nodes.addAll(constructorPrototype);
					return nodes;
				},
				element -> {
					final var nodes = new ArrayList<Node>();
					final var prototype = getHtmlFactory().createPreTag(null, null);
					final var modifierStr = getElementUtils().getVisibilityModifiersString(element, false);
					if (!Strings.isNullOrEmpty(modifierStr)) {
						getHtmlFactory().keyword(prototype, modifierStr);
						getHtmlFactory().createSecableSpace(prototype);
					}
					getHtmlFactory().keyword(prototype, getSARLGrammarKeywordAccess().getDefKeyword());
					getHtmlFactory().createSecableSpace(prototype);
					final var simpleName = element.getSimpleName().toString();
					final var constructorPrototype = getHtmlFactory().getExecutablePrototype(element, simpleName, this);
					prototype.appendChildren(constructorPrototype);
					generateReturnTypeConstruct(element, prototype);
					generateTypeParameterConstruct(element, prototype, true);
					generateThrowsConstruct(element, prototype, true);
					generateFiresConstruct(element, prototype, true);
					nodes.add(prototype);
					//
					createFullDescriptionBody(element, nodes, false, true);
					//
					createBlockTagsFor(element, nodes, Location.METHOD, CssStyles.DETAIL_BOX_TAG);
					return nodes;
				});
	}

	/** Generate details of the constructors of the given type.
	 *
	 * @param parent the container.
	 * @param typeElement the type element.
	 */
	protected void generateConstructorsDetails(Element parent, TypeElement typeElement) {
		if (typeElement.getKind() != ElementKind.CLASS) {
			return;
		}
		final var source = Iterables.filter(typeElement.getEnclosedElements(), ExecutableElement.class);
		final var constructors = Iterables.filter(source,
				it -> it.getKind() == ElementKind.CONSTRUCTOR && getEnvironment().isIncluded(it));
		//
		final var constructorName = getSARLGrammarKeywordAccess().getNewKeyword();
		//
		createDetailBox(Messages.AbstractTypeDocumentationGenerator_53, ID_CONSTRUCTOR_DETAILS, parent, constructors,
				getElementUtils().getExecutableElementComparator(),
				element -> getHtmlFactory().toExecutableAnchor(element), 
				element -> {
					final var nodes = new ArrayList<Node>();
					final var constructorPrototype = getHtmlFactory().getExecutablePrototype(element, constructorName, this);
					nodes.addAll(constructorPrototype);
					return nodes;
				},
				element -> {
					final var nodes = new ArrayList<Node>();
					final var prototype = getHtmlFactory().createPreTag(null, null);
					final var modifierStr = getElementUtils().getVisibilityModifiersString(element, false);
					if (!Strings.isNullOrEmpty(modifierStr)) {
						getHtmlFactory().keyword(prototype, modifierStr);
						getHtmlFactory().createSecableSpace(prototype);
					}
					final var label = Collections.<Node>singletonList(getHtmlFactory().keyword(null, constructorName));
					final var constructorPrototype = getHtmlFactory().getExecutablePrototype(element, label, this);
					prototype.appendChildren(constructorPrototype);
					generateTypeParameterConstruct(element, prototype, true);
					generateThrowsConstruct(element, prototype, true);
					generateFiresConstruct(element, prototype, true);
					nodes.add(prototype);
					//
					createFullDescriptionBody(element, nodes, false, true);
					//
					createBlockTagsFor(element, nodes, Location.CONSTRUCTOR, CssStyles.DETAIL_BOX_TAG);
					return nodes;
				});
	}

	/** Generate details of the fields of the given type.
	 *
	 * @param parent the container.
	 * @param typeElement the type element.
	 */
	protected void generateFieldsDetails(Element parent, TypeElement typeElement) {
		final var source = Iterables.filter(typeElement.getEnclosedElements(), VariableElement.class);
		final var fields = Iterables.filter(source, it -> it.getKind() == ElementKind.FIELD && getEnvironment().isIncluded(it));
		//
		createDetailBox(Messages.AbstractTypeDocumentationGenerator_52, ID_FIELD_DETAILS, parent, fields,
				getElementUtils().getVariableElementComparator(),
				element -> getHtmlFactory().toVariableAnchor(element), 
				element -> {
					final var nodes = new ArrayList<Node>();
					nodes.add(new TextNode(element.getSimpleName().toString()));
					return nodes;
				},
				element -> {
					final var nodes = new ArrayList<Node>();
					final var prototype = getHtmlFactory().createPreTag(null, null);
					final var modifierStr = getElementUtils().getModifiersString(element, false, false, true);
					if (!Strings.isNullOrEmpty(modifierStr)) {
						getHtmlFactory().keyword(prototype, modifierStr);
						getHtmlFactory().createSecableSpace(prototype);
					}
					final var fieldName = element.getSimpleName().toString();
					prototype.appendText(fieldName);
					getHtmlFactory().createSecableSpace(prototype);
					prototype.appendText(getSARLGrammarKeywordAccess().getColonKeyword());
					getHtmlFactory().createSecableSpace(prototype);
					final var type = element.asType();
					if (type != null) {
						final var typeNodes = getHtmlFactory().createTypeLink(type, true, null, this);
						prototype.appendChildren(typeNodes);
					} else {
						prototype.appendChild(new TextNode(element.toString()));
					}
					final var constantValue = element.getConstantValue();
					if (constantValue != null) {
						getHtmlFactory().createSecableSpace(prototype);
						prototype.appendText(getSARLGrammarKeywordAccess().getEqualsSignKeyword());
						getHtmlFactory().createSecableSpace(prototype);
						prototype.appendChild(new TextNode(ValueTaglet.defaultFormatValue(constantValue)));
					}
					nodes.add(prototype);
					//
					createFullDescriptionBody(element, nodes, false, true);
					//
					createBlockTagsFor(element, nodes, Location.FIELD, CssStyles.DETAIL_BOX_TAG);
					return nodes;
				});
	}

	/** Generate details of the properties of the given type.
	 *
	 * @param parent the container.
	 * @param typeElement the type element.
	 */
	protected void generatePropertiesDetails(Element parent, TypeElement typeElement) {
		final var source = Iterables.filter(typeElement.getEnclosedElements(), ExecutableElement.class);
		final var voidType = getEnvironment().getTypeUtils().getNoType(TypeKind.VOID);
		final var properties = Iterables.filter(source,
				it -> {
					if (getEnvironment().isIncluded(it) && !getElementUtils().isStatic(it)
							&& it.getParameters().size() == 0 && it.getReturnType() != null
							&& !voidType.equals(it.getReturnType()) && isPropertyGetterName(it.getSimpleName().toString())) {
						return true;
					}
					return false;
				});
		//
		final var declaredSetters = new TreeSet<String>();
		for (final var element : getTypeHierarchy().getDeclaredElements(typeElement, true, getEnvironment(), it1 -> {
			if (it1 instanceof ExecutableElement ee) {
				if (!getElementUtils().isStatic(ee) && ee.getParameters().size() == 1
						&& (ee.getReturnType() == null || voidType.equals(ee.getReturnType()))
						&& isPropertySetterName(ee.getSimpleName().toString())) {
					return true;
				}
				}
			return false;
		})) {
			final var name = setterName2property(element.getSimpleName().toString());
			assert name != null;
			declaredSetters.add(name);
		}
		//
		createDetailBox(Messages.AbstractTypeDocumentationGenerator_50, ID_PROPERTY_DETAILS, parent, properties,
				getElementUtils().getExecutableElementComparator(),
				null, 
				element -> {
					final var nodes = new ArrayList<Node>();
					final var propertyName = getterName2property(element.getSimpleName().toString());
					nodes.add(new TextNode(propertyName));
					return nodes;
				},
				element -> {
					final var nodes = new ArrayList<Node>();
					final var prototype = getHtmlFactory().createPreTag(null, null);
					final var modifierStr = getElementUtils().getVisibilityModifiersString(element, false);
					if (!Strings.isNullOrEmpty(modifierStr)) {
						getHtmlFactory().keyword(prototype, modifierStr);
						getHtmlFactory().createSecableSpace(prototype);
					}
					final var propertyName = getterName2property(element.getSimpleName().toString());
