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

package io.sarl.lang.pythongenerator.generator;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;

import com.google.inject.Inject;
import org.eclipse.xtend.core.xtend.XtendEnumLiteral;
import org.eclipse.xtend.core.xtend.XtendExecutable;
import org.eclipse.xtend.core.xtend.XtendMember;
import org.eclipse.xtend.core.xtend.XtendParameter;
import org.eclipse.xtext.common.types.JvmDeclaredType;
import org.eclipse.xtext.common.types.JvmIdentifiableElement;
import org.eclipse.xtext.common.types.JvmOperation;
import org.eclipse.xtext.common.types.JvmType;
import org.eclipse.xtext.common.types.JvmTypeReference;
import org.eclipse.xtext.common.types.TypesFactory;
import org.eclipse.xtext.naming.IQualifiedNameProvider;
import org.eclipse.xtext.naming.QualifiedName;
import org.eclipse.xtext.util.Strings;
import org.eclipse.xtext.xbase.XExpression;
import org.eclipse.xtext.xbase.lib.Pair;
import org.eclipse.xtext.xbase.lib.Procedures.Procedure2;

import io.sarl.lang.core.Agent;
import io.sarl.lang.core.Behavior;
import io.sarl.lang.core.Capacity;
import io.sarl.lang.core.Event;
import io.sarl.lang.core.Skill;
import io.sarl.lang.extralanguage.compiler.AbstractExtraLanguageGenerator;
import io.sarl.lang.extralanguage.compiler.ExtraLanguageAppendable;
import io.sarl.lang.extralanguage.compiler.IExtraLanguageGeneratorContext;
import io.sarl.lang.pythongenerator.PyGeneratorPlugin;
import io.sarl.lang.pythongenerator.configuration.IPyGeneratorConfigurationProvider;
import io.sarl.lang.pythongenerator.configuration.PyOutputConfigurationProvider;
import io.sarl.lang.sarl.SarlAction;
import io.sarl.lang.sarl.SarlAgent;
import io.sarl.lang.sarl.SarlAnnotationType;
import io.sarl.lang.sarl.SarlBehavior;
import io.sarl.lang.sarl.SarlBehaviorUnit;
import io.sarl.lang.sarl.SarlCapacity;
import io.sarl.lang.sarl.SarlCapacityUses;
import io.sarl.lang.sarl.SarlClass;
import io.sarl.lang.sarl.SarlConstructor;
import io.sarl.lang.sarl.SarlEnumeration;
import io.sarl.lang.sarl.SarlEvent;
import io.sarl.lang.sarl.SarlField;
import io.sarl.lang.sarl.SarlFormalParameter;
import io.sarl.lang.sarl.SarlInterface;
import io.sarl.lang.sarl.SarlSkill;
import io.sarl.lang.sarl.actionprototype.InferredValuedParameter;
import io.sarl.lang.util.Utils;

/** The generator from SARL to the Python language.
 *
 * @author $Author: sgalland$
 * @version $FullVersion$
 * @mavengroupid $GroupId$
 * @mavenartifactid $ArtifactId$
 * @since 0.6
 */
public class PyGenerator extends AbstractExtraLanguageGenerator {

	/** Header for a Python file.
	 */
	public static final String PYTHON_FILE_HEADER = "#!/usr/bin/env python3"; //$NON-NLS-1$

	private static final String PYTHON_FILENAME_EXTENSION = ".py"; //$NON-NLS-1$

	private static final String LIBRARY_CONTENT = "__all__ = []"; //$NON-NLS-1$

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

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

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

	@Inject
	private IQualifiedNameProvider qualifiedNameProvider;

	private PyExpressionGenerator expressionGenerator;

	private IPyGeneratorConfigurationProvider configuration;

	private Map<JvmOperation, String> useCapacityMapping = new HashMap<>();

	/** Change the provider of the generator's configuration.
	 *
	 * @param provider the provider.
	 */
	@Inject
	public void setPyGeneratorConfigurationProvider(IPyGeneratorConfigurationProvider provider) {
		this.configuration = provider;
	}

	/** Change the generator of XExpression.
	 *
	 * @param generator the generator.
	 */
	@Inject
	public void setExpressionGenerator(PyExpressionGenerator generator) {
		this.expressionGenerator = generator;
	}

	/** Replies the generator of XExpression.
	 *
	 * @return the generator.
	 */
	@Override
	public PyExpressionGenerator getExpressionGenerator() {
		return this.expressionGenerator;
	}

	@Override
	protected void initializeContext(IExtraLanguageGeneratorContext generatorContext) {
		final var config = this.configuration.get(generatorContext.getResource(), true);
		final var converter = getExpressionGenerator().getTypeConverter(generatorContext);
		converter.setImplicitJvmTypes(config.isImplicitJvmTypes());
	}

	@Override
	protected PyAppendable createAppendable(JvmDeclaredType thisType, IExtraLanguageGeneratorContext context) {
		final var converter = getTypeConverter(context);
		final var appendable = new PyAppendable(thisType, converter);
		markCapacityFunctions(appendable);
		return appendable;
	}

	@Override
	protected String getOutputConfigurationName() {
		return PyOutputConfigurationProvider.OUTPUT_CONFIGURATION_NAME;
	}

	@Override
	protected String getFilenameExtension() {
		return PYTHON_FILENAME_EXTENSION;
	}

	@Override
	protected String getPreferenceID() {
		return PyGeneratorPlugin.PREFERENCE_ID;
	}

	@Override
	protected boolean writeFile(QualifiedName name, ExtraLanguageAppendable appendable,
			IExtraLanguageGeneratorContext context) {
		if (super.writeFile(name, appendable, context)) {
			// Generate the package files for the Python library
			writePackageFiles(name, appendable.getLineSeparator(), context);
			return true;
		}
		return false;
	}

	/** Generate the Python package files.
	 *
	 * <p>This function generates the "__init__.py" files for all the packages.
	 *
	 * @param name the name of the generated type.
	 * @param lineSeparator the line separator.
	 * @param context the generation context.
	 */
	protected void writePackageFiles(QualifiedName name, String lineSeparator,
			IExtraLanguageGeneratorContext context) {
		final var fsa = context.getFileSystemAccess();
		final var outputConfiguration = getOutputConfigurationName();
		QualifiedName libraryName = null;
		for (final var segment : name.skipLast(1).getSegments()) {
			if (libraryName == null) {
				libraryName = QualifiedName.create(segment, LIBRARY_FILENAME);
			} else {
				libraryName = libraryName.append(segment).append(LIBRARY_FILENAME);
			}
			final var fileName = toFilename(libraryName);
			if (!fsa.isFile(fileName)) {
				final var content = PYTHON_FILE_HEADER + lineSeparator
						+ getGenerationComment(context) + lineSeparator
						+ LIBRARY_CONTENT;
				if (Strings.isEmpty(outputConfiguration)) {
					fsa.generateFile(fileName, content);
				} else {
					fsa.generateFile(fileName, outputConfiguration, content);
				}
			}
			libraryName = libraryName.skipLast(1);
		}
	}

	/** Replies a string representing a comment with the generation information.
	 *
	 * @param context the generation context.
	 * @return the comment text.
	 */
	@SuppressWarnings("static-method")
	protected String getGenerationComment(IExtraLanguageGeneratorContext context) {
		return "# Generated by the SARL compiler the " + context.getGenerationDate().toString() //$NON-NLS-1$
				+ ". Do not change this file."; //$NON-NLS-1$
	}

	/** Generate the type declaration for a Python class.
	 *
	 * @param typeName name of the type.
	 * @param isAbstract indicates if the type is abstract (interface included).
	 * @param superTypes the super types.
	 * @param comment the type declaration's comment.
	 * @param ignoreObjectType ignores the "Object" type if the super types.
	 * @param it the output.
	 * @param context the generation context.
	 * @return {@code true} if the declaration was generated. {@code false} if the declaration was not generated.
	 */
	@SuppressWarnings("static-method")
	protected boolean generatePythonClassDeclaration(String typeName, boolean isAbstract,
			List<? extends JvmTypeReference> superTypes, String comment, boolean ignoreObjectType, PyAppendable it,
			IExtraLanguageGeneratorContext context) {
		if (!Strings.isEmpty(typeName)) {
			it.append("class ").append(typeName).append("("); //$NON-NLS-1$ //$NON-NLS-2$
			var isOtherSuperType = false;
			var first = true;
			for (final var reference : superTypes) {
				if (!ignoreObjectType || !Strings.equal(reference.getQualifiedName(), Object.class.getCanonicalName())) {
					isOtherSuperType = true;
					if (first) {
						first = false;
					} else {
						it.append(","); //$NON-NLS-1$
					}
					it.append(reference.getType());
				}
			}
			if (isOtherSuperType) {
				it.append(","); //$NON-NLS-1$
			}
			// Add "object to avoid a bug within the Python interpreter.
			it.append("object):"); //$NON-NLS-1$
			it.increaseIndentation().newLine();
			generateDocString(comment, it);
			return true;
		}
		return false;
	}

	/** Generate a Python docstring with the given comment.
	 *
	 * @param comment the comment.
	 * @param it the receiver of the docstring.
	 * @return {@code true} if the docstring is added, {@code false} otherwise.
	 */
	protected static boolean generateDocString(String comment, PyAppendable it) {
		final var cmt = comment == null ? null : comment.trim();
		if (!Strings.isEmpty(cmt)) {
			assert cmt != null;
			it.append("\"\"\"").increaseIndentation(); //$NON-NLS-1$
			for (final var line : cmt.split("[\n\r\f]+")) {  //$NON-NLS-1$
				it.newLine().append(line);
			}
			it.decreaseIndentation().newLine();
			it.append("\"\"\"").newLine(); //$NON-NLS-1$
			return true;
		}
		return false;
	}

	/** Generate a Python block comment with the given comment.
	 *
	 * @param comment the comment.
	 * @param it the receiver of the block comment.
	 * @return {@code true} if the block comment is added, {@code false} otherwise.
	 */
	protected static boolean generateBlockComment(String comment, PyAppendable it) {
		final var cmt = comment == null ? null : comment.trim();
		if (!Strings.isEmpty(cmt)) {
			assert cmt != null;
			for (final var line : cmt.split("[\n\r\f]+")) {  //$NON-NLS-1$
				it.append("# ").append(line).newLine(); //$NON-NLS-1$
			}
			return true;
		}
		return false;
	}

	/** Generate the given type.
	 *
	 * @param fullyQualifiedName the fully qualified name of the type.
	 * @param name the name of the type.
	 * @param isAbstract indicates if the type is abstract.
	 * @param superTypes the super types.
	 * @param comment the comment.
	 * @param ignoreObjectType ignores the "Object" type if the super types.
	 * @param members the members.
	 * @param it the output.
	 * @param context the context.
	 * @param memberGenerator the generator of members.
	 * @return {@code true} if the type declaration was generated.
	 */
	protected boolean generateTypeDeclaration(
			String fullyQualifiedName,
			String name, boolean isAbstract,
			List<? extends JvmTypeReference> superTypes,
			String comment,
			boolean ignoreObjectType, List<? extends XtendMember> members, PyAppendable it,
			IExtraLanguageGeneratorContext context, Procedure2<? super PyAppendable, ? super IExtraLanguageGeneratorContext> memberGenerator) {
		if (!Strings.isEmpty(name)) {
			if (!generatePythonClassDeclaration(name, isAbstract, superTypes, comment, ignoreObjectType, it, context)
				|| context.getCancelIndicator().isCanceled()) {
				return false;
			}
			//
			it.openScope();
			//
			if (!generateSarlMembers(members, it, context)
				|| context.getCancelIndicator().isCanceled()) {
				return false;
			}
			//
			if (memberGenerator != null) {
				memberGenerator.apply(it,  context);
			}
			//
			if (!generatePythonConstructors(fullyQualifiedName, members, it, context)
				|| context.getCancelIndicator().isCanceled()) {
				return false;
			}
			//
			it.decreaseIndentation().newLine().newLine();
			//
			it.closeScope();
			//
			if (context.getCancelIndicator().isCanceled()) {
				return false;
			}
			return true;
		}
		return false;
	}

	/** Generate the given enumeration declaration.
	 *
	 * @param enumeration the enumeration.
	 * @param it the receiver of the generated code.
	 * @param context the context.
	 * @return {@code true} if a declaration was generated. {@code false} if no enumeration was generated.
	 * @since 0.8
	 */
	protected boolean generateEnumerationDeclaration(SarlEnumeration enumeration, PyAppendable it, IExtraLanguageGeneratorContext context) {
		if (!Strings.isEmpty(enumeration.getName())) {
			it.append("class ").append(enumeration.getName()); //$NON-NLS-1$
			it.append("(Enum"); //$NON-NLS-1$
			it.append(newType("enum.Enum")); //$NON-NLS-1$
			it.append("):"); //$NON-NLS-1$
			it.increaseIndentation().newLine();
			generateDocString(getTypeBuilder().getDocumentation(enumeration), it);
			int i = 0;
			for (final var item : enumeration.getMembers()) {
				if (context.getCancelIndicator().isCanceled()) {
					return false;
				}
				if (item instanceof XtendEnumLiteral literal) {
					it.append(literal.getName()).append(" = "); //$NON-NLS-1$
					it.append(Integer.toString(i));
					it.newLine();
					++i;
				}
			}
			//
			it.decreaseIndentation().newLine().newLine();
			return true;
		}
		return false;
	}

	/** Generate the constructors for a Python class.
	 *
	 * @param container the fully qualified name of the container.
	 * @param members the members to be added.
	 * @param it the output.
	 * @param context the generation context.
	 * @return {@code true} if a constructor was generated. {@code false} if no constructor was generated.
	 */
	protected boolean generatePythonConstructors(String container, List<? extends XtendMember> members,
			PyAppendable it, IExtraLanguageGeneratorContext context) {
		// Prepare field initialization
		var hasConstructor = false;
		for (final var member : members) {
			if (context.getCancelIndicator().isCanceled()) {
				return false;
			}
			if (member instanceof SarlConstructor) {
				hasConstructor = true;
				generate(member, it, context);
				it.newLine();
			}
		}
		if (context.getCancelIndicator().isCanceled()) {
			return false;
		}
		if (!hasConstructor) {
			it.append("def __init__(self):"); //$NON-NLS-1$
			it.increaseIndentation().newLine();
			final List<SarlField> fields = context.getMultimapValues(INSTANCE_VARIABLES_MEMENTO, container);
			if (fields.isEmpty()) {
				it.append("pass"); //$NON-NLS-1$
			} else {
				for (final var field : fields) {
					generatePythonField(field, it, context);
				}
			}
			it.decreaseIndentation().newLine();
		}
		return true;
	}

	/** Create a JvmType for a Python type.
	 *
	 * @param pythonName the python type name.
	 * @return the type.
	 */
	@SuppressWarnings("static-method")
	protected JvmType newType(String pythonName) {
		final var type = TypesFactory.eINSTANCE.createJvmGenericType();
		final var index = pythonName.indexOf("."); //$NON-NLS-1$
		if (index <= 0) {
			type.setSimpleName(pythonName);
		} else {
			type.setPackageName(pythonName.substring(0, index - 1));
			type.setSimpleName(pythonName.substring(index + 1));
		}
		return type;
	}

	/** Create a field declaration.
	 *
	 * @param field the field to generate.
	 * @param it the output
	 * @param context the generation context.
	 */
	protected void generatePythonField(SarlField field, PyAppendable it, IExtraLanguageGeneratorContext context) {
		generateBlockComment(getTypeBuilder().getDocumentation(field), it);
		if (!field.isStatic()) {
			it.append("self."); //$NON-NLS-1$
		}
		final var fieldName = it.declareUniqueNameVariable(field, field.getName());
		it.append(fieldName);
		it.append(" = "); //$NON-NLS-1$
		if (field.getInitialValue() != null) {
			generate(field.getInitialValue(), null, it, context);
		} else {
			it.append(PyExpressionGenerator.toDefaultValue(field.getType()));
		}
		it.newLine();
	}

	/** Generate the Python code for an executable statement.
	 *
	 * @param name the name of the executable.
	 * @param executable the executable statement.
	 * @param appendSelf indicates if the "self" parameter should be added.
	 * @param isAbstract indicates if the executable is abstract.
	 * @param returnType the type of the value to be returned, or {@code null} if void.
	 * @param comment the comment associated to the function.
	 * @param it the target for the generated content.
	 * @param context the context.
	 */
	protected void generateExecutable(String name, XtendExecutable executable, boolean appendSelf, boolean isAbstract,
			JvmTypeReference returnType, String comment, PyAppendable it, IExtraLanguageGeneratorContext context) {
		it.append("def ").append(name); //$NON-NLS-1$
		it.append("("); //$NON-NLS-1$
		var firstParam = true;
		if (appendSelf) {
			firstParam = false;
			it.append(getExpressionGenerator().getExtraLanguageKeywordProvider().getThisKeywordLambda().apply());
		}
		for (final var parameter : executable.getParameters()) {
			if (firstParam) {
				firstParam = false;
			} else {
				it.append(", "); //$NON-NLS-1$
			}
			if (parameter.isVarArg()) {
				it.append("*"); //$NON-NLS-1$
			}
			final var pname = it.declareUniqueNameVariable(parameter, parameter.getName());
			it.append(pname).append(" : ").append(parameter.getParameterType().getType()); //$NON-NLS-1$
		}
		final var actualReturnType = getExpectedType(executable, returnType);
		it.append(")"); //$NON-NLS-1$
		if (actualReturnType != null) {
			it.append(" -> ").append(actualReturnType); //$NON-NLS-1$
		}
		it.append(":"); //$NON-NLS-1$
		it.increaseIndentation().newLine();
		generateDocString(comment, it);
		if (executable.getExpression() != null) {
			it.openScope();
			generate(executable.getExpression(), actualReturnType, it, context);
			it.closeScope();
		} else if (isAbstract) {
			it.append("raise Exception(\"Unimplemented function\")"); //$NON-NLS-1$
		} else {
			it.append("pass"); //$NON-NLS-1$
		}
		it.decreaseIndentation().newLine();
		//
		// Generate the additional functions
		final var prototypeProvider = getActionPrototypeProvider();
		final var actionName = prototypeProvider.createQualifiedActionName(
				(JvmIdentifiableElement) getJvmModelAssociations().getPrimaryJvmElement(executable.getDeclaringType()),
				name);
		final var inferredPrototype = getActionPrototypeProvider().createPrototypeFromSarlModel(
				context.getActionPrototypeContext(getActionPrototypeProvider()),
				actionName,
				Utils.isVarArg(executable.getParameters()), executable.getParameters());
		for (final var types : inferredPrototype.getInferredParameterTypes().entrySet()) {
			final var argumentsToOriginal = types.getValue();
			it.append("def ").append(name); //$NON-NLS-1$
			it.append("(self"); //$NON-NLS-1$
			for (final var parameter : argumentsToOriginal) {
				if (!(parameter instanceof InferredValuedParameter)) {
					it.append(", "); //$NON-NLS-1$
					if (((XtendParameter) parameter.getParameter()).isVarArg()) {
						it.append("*"); //$NON-NLS-1$
					}
					it.append(parameter.getName()).append(" : ").append(parameter.getType().getType()); //$NON-NLS-1$
				}
			}
			it.append(")"); //$NON-NLS-1$
			if (actualReturnType != null) {
				it.append(" -> ").append(actualReturnType); //$NON-NLS-1$
			}
			it.append(":"); //$NON-NLS-1$
			it.increaseIndentation().newLine();
			if (actualReturnType != null) {
				it.append("return "); //$NON-NLS-1$
			}
			it.append("self.").append(name).append("("); //$NON-NLS-1$ //$NON-NLS-2$
			var first = true;
			for (final var parameter : argumentsToOriginal) {
				if (first) {
					first = false;
				} else {
					it.append(", "); //$NON-NLS-1$
				}
				if (parameter instanceof InferredValuedParameter valuedParameter) {
					generate(((SarlFormalParameter) valuedParameter.getParameter()).getDefaultValue(), null, it, context);
				} else {
					it.append(parameter.getName());
				}
			}
			it.append(")"); //$NON-NLS-1$
			it.decreaseIndentation().newLine();
		}
	}

	/** Generate the memorized guard evaluators.
	 *
	 * @param container the fully qualified name of the container of the guards.
	 * @param it the output.
	 * @param context the generation context.
	 */
	protected void generateGuardEvaluators(String container, PyAppendable it, IExtraLanguageGeneratorContext context) {
		final Map<String, Map<String, List<Pair<XExpression, String>>>> allGuardEvaluators = context.getMapData(EVENT_GUARDS_MEMENTO);
		final Map<String, List<Pair<XExpression, String>>> guardEvaluators = allGuardEvaluators.get(container);
		if (guardEvaluators == null) {
			return;
		}
		boolean first = true;
		for (final var entry : guardEvaluators.entrySet()) {
			if (first) {
				first = false;
			} else {
				it.newLine();
			}
			it.append("def __guard_"); //$NON-NLS-1$
			it.append(entry.getKey().replaceAll("[^a-zA-Z0-9_]+", "_")); //$NON-NLS-1$ //$NON-NLS-2$
			it.append("__(self, occurrence):"); //$NON-NLS-1$
			it.increaseIndentation().newLine();
			it.append("it = occurrence").newLine(); //$NON-NLS-1$
			final var eventHandleName = it.declareUniqueNameVariable(new Object(), "__event_handles"); //$NON-NLS-1$
			it.append(eventHandleName).append(" = list"); //$NON-NLS-1$
			for (final var guardDesc : entry.getValue()) {
				it.newLine();
				if (guardDesc.getKey() == null) {
					it.append(eventHandleName).append(".add(").append(guardDesc.getValue()).append(")"); //$NON-NLS-1$ //$NON-NLS-2$
				} else {
					it.append("if "); //$NON-NLS-1$
					generate(guardDesc.getKey(), null, it, context);
					it.append(":").increaseIndentation().newLine(); //$NON-NLS-1$
					it.append(eventHandleName).append(".add(").append(guardDesc.getValue()).append(")"); //$NON-NLS-1$ //$NON-NLS-2$
					it.decreaseIndentation();
				}
			}
			it.newLine().append("return ").append(eventHandleName); //$NON-NLS-1$
			it.decreaseIndentation().newLine();
		}
	}

	//----------------------------------------
	// File Header
	//----------------------------------------

	@Override
	protected void generateFileHeader(QualifiedName qualifiedName, ExtraLanguageAppendable appendable,
			IExtraLanguageGeneratorContext context) {
		appendable.append(PYTHON_FILE_HEADER);
		appendable.newLine();
		appendable.append(getGenerationComment(context));
		appendable.newLine().newLine();
	}

	@Override
	protected void generateImportStatement(QualifiedName qualifiedName, ExtraLanguageAppendable appendable,
			IExtraLanguageGeneratorContext context) {
		final var typeName = qualifiedName.getLastSegment();
		final var packageName = qualifiedName.skipLast(1);
		appendable.append("from "); //$NON-NLS-1$
		appendable.append(packageName.toString());
		appendable.append(" import "); //$NON-NLS-1$
		appendable.append(typeName);
		appendable.newLine();
	}

	//----------------------------------------
	// Types
	//----------------------------------------

	/** Generate the given object.
	 *
	 * @param clazz the class.
	 * @param context the context.
	 */
	protected void _generate(SarlClass clazz, IExtraLanguageGeneratorContext context) {
		final var jvmType = getJvmModelAssociations().getInferredType(clazz);
		final var appendable = createAppendable(jvmType, context);
		if (generateTypeDeclaration(
				this.qualifiedNameProvider.getFullyQualifiedName(clazz).toString(),
				clazz.getName(), clazz.isAbstract(),
				getSuperTypes(clazz.getExtends(), clazz.getImplements()),
				getTypeBuilder().getDocumentation(clazz),
				true,
				clazz.getMembers(), appendable, context, null)) {
			final var name = getQualifiedNameProvider().getFullyQualifiedName(clazz);
			writeFile(name, appendable, context);
		}
	}

	/** Generate the given object.
	 *
	 * @param interf the interface.
	 * @param context the context.
	 */
	protected void _generate(SarlInterface interf, IExtraLanguageGeneratorContext context) {
		final var jvmType = getJvmModelAssociations().getInferredType(interf);
		final var appendable = createAppendable(jvmType, context);
		if (generateTypeDeclaration(
				this.qualifiedNameProvider.getFullyQualifiedName(interf).toString(),
				interf.getName(), true, interf.getExtends(),
				getTypeBuilder().getDocumentation(interf),
				true,
				interf.getMembers(), appendable, context, null)) {
			final var name = getQualifiedNameProvider().getFullyQualifiedName(interf);
			writeFile(name, appendable, context);
		}
	}

	/** Generate the given object.
	 *
	 * @param enumeration the enumeration.
	 * @param context the context.
	 */
	protected void _generate(SarlEnumeration enumeration, IExtraLanguageGeneratorContext context) {
		final var jvmType = getJvmModelAssociations().getInferredType(enumeration);
		final var appendable = createAppendable(jvmType, context);
		if (generateEnumerationDeclaration(enumeration, appendable, context)) {
			final var name = getQualifiedNameProvider().getFullyQualifiedName(enumeration);
			writeFile(name, appendable, context);
		}
	}

	/** Generate the given object.
	 *
	 * @param annotation the annotation.
	 * @param context the context.
	 */
	protected void _generate(SarlAnnotationType annotation, IExtraLanguageGeneratorContext context) {
		final var jvmType = getJvmModelAssociations().getInferredType(annotation);
		final var appendable = createAppendable(jvmType, context);
		if (generateTypeDeclaration(
				this.qualifiedNameProvider.getFullyQualifiedName(annotation).toString(),
				annotation.getName(), false, Collections.emptyList(),
				getTypeBuilder().getDocumentation(annotation),
				true,
				annotation.getMembers(), appendable, context, null)) {
			final var name = getQualifiedNameProvider().getFullyQualifiedName(annotation);
			writeFile(name, appendable, context);
		}
	}

	/** Generate the given object.
	 *
	 * @param event the event.
	 * @param context the context.
	 */
	protected void _generate(SarlEvent event, IExtraLanguageGeneratorContext context) {
		final var jvmType = getJvmModelAssociations().getInferredType(event);
		final var appendable = createAppendable(jvmType, context);
		final List<JvmTypeReference> superTypes;
		if (event.getExtends() != null) {
			superTypes = Collections.singletonList(event.getExtends());
		} else {
			superTypes = Collections.singletonList(getTypeReferences().getTypeForName(Event.class, event));
		}
		if (generateTypeDeclaration(
				this.qualifiedNameProvider.getFullyQualifiedName(event).toString(),
				event.getName(), event.isAbstract(), superTypes,
				getTypeBuilder().getDocumentation(event),
				true,
				event.getMembers(), appendable, context, null)) {
			final var name = getQualifiedNameProvider().getFullyQualifiedName(event);
			writeFile(name, appendable, context);
		}
	}

	/** Generate the given object.
	 *
	 * @param agent the agent.
	 * @param context the context.
	 */
	protected void var(SarlAgent agent, IExtraLanguageGeneratorContext context) {
		final JvmDeclaredType jvmType = getJvmModelAssociations().getInferredType(agent);
		final var appendable = createAppendable(jvmType, context);
		final List<JvmTypeReference> superTypes;
		if (agent.getExtends() != null) {
			superTypes = Collections.singletonList(agent.getExtends());
		} else {
			superTypes = Collections.singletonList(getTypeReferences().getTypeForName(Agent.class, agent));
		}
		final var qualifiedName = this.qualifiedNameProvider.getFullyQualifiedName(agent).toString();
		if (generateTypeDeclaration(
				qualifiedName,
				agent.getName(), agent.isAbstract(), superTypes,
				getTypeBuilder().getDocumentation(agent),
				true,
			agent.getMembers(), appendable, context, (it, context2) -> {
				generateGuardEvaluators(qualifiedName, it, context2);
			})) {
			final var name = getQualifiedNameProvider().getFullyQualifiedName(agent);
			writeFile(name, appendable, context);
		}
	}

	/** Generate the given object.
	 *
	 * @param behavior the behavior.
	 * @param context the context.
	 */
	protected void _generate(SarlBehavior behavior, IExtraLanguageGeneratorContext context) {
		final var jvmType = getJvmModelAssociations().getInferredType(behavior);
		final var appendable = createAppendable(jvmType, context);
		final List<JvmTypeReference> superTypes;
		if (behavior.getExtends() != null) {
			superTypes = Collections.singletonList(behavior.getExtends());
		} else {
			superTypes = Collections.singletonList(getTypeReferences().getTypeForName(Behavior.class, behavior));
		}
		final var qualifiedName = this.qualifiedNameProvider.getFullyQualifiedName(behavior).toString();
		if (generateTypeDeclaration(
				qualifiedName,
				behavior.getName(), behavior.isAbstract(), superTypes,
				getTypeBuilder().getDocumentation(behavior),
				true,
				behavior.getMembers(), appendable, context, (it, context2) -> {
				generateGuardEvaluators(qualifiedName, it, context2);
			})) {
			final var name = getQualifiedNameProvider().getFullyQualifiedName(behavior);
			writeFile(name, appendable, context);
		}
	}

	/** Generate the given object.
	 *
	 * @param capacity the capacity.
	 * @param context the context.
	 */
	protected void _generate(SarlCapacity capacity, IExtraLanguageGeneratorContext context) {
		final var jvmType = getJvmModelAssociations().getInferredType(capacity);
		final var appendable = createAppendable(jvmType, context);
		final List<? extends JvmTypeReference> superTypes;
		if (!capacity.getExtends().isEmpty()) {
			superTypes = capacity.getExtends();
		} else {
			superTypes = Collections.singletonList(getTypeReferences().getTypeForName(Capacity.class, capacity));
		}
		if (generateTypeDeclaration(
				this.qualifiedNameProvider.getFullyQualifiedName(capacity).toString(),
				capacity.getName(), true, superTypes,
				getTypeBuilder().getDocumentation(capacity),
				true,
				capacity.getMembers(), appendable, context, null)) {
			final var name = getQualifiedNameProvider().getFullyQualifiedName(capacity);
			writeFile(name, appendable, context);
		}
	}

	/** Generate the given object.
	 *
	 * @param skill the skill.
	 * @param context the context.
	 */
	protected void _generate(SarlSkill skill, IExtraLanguageGeneratorContext context) {
		final JvmDeclaredType jvmType = getJvmModelAssociations().getInferredType(skill);
		final PyAppendable appendable = createAppendable(jvmType, context);

		List<JvmTypeReference> superTypes = getSuperTypes(skill.getExtends(), skill.getImplements());
		if (superTypes.isEmpty()) {
			superTypes = Collections.singletonList(getTypeReferences().getTypeForName(Skill.class, skill));
		}
		final String qualifiedName = this.qualifiedNameProvider.getFullyQualifiedName(skill).toString();
		if (generateTypeDeclaration(
				qualifiedName,
				skill.getName(), skill.isAbstract(), superTypes,
				getTypeBuilder().getDocumentation(skill),
				true,
				skill.getMembers(), appendable, context, (it, context2) -> {
				generateGuardEvaluators(qualifiedName, it, context2);
			})) {
			final QualifiedName name = getQualifiedNameProvider().getFullyQualifiedName(skill);
			writeFile(name, appendable, context);
		}
	}

	//----------------------------------------
	// Members
	//----------------------------------------

	/** Generate the given object.
	 *
	 * @param clazz the class.
	 * @param it the target for the generated content.
	 * @param context the context.
	 */
	protected void _generate(SarlClass clazz, PyAppendable it, IExtraLanguageGeneratorContext context) {
		generateTypeDeclaration(
				this.qualifiedNameProvider.getFullyQualifiedName(clazz).toString(),
				clazz.getName(), clazz.isAbstract(),
				getSuperTypes(clazz.getExtends(), clazz.getImplements()),
				getTypeBuilder().getDocumentation(clazz),
				true,
				clazz.getMembers(), it, context, null);
	}

	/** Generate the given object.
	 *
	 * @param interf the interface.
	 * @param it the target for the generated content.
	 * @param context the context.
	 */
	protected void _generate(SarlInterface interf, PyAppendable it, IExtraLanguageGeneratorContext context) {
		generateTypeDeclaration(
				this.qualifiedNameProvider.getFullyQualifiedName(interf).toString(),
				interf.getName(), true, interf.getExtends(),
				getTypeBuilder().getDocumentation(interf),
				true,
				interf.getMembers(), it, context, null);
	}

	/** Generate the given object.
	 *
	 * @param enumeration the enumeration.
	 * @param it the target for the generated content.
	 * @param context the context.
	 */
	protected void _generate(SarlEnumeration enumeration, PyAppendable it, IExtraLanguageGeneratorContext context) {
		generateEnumerationDeclaration(enumeration, it, context);
