/*
 * $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 io.sarl.lang.sarl.SarlEvent;
import io.sarl.lang.sarl.SarlField;
import io.sarl.lang.sarl.SarlScript;
import io.sarl.lang.services.ITypeDefaultValueProvider;
import java.util.function.Predicate;
import org.eclipse.emf.common.notify.Adapter;
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.EcoreUtil2;
import org.eclipse.xtext.common.types.JvmDeclaredType;
import org.eclipse.xtext.common.types.JvmIdentifiableElement;
import org.eclipse.xtext.common.types.JvmParameterizedTypeReference;
import org.eclipse.xtext.common.types.JvmType;
import org.eclipse.xtext.common.types.JvmTypeReference;
import org.eclipse.xtext.common.types.access.IJvmTypeProvider;
import org.eclipse.xtext.util.EmfFormatter;
import org.eclipse.xtext.util.StringInputStream;
import org.eclipse.xtext.util.Strings;
import org.eclipse.xtext.xbase.XExpression;
import org.eclipse.xtext.xbase.XFeatureCall;
import org.eclipse.xtext.xbase.XbaseFactory;
import org.eclipse.xtext.xbase.compiler.DocumentationAdapter;
import org.eclipse.xtext.xbase.lib.Procedures.Procedure1;
import org.eclipse.xtext.xbase.lib.Pure;

/** Builder of a Sarl XExpression.
	 * @see "ExpressionBuilderFragment.java : appendTo : 153"
 */
@SuppressWarnings("all")
public class ExpressionBuilderImpl extends AbstractBuilder implements IExpressionBuilder {

	/**
	 * @see "ExpressionBuilderFragment.java : appendTo : 226"
	 */
	private EObject context;

	/**
	 * @see "ExpressionBuilderFragment.java : appendTo : 232"
	 */
	private Procedure1<? super XExpression> setter;

	/**
	 * @see "ExpressionBuilderFragment.java : appendTo : 240"
	 */
	private XExpression expr;

	/**
	 * @see "ExpressionBuilderFragment.java : appendTo : 246"
	 */
	@Inject
	private ITypeDefaultValueProvider defaultValueProvider;

	/** Initialize the expression.
	 * @param context the context of the expressions.
	 * @param setter the object that permits to assign the expression to the context.
	 * @param typeContext the context for type resolution.
	 * @see "ExpressionBuilderFragment.java : appendTo : 452"
	 */
	public void eInit(EObject context, Procedure1<? super XExpression> setter, IJvmTypeProvider typeContext) {
		setTypeResolutionContext(typeContext);
		this.context = context;
		this.setter = setter;
		this.expr = null;
	}

	/** Replies the last created expression.
	 *
	 * @return the last created expression.
	 * @see "ExpressionBuilderFragment.java : appendTo : 495"
	 */
	@Pure
	public XExpression getXExpression() {
		return this.expr;
	}

	/** Replies the resource to which the XExpression is attached.
	 * @see "ExpressionBuilderFragment.java : appendTo : 524"
	 */
	@Pure
	public Resource eResource() {
		final var expr = getXExpression();
		if (expr != null) {
			return expr.eResource();
		}
		return this.context.eResource();
	}

	/** Change the expression in the container.
	 *
	 * @param expression the textual representation of the expression.
	 * @return {@code this}
	 * @see "ExpressionBuilderFragment.java : appendTo : 567"
	 */
	public IExpressionBuilder setExpression(String expression) {
		this.expr = fromString(expression);
		this.setter.apply(this.expr);
		return this;
	}

	/** Change the expression in the container.
	 *
	 * @param expression the expression.
	 * @return {@code this}
	 * @see "ExpressionBuilderFragment.java : appendTo : 603"
	 */
	public IExpressionBuilder setXExpression(XExpression expression) {
		this.expr = expression;
		this.setter.apply(this.expr);
		return this;
	}

	/** Generate a piece of Sarl code that permits to compile an XExpression.
	 *
	 * @param expression the expression to compile.
	 * @return the Sarl code.
	 * @see "ExpressionBuilderFragment.java : appendTo : 646"
	 */
	static String generateExpressionCode(String expression) {
		return "event ____synthesis { var ____fakefield = " + expression + " }";
	}

	/**
	 * @see "ExpressionBuilderFragment.java : appendTo : 660"
	 */
	static String generateTypenameCode(String typeName) {
		return "event ____synthesis { var ____fakefield : " + typeName + " }";
	}

	/**
	 * @see "ExpressionBuilderFragment.java : appendTo : 672"
	 */
	static JvmTypeReference parseType(Notifier context, String typeName, AbstractBuilder caller) {
		ResourceSet resourceSet = toResource(context).getResourceSet();
		URI uri = caller.computeUnusedUri(resourceSet);
		Resource resource = caller.getResourceFactory().createResource(uri);
		resourceSet.getResources().add(resource);
		try (StringInputStream is = new StringInputStream(generateTypenameCode(typeName))) {
			resource.load(is, null);
			SarlScript script = resource.getContents().isEmpty() ? null : (SarlScript) resource.getContents().get(0);
			SarlEvent topElement = (SarlEvent) script.getXtendTypes().get(0);
			SarlField member = (SarlField) topElement.getMembers().get(0);
			JvmTypeReference reference = member.getType();
			if (reference instanceof JvmParameterizedTypeReference pref) {
				if (!pref.getArguments().isEmpty()) {
					EcoreUtil2.resolveAll(resource);
				}
			}
			return reference;
		} catch (Exception exception) {
			throw new TypeNotPresentException(typeName, exception);
		} finally {
			resourceSet.getResources().remove(resource);
		}
	}

	/** Create an expression but does not change the container.
	 *
	 * @param expression the textual representation of the expression.
	 * @return the expression.
	 * @see "ExpressionBuilderFragment.java : appendTo : 770"
	 */
	@Pure
	protected XExpression fromString(String expression) {
		if (!Strings.isEmpty(expression)) {
			ResourceSet resourceSet = this.context.eResource().getResourceSet();
			URI uri = computeUnusedUri(resourceSet);
			Resource resource = getResourceFactory().createResource(uri);
			resourceSet.getResources().add(resource);
			try (StringInputStream is = new StringInputStream(generateExpressionCode(expression))) {
				resource.load(is, null);
				SarlScript script = resource.getContents().isEmpty() ? null : (SarlScript) resource.getContents().get(0);
				SarlEvent topElement = (SarlEvent) script.getXtendTypes().get(0);
				SarlField member = (SarlField) topElement.getMembers().get(0);
				return member.getInitialValue();
			} catch (Throwable exception) {
				throw new RuntimeException(exception);
			} finally {
				resourceSet.getResources().remove(resource);
			}
		}
		throw new IllegalArgumentException("not a valid expression");
	}

	/** Replies the XExpression for the default value associated to the given type.
	 * @param type the type for which the default value should be determined.
	 * @return the default value.
	 * @see "ExpressionBuilderFragment.java : appendTo : 857"
	 */
	@Pure
	public XExpression getDefaultXExpressionForType(String type) {
		return this.defaultValueProvider.getDefaultValueXExpression(type, this.context);
	}

	/** Replies the XExpression for the default value associated to the given type.
	 * @param type the type for which the default value should be determined.
	 * @return the default value.
	 * @see "ExpressionBuilderFragment.java : appendTo : 890"
	 */
	@Pure
	public XExpression getDefaultXExpressionForType(JvmTypeReference type) {
		return getDefaultXExpressionForType(type.getType().getIdentifier());
	}

	/** Replies the default value for the given type.
	 * @param type the type for which the default value should be determined.
	 * @return the default value.
	 * @see "ExpressionBuilderFragment.java : appendTo : 925"
	 */
	@Pure
	public String getDefaultValueForType(String type) {
		return this.defaultValueProvider.getDefaultValueInSarlSyntax(type);
	}

	/** Replies the default value for the given type.
	 * @param type the type for which the default value should be determined.
	 * @return the default value.
	 * @see "ExpressionBuilderFragment.java : appendTo : 957"
	 */
	@Pure
	public String getDefaultValueForType(JvmTypeReference type) {
		return getDefaultValueForType(type.getType().getIdentifier());
	}

	/** Change the documentation of the element.
	 *
	 * <p>The documentation will be displayed just before the element.
	 *
	 * @param doc the documentation.
	 * @return {@code this}.
	 * @see "AbstractSubCodeBuilderFragment.java : appendTo : 603"
	 */
	public IExpressionBuilder setDocumentation(String doc) {
		if (Strings.isEmpty(doc)) {
			getXExpression().eAdapters().removeIf(new Predicate<Adapter>() {
				public boolean test(Adapter adapter) {
					return adapter.isAdapterForType(DocumentationAdapter.class);
				}
			});
		} else {
			DocumentationAdapter adapter = (DocumentationAdapter) EcoreUtil.getExistingAdapter(
					getXExpression(), DocumentationAdapter.class);
			if (adapter == null) {
				adapter = new DocumentationAdapter();
				getXExpression().eAdapters().add(adapter);
			}
			adapter.setDocumentation(doc);
		}
		return this;
	}

	@Override
	@Pure
	public String toString() {
		return EmfFormatter.objToStr(getXExpression());
	}

	/** Create a reference to "this" object or to the current type.
	 *
	 * @return the reference.
	 * @see "ExpressionBuilderFragment.java : appendTo : 1016"
	 */
	public XFeatureCall createReferenceToThis() {
		final XExpression expr = getXExpression();
		XtendTypeDeclaration type = EcoreUtil2.getContainerOfType(expr, XtendTypeDeclaration.class);
		JvmType jvmObject = getAssociatedElement(JvmType.class, type, expr.eResource(), true);
		final XFeatureCall thisFeature = XbaseFactory.eINSTANCE.createXFeatureCall();
		thisFeature.setFeature(jvmObject);
		return thisFeature;
	}

	/** Create a reference to "super" object or to the super type.
	 *
	 * @return the reference.
	 * @see "ExpressionBuilderFragment.java : appendTo : 1074"
	 */
	public XFeatureCall createReferenceToSuper() {
		final XExpression expr = getXExpression();
		XtendTypeDeclaration type = EcoreUtil2.getContainerOfType(expr, XtendTypeDeclaration.class);
		JvmType jvmObject = getAssociatedElement(JvmType.class, type, expr.eResource(), true);
		final XFeatureCall superFeature = XbaseFactory.eINSTANCE.createXFeatureCall();
		JvmIdentifiableElement feature;
		if (jvmObject instanceof JvmDeclaredType $c$value) {
			feature = $c$value.getExtendedClass().getType();
		} else {
			feature = findType(expr, getQualifiedName(type)).getType();
			if (feature instanceof JvmDeclaredType $c$value) {
				feature = $c$value.getExtendedClass().getType();
			} else {
				feature = null;
			}
		}
		if (feature == null) {
			return null;
		}
		superFeature.setFeature(feature);
		return superFeature;

	}

}

