/*
 * $Id$
 *
 * File is automatically generated by the Xtext language generator.
 * Do not change it.
 *
 * 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.codebuilder.builders;

import com.google.inject.Inject;
import com.google.inject.name.Named;
import io.sarl.lang.core.util.SarlUtils;
import io.sarl.lang.sarl.SarlAnnotationType;
import io.sarl.lang.sarl.SarlCapacity;
import io.sarl.lang.sarl.SarlEvent;
import io.sarl.lang.sarl.SarlInterface;
import org.eclipse.emf.common.notify.Notifier;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.xtend.core.xtend.XtendTypeDeclaration;
import org.eclipse.xtext.Constants;
import org.eclipse.xtext.common.types.JvmAnyTypeReference;
import org.eclipse.xtext.common.types.JvmDeclaredType;
import org.eclipse.xtext.common.types.JvmExecutable;
import org.eclipse.xtext.common.types.JvmParameterizedTypeReference;
import org.eclipse.xtext.common.types.JvmType;
import org.eclipse.xtext.common.types.JvmTypeConstraint;
import org.eclipse.xtext.common.types.JvmTypeReference;
import org.eclipse.xtext.common.types.JvmWildcardTypeReference;
import org.eclipse.xtext.common.types.access.IJvmTypeProvider;
import org.eclipse.xtext.common.types.util.Primitives;
import org.eclipse.xtext.common.types.util.TypeReferences;
import org.eclipse.xtext.naming.IQualifiedNameProvider;
import org.eclipse.xtext.resource.DerivedStateAwareResource;
import org.eclipse.xtext.resource.IResourceFactory;
import org.eclipse.xtext.resource.XtextResource;
import org.eclipse.xtext.xbase.compiler.ImportManager;
import org.eclipse.xtext.xbase.imports.IImportsConfiguration;
import org.eclipse.xtext.xbase.jvmmodel.JvmModelAssociator;
import org.eclipse.xtext.xbase.jvmmodel.JvmTypeReferenceBuilder;
import org.eclipse.xtext.xbase.lib.Pure;
import org.eclipse.xtext.xbase.typesystem.references.LightweightTypeReference;
import org.eclipse.xtext.xbase.typesystem.references.LightweightTypeReferenceFactory;
import org.eclipse.xtext.xbase.typesystem.references.StandardTypeReferenceOwner;
import org.eclipse.xtext.xbase.typesystem.util.CommonTypeComputationServices;

/** Abstract implementation of a builder for the Sarl language.
	 * @see "AbstractBuilderBuilderFragment.java : appendTo : 88"
 */
@SuppressWarnings("all")
public abstract class AbstractBuilder {

	/**
	 * @see "AbstractBuilderBuilderFragment.java : appendTo : 98"
	 */
	@Inject
	private IQualifiedNameProvider qualifiedNameProvider;

	/**
	 * @see "AbstractBuilderBuilderFragment.java : appendTo : 107"
	 */
	@Inject
	private JvmModelAssociator associations;

	/**
	 * @see "AbstractBuilderBuilderFragment.java : appendTo : 116"
	 */
	@Inject
	private CommonTypeComputationServices services;

	/**
	 * @see "AbstractBuilderBuilderFragment.java : appendTo : 125"
	 */
	@Inject
	private ImportManager importManager;

	/**
	 * @see "AbstractBuilderBuilderFragment.java : appendTo : 134"
	 */
	@Inject
	private JvmTypeReferenceBuilder.Factory typeReferenceBuilderFactory;

	/**
	 * @see "AbstractBuilderBuilderFragment.java : appendTo : 143"
	 */
	private JvmTypeReferenceBuilder typeReferenceBuilder;

	/**
	 * @see "AbstractBuilderBuilderFragment.java : appendTo : 149"
	 */
	@Inject
	private TypeReferences typeReferences;

	/**
	 * @see "AbstractBuilderBuilderFragment.java : appendTo : 158"
	 */
	@Inject
	private Primitives primitives;

	/**
	 * @see "AbstractBuilderBuilderFragment.java : appendTo : 167"
	 */
	@Inject
	private IImportsConfiguration importsConfiguration;

	/**
	 * @see "AbstractBuilderBuilderFragment.java : appendTo : 176"
	 */
	@Inject
	private IResourceFactory resourceFactory;

	/**
	 * @see "AbstractBuilderBuilderFragment.java : appendTo : 185"
	 */
	private String fileExtension;

	/**
	 * @see "AbstractBuilderBuilderFragment.java : appendTo : 189"
	 */
	private IJvmTypeProvider typeResolutionContext;

	/**
	 * @see "AbstractBuilderBuilderFragment.java : appendTo : 195"
	 */
	@Inject
	public void setFileExtensions(@Named(Constants.FILE_EXTENSIONS) String fileExtensions) {
		this.fileExtension = SarlUtils.getMajorFileExtension(fileExtensions);
	}

	/**
	 * @see "AbstractBuilderBuilderFragment.java : appendTo : 212"
	 */
	protected <T> T getAssociatedElement(Class<T> expectedType, EObject dslObject, Resource resource, boolean failIfNotFound) {
		for (final EObject obj : this.associations.getJvmElements(dslObject)) {
			if (expectedType.isInstance(obj)) {
				return expectedType.cast(obj);
			}
		}
		if (resource instanceof DerivedStateAwareResource $c$value) {
			$c$value.discardDerivedState();
			$c$value.getContents();
			return getAssociatedElement(expectedType, dslObject, null, failIfNotFound);
		}
		if (failIfNotFound) {
			throw new IllegalStateException("No " + expectedType.getSimpleName() + " associated to " + dslObject + " in " + dslObject.eContainer());
		}
		return null;
	}

	/**
	 * @see "AbstractBuilderBuilderFragment.java : appendTo : 256"
	 */
	protected void setTypeResolutionContext(IJvmTypeProvider context) {
		this.typeResolutionContext = context;
	}

	/**
	 * @see "AbstractBuilderBuilderFragment.java : appendTo : 266"
	 */
	public IJvmTypeProvider getTypeResolutionContext() {
		return this.typeResolutionContext;
	}

	/** Replies the script's file extension.
	 * @see "AbstractBuilderBuilderFragment.java : appendTo : 278"
	 */
	@Pure
	public String getScriptFileExtension() {
		return this.fileExtension;
	}

	/** Replies the builder of type references.
	 *
	 * @return the type reference builder.
	 * @see "AbstractBuilderBuilderFragment.java : appendTo : 297"
	 */
	@Pure
	protected JvmTypeReferenceBuilder getTypeReferenceBuilder() {
		if (this.typeReferenceBuilder == null) {
			this.typeReferenceBuilder = this.typeReferenceBuilderFactory.create(eResource().getResourceSet());
		}
		return this.typeReferenceBuilder;
	}

	/**
	 * @see "AbstractBuilderBuilderFragment.java : appendTo : 318"
	 */
	protected TypeReferences getTypeReferences() {
		return this.typeReferences;
	}

	/** Replies the primitive type tools.
	 *
	 * @return the primitive type tools.
	 * @see "AbstractBuilderBuilderFragment.java : appendTo : 334"
	 */
	@Pure
	protected Primitives getPrimitiveTypes() {
		return this.primitives;
	}

	/**
	 * @see "AbstractBuilderBuilderFragment.java : appendTo : 349"
	 */
	private JvmTypeReference innerFindType(Notifier context, String typeName) {
		final IJvmTypeProvider provider = getTypeResolutionContext();
		JvmType type = null;
		if (provider != null) {
			type = provider.findTypeByName(typeName);
		}
		if (type == null) {
			type = getTypeReferences().findDeclaredType(typeName, context);
		}
		if (type == null) {
			return null;
		}
		return getTypeReferenceBuilder().typeRef(type);
	}

	/**
	 * @see "AbstractBuilderBuilderFragment.java : appendTo : 387"
	 */
	protected JvmTypeReference findType(Notifier context, String typeName) {
		final JvmTypeReference type = innerFindType(context, typeName);
		if (!isTypeReference(type)) {
			XtextResource xtextResource = toResource(context);
			for (String packageName : getImportsConfiguration().getImplicitlyImportedPackages(xtextResource)) {
				JvmTypeReference typeReference = innerFindType(context, packageName + "." + typeName);
				if (isTypeReference(typeReference)) {
					return typeReference;
				}
			}
			throw new TypeNotPresentException(typeName, null);
		}
		return type;
	}

	/**
	 * @see "AbstractBuilderBuilderFragment.java : appendTo : 429"
	 */
	protected static XtextResource toResource(Notifier context) {
		return (XtextResource) (context instanceof Resource ? context : ((EObject) context).eResource());
	}

	/**
	 * @see "AbstractBuilderBuilderFragment.java : appendTo : 447"
	 */
	private void addImport(JvmTypeReference reference) {
		if (reference != null) {
			final JvmType type = reference.getType();
			if (type instanceof JvmDeclaredType) {
				getImportManager().addImportFor(type);
			}
		}
	}

	/** Replies the type reference for the given name in the given context.
	 * @see "AbstractBuilderBuilderFragment.java : appendTo : 473"
	 */
	public JvmTypeReference newTypeRef(String typeName) {
		return newTypeRef(eResource(), typeName);
	}

	/** Replies the type reference for the given name in the given context.
	 * @see "AbstractBuilderBuilderFragment.java : appendTo : 487"
	 */
	public JvmTypeReference newTypeRef(Notifier context, String typeName) {
		JvmTypeReference typeReference;
		try {
			typeReference = findType(context, typeName);
			addImport(typeReference);
			return typeReference;
		} catch (TypeNotPresentException exception) {
		}
		final JvmTypeReference pref = ExpressionBuilderImpl.parseType(context, typeName, this);
		final JvmTypeReference baseType = findType(context, pref.getType().getIdentifier());
		final JvmTypeReference result;
		if (pref instanceof JvmParameterizedTypeReference ppref) {
			final int len = ppref.getArguments().size();
			if (len > 0) {
				final JvmTypeReference[] args = new JvmTypeReference[len];
				for (int i = 0; i < len; ++i) {
					final JvmTypeReference original = ppref.getArguments().get(i);
					if (original instanceof JvmAnyTypeReference) {
						args[i] = EcoreUtil.copy(original);
					} else if (original instanceof JvmWildcardTypeReference $c$value) {
						final JvmWildcardTypeReference wc = EcoreUtil.copy($c$value);
						for (final JvmTypeConstraint c : wc.getConstraints()) {
							c.setTypeReference(newTypeRef(context, c.getTypeReference().getIdentifier()));
						}
						args[i] = wc;
					} else {
						args[i] = newTypeRef(context, original.getIdentifier());
					}
				}
				result = getTypeReferenceBuilder().typeRef(baseType.getType(), args);
			} else {
				result = getTypeReferenceBuilder().typeRef(baseType.getType());
			}
		} else {
			result = getTypeReferenceBuilder().typeRef(baseType.getType());
		}
		addImport(result);
		return result;
	}

	/** Replies the type reference for the given type and type parameters.
	 * @see "AbstractBuilderBuilderFragment.java : appendTo : 603"
	 */
	public JvmTypeReference newTypeRef(JvmType typeName, JvmTypeReference... args) {
		final JvmTypeReference ref;
		if (args != null && args.length > 0) {
			ref = getTypeReferenceBuilder().typeRef(typeName, args);
		} else {
			ref = getTypeReferenceBuilder().typeRef(typeName);
		}
		addImport(ref);
		return ref;
	}

	/** Replies the type reference for the given type and type parameters.
	 * @see "AbstractBuilderBuilderFragment.java : appendTo : 637"
	 */
	public JvmTypeReference newTypeRef(Class type, JvmTypeReference... args) {
		return newTypeRef(eResource(), type, args);
	}

	/** Replies the type reference for the given type and type parameters.
	 * @see "AbstractBuilderBuilderFragment.java : appendTo : 655"
	 */
	public JvmTypeReference newTypeRef(Notifier context, Class type, JvmTypeReference... args) {
		final JvmType type0 = getTypeReferences().findDeclaredType(type, context);
		final JvmTypeReference ref;
		if (args != null && args.length > 0) {
			ref = getTypeReferenceBuilder().typeRef(type0, args);
		} else {
			ref = getTypeReferenceBuilder().typeRef(type0);
		}
		addImport(ref);
		return ref;
	}

	/** Replies if the first parameter is a subtype of the second parameter.
	 *
	 * @param context the context.
	 * @param subType the subtype to test.
	 * @param superType the expected super type.
	 * @return the type reference.
	 * @see "AbstractBuilderBuilderFragment.java : appendTo : 705"
	 */
	@Pure
	protected boolean isSubTypeOf(EObject context, JvmTypeReference subType, JvmTypeReference superType) {
		if (isTypeReference(superType) && isTypeReference(subType)) {
			StandardTypeReferenceOwner owner = new StandardTypeReferenceOwner(services, context);
			LightweightTypeReferenceFactory factory = new LightweightTypeReferenceFactory(owner, false);
			LightweightTypeReference reference = factory.toLightweightReference(subType);
			return reference.isSubtypeOf(superType.getType());
		}
		return false;
	}

	/** Replies if the given object is a valid type reference.
	 * @see "AbstractBuilderBuilderFragment.java : appendTo : 748"
	 */
	@Pure
	protected boolean isTypeReference(JvmTypeReference typeReference) {
		return (typeReference != null && !typeReference.eIsProxy()
			&& typeReference.getType() != null && !typeReference.getType().eIsProxy());
	}

	/** Replies the import's configuration.
	 *
	 * @return the import's configuration.
	 * @see "AbstractBuilderBuilderFragment.java : appendTo : 771"
	 */
	@Pure
	protected IImportsConfiguration getImportsConfiguration() {
		return this.importsConfiguration;
	}

	/** Compute a unused URI for a synthetic resource.
	 * @param resourceSet the resource set in which the resource should be located.
	 * @return the uri.
	 * @see "AbstractBuilderBuilderFragment.java : appendTo : 792"
	 */
	@Pure
	protected URI computeUnusedUri(ResourceSet resourceSet) {
		String name = "__synthetic";
		for (int i = 0; i < Integer.MAX_VALUE; ++i) {
			URI syntheticUri = URI.createURI(name + i + "." + getScriptFileExtension());
			if (resourceSet.getResource(syntheticUri, false) == null) {
				return syntheticUri;
			}
		}
		throw new IllegalStateException();
	}

	/** Replies the resource factory.
	 *
	 * @return the resource factory.
	 * @see "AbstractBuilderBuilderFragment.java : appendTo : 833"
	 */
	@Pure
	protected IResourceFactory getResourceFactory() {
		return this.resourceFactory;
	}

	/** Replies if the type could contains functions with a body.
	 * @see "AbstractBuilderBuilderFragment.java : appendTo : 850"
	 */
	@Pure
	protected boolean isActionBodyAllowed(XtendTypeDeclaration type) {
		return !(type instanceof SarlAnnotationType
			|| type instanceof SarlCapacity
			|| type instanceof SarlEvent
			|| type instanceof SarlInterface);
	}

	/** Replies the import manager that stores the imported types.
	 *
	 * @return the import manager.
	 * @see "AbstractBuilderBuilderFragment.java : appendTo : 889"
	 */
	@Pure
	protected ImportManager getImportManager() {
		return this.importManager;
	}

	/**
	 * @see "AbstractBuilderBuilderFragment.java : appendTo : 904"
	 */
	protected String getQualifiedName(EObject object) {
		return this.qualifiedNameProvider.getFullyQualifiedName(object).toString();
	}

	/**
	 * @see "AbstractBuilderBuilderFragment.java : appendTo : 914"
	 */
	@Pure
	public abstract Resource eResource();

	/**
	 * @see "AbstractBuilderBuilderFragment.java : appendTo : 923"
	 */
	public void dispose() {
		Resource resource = eResource();
		ResourceSet resourceSet = resource.getResourceSet();
		resourceSet.getResources().remove(resource);
	}

	/** Replies the reference to the generated Ecore element.
	 * @param ecoreObject the object to get a reference to.
	 * @param args the generic type arguments to put inside the reference.
	 * @since 0.15
	 * @see "AbstractBuilderBuilderFragment.java : appendTo : 947"
	 */
	@Pure
	protected JvmTypeReference getTypeReferenceFor(EObject ecoreObject, JvmTypeReference... args) {
		assert ecoreObject != null;
		JvmType jvmObject = getAssociatedElement(JvmType.class, ecoreObject, ecoreObject.eResource(), false);
		if (jvmObject == null) {
			final var qn = getQualifiedName(ecoreObject);
			final var provider = getTypeResolutionContext();
			if (provider != null) {
				jvmObject = provider.findTypeByName(qn);
			}
			if (jvmObject == null) {
				jvmObject = getTypeReferences().findDeclaredType(qn, ecoreObject);
			}
			if (jvmObject == null) {
				return null;
			}
		}
		return newTypeRef(jvmObject, args);
	}

	/** Replies the reference to the generated Ecore element.
	 * @param ecoreObject the object to get a reference to.
	 * @since 0.15
	 * @see "AbstractBuilderBuilderFragment.java : appendTo : 1006"
	 */
	@Pure
	protected JvmExecutable getExecutableReferenceFor(EObject ecoreObject) {
		assert ecoreObject != null;
		return getAssociatedElement(JvmExecutable.class, ecoreObject, ecoreObject.eResource(), true);
	}

}

