package adql.query.operand.function;

/*
 * This file is part of ADQLLibrary.
 *
 * ADQLLibrary is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * ADQLLibrary 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with ADQLLibrary.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Copyright 2012-2021 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
 *                       Astronomisches Rechen Institut (ARI)
 */

import java.lang.reflect.InvocationTargetException;

import adql.db.DBType;
import adql.db.DBType.DBDatatype;
import adql.db.FunctionDef;
import adql.db.FunctionDef.FunctionParam;
import adql.parser.ADQLQueryFactory;
import adql.parser.feature.LanguageFeature;
import adql.parser.grammar.ParseException;
import adql.query.ADQLList;
import adql.query.ADQLObject;
import adql.query.ClauseADQL;
import adql.query.TextPosition;
import adql.query.operand.ADQLOperand;
import adql.query.operand.UnknownType;
import adql.translator.ADQLTranslator;
import adql.translator.FunctionTranslator;
import adql.translator.TranslationException;

/**
 * Custom function defined by an ADQL service provider.
 *
 * <h3>Abstract to concrete class</h3>
 *
 * <p>
 * 	Since ADQL-Lib v2.0, this class is no longer abstract. {@link DefaultUDF}
 * 	has been merged into {@link UserDefinedFunction} which then became a
 * 	concrete class. Most of the 'final' functions in {@link DefaultUDF} are no
 * 	longer 'final' in {@link UserDefinedFunction}.
 * </p>
 * <p><i><b>Warning:</b>
 * 	{@link DefaultUDF} is now deprecated and should not be used any more.
 * </i></p>
 *
 * <h3>Language feature</h3>
 *
 * <p>
 * 	As any optional feature of ADQL (since ADQL-2.1), an explicit ID and
 * 	description have to be provided in the capabilities of an ADQL service
 * 	(like TAP). This is represented here by a {@link LanguageFeature}. In
 * 	{@link UserDefinedFunction} it is automatically generated by
 * 	{@link #generateLanguageFeature()} at creation (by
 * 	{@link #UserDefinedFunction(String, ADQLOperand[])}) or when NO definition
 * 	is explicitly set (by {@link #setDefinition(FunctionDef)}). Otherwise,
 * 	when setting a non NULL definition, a Language Feature is created using this
 * 	definition (with {@link LanguageFeature#LanguageFeature(FunctionDef)}).
 * </p>
 *
 * <h3>Function definition</h3>
 *
 * <p>
 * 	ADQL allows the usage of User Defined Function (UDF), but as such there is
 * 	no definition (mainly a name and signature) available. However, TAPRegExt
 * 	lets return a definition and human description of UDFs. In the ADQL tree,
 * 	a such definition is represented by a {@link FunctionDef}. It is not
 * 	mandatory to set one, but recommended anyway. To do so, call
 * 	{@link #setDefinition(FunctionDef)}.
 * </p>
 *
 * <h3>Custom extension</h3>
 *
 * <p>
 * 	It is expected that this class may be extended in order to cover special
 * 	custom User Defined Function. That's why most of the defined function are
 * 	not 'final', as attributes are not too.
 * </p>
 *
 * <p><i><b>Recommendation:</b>
 * 	It is recommended, when possible, to update the protected attributes
 * 	directly instead of their corresponding getter function.
 * </i></p>
 *
 * <p>
 * 	The empty constructor is protected only for extension purpose. That way,
 * 	it is up to the developer to decide whether he/she wants to call the super
 * 	class's empty constructor or the default one
 * 	({@link #UserDefinedFunction(String, ADQLOperand[])}). However, the
 * 	extension class SHOULD have at least one constructor with 2 arguments:
 * 	String for the UDF name, {@link ADQLOperand}[] for its parameters.
 * </p>
 *
 * <p><b>Warning:</b>
 * 	If this constructor does not exist or is not desired,
 * 	{@link ADQLQueryFactory#createUserDefinedFunction(String, ADQLOperand[])}
 * 	will have to be overwritten. Also be careful that the definition set to this
 * 	extension class has no custom {@link FunctionDef#getUDFClass() UDF class},
 * 	otherwise, there will be an attempt to replace it by an instance of this
 * 	UDF class ; and this will probably end with an error because of the missing
 * 	constructor.
 * </p>
 *
 * @author Gr&eacute;gory Mantelet (CDS;ARI)
 * @version 2.0 (05/2021)
 */
public class UserDefinedFunction extends ADQLFunction implements UnknownType {

	/** Type expected by the parser.
	 * @since 1.3 */
	private char expectedType = '?';

	/** Description of this ADQL Feature.
	 * @since 2.0 */
	protected LanguageFeature languageFeature;

	/** Define/Describe this user defined function.
	 * This object gives the return type and the number and type of all
	 * parameters.
	 * <p><i><b>Note:</b> NULL if the function name is invalid. See
	 * 	{@link FunctionDef#FunctionDef(String, DBType, FunctionParam[], adql.parser.ADQLParserFactory.ADQLVersion) FunctionDef.FunctionDef(..., ADQLVersion)}
	 * for more details.</i></p>
	 * @since 2.0 */
	protected FunctionDef definition = null;

	/** Its parsed parameters.
	 * @since 2.0 */
	protected ADQLList<ADQLOperand> parameters;

	/** Parsed name of this UDF.
	 * <p><b>Important:</b> This attribute should never be NULL.</p>
	 * @since 2.0 */
	protected String functionName;

	/**
	 * Creates a UDF with no name and no parameter.
	 *
	 * <p><i><b>Implementation note 1:</b>
	 * 	By default {@link #functionName} is not set to NULL, but to
	 * 	<code>"&lt;unnamed&gt;"</code> in order to avoid
	 * 	{@link NullPointerException} (especially when generating a
	 * 	{@link LanguageFeature}).
	 * </i></p>
	 *
	 * <i>
	 * <p><b>Implementation note 2:</b>
	 * 	The purpose of this constructor is just allow more flexibility when
	 * 	extending this class. However, it is highly recommended to set at least
	 * 	a non-null function name. This can be done in at least 3 ways:
	 * </p>
	 * <ol>
	 * 	<li>calling the super constructor: {@link #UserDefinedFunction(String, ADQLOperand[])}</li>
	 * 	<li>setting the protected attribute {@link #functionName}</li>
	 * 	<li>overwriting the function {@link #getName()}</li>
	 * </ol>
	 * </i>
	 */
	protected UserDefinedFunction() {
		this("<unnamed>", null);
	}

	/**
	 * Creates a user function.
	 *
	 * @param params	Parameters of the function.
	 *
	 * @since 2.0
	 */
	public UserDefinedFunction(final String name, final ADQLOperand[] params) throws NullPointerException {
		functionName = name;
		parameters = new ClauseADQL<ADQLOperand>();
		if (params != null) {
			for(ADQLOperand p : params)
				parameters.add(p);
		}
		generateLanguageFeature();
	}

	/**
	 * Builds a UserFunction by copying the given one.
	 *
	 * @param toCopy		The UserFunction to copy.
	 *
	 * @throws Exception	If there is an error during the copy.
	 *
	 * @since 2.0
	 */
	@SuppressWarnings("unchecked")
	public UserDefinedFunction(final UserDefinedFunction toCopy) throws Exception {
		functionName = toCopy.functionName;
		parameters = (ADQLList<ADQLOperand>)(toCopy.parameters.getCopy());
		setPosition((toCopy.getPosition() == null) ? null : new TextPosition(toCopy.getPosition()));
		definition = toCopy.definition;
		languageFeature = toCopy.languageFeature;
	}

	@Override
	public final char getExpectedType() {
		return expectedType;
	}

	@Override
	public final void setExpectedType(final char c) {
		expectedType = c;
	}

	@Override
	public final LanguageFeature getFeatureDescription() {
		return languageFeature;
	}

	/**
	 * Get the signature/definition/description of this user defined function.
	 * The returned object provides information on the return type and the
	 * number and type of parameters.
	 *
	 * @return	Definition of this function. (MAY be NULL)
	 *
	 * @since 2.0
	 */
	public final FunctionDef getDefinition() {
		return definition;
	}

	/**
	 * Let set the signature/definition/description of this user defined
	 * function.
	 *
	 * <p><i><b>IMPORTANT:</b>
	 * 	No particular checks are done here except on the function name which
	 * 	MUST be the same (case insensitive) as the name of the given definition.
	 * 	Advanced checks must have been done before calling this setter.
	 * </i></p>
	 *
	 * @param def	The definition applying to this parsed UDF,
	 *           	or NULL if none has been found.
	 *
	 * @throws IllegalArgumentException	If the name in the given definition does
	 *                                 	not match the name of this parsed
	 *                                 	function.
	 *
	 * @since 2.0
	 */
	public void setDefinition(final FunctionDef def) throws IllegalArgumentException {
		// Ensure the definition is compatible with this ADQL function:
		if (def != null && (def.name == null || getName() == null || !getName().equalsIgnoreCase(def.name)))
			throw new IllegalArgumentException("The parsed function name (" + getName() + ") does not match to the name of the given UDF definition (" + def.name + ").");

		// Set the new definition (may be NULL):
		this.definition = def;

		// Update the Language Feature of this ADQL function:
		// ...if no definition, generate a default LanguageFeature:
		if (this.definition == null)
			generateLanguageFeature();
		// ...otherwise, use the definition to set the LanguageFeature:
		else
			languageFeature = new LanguageFeature(this.definition);
	}

	/**
	 * Generate and set a default {@link LanguageFeature} for this ADQL
	 * function.
	 *
	 * <p><i><b>Note:</b>
	 * 	Knowing neither the parameters name nor their type, the generated
	 * 	LanguageFeature will just set an unknown type and set a default
	 * 	parameter name (index prefixed with `$`).
	 * </i></p>
	 *
	 * @since 2.0
	 */
	protected void generateLanguageFeature() {
		// Create an unknown DBType:
		DBType unknownType = new DBType(DBDatatype.UNKNOWN);
		unknownType.type.setCustomType("type");

		// Create the list of input parameters:
		FunctionParam[] inputParams = new FunctionParam[parameters.size()];
		for(int i = 1; i <= parameters.size(); i++)
			inputParams[i - 1] = new FunctionParam("param" + i, unknownType);

		try {
			// Create the Function Definition:
			FunctionDef fctDef = new FunctionDef((getName() == null ? "<unnamed>" : getName()), unknownType, inputParams);

			// Finally create the LanguageFeature:
			languageFeature = new LanguageFeature(fctDef);
		} catch(ParseException pe) {
			// TODO Invalid function name. TO LOG in some way!
			languageFeature = null;
		}
	}

	@Override
	public boolean isNumeric() {
		return (definition == null || definition.isNumeric());
	}

	@Override
	public boolean isString() {
		return (definition == null || definition.isString());
	}

	@Override
	public boolean isGeometry() {
		return (definition == null || definition.isGeometry());
	}

	@Override
	public ADQLObject getCopy() throws Exception {
		UserDefinedFunction copy = new UserDefinedFunction(this);
		copy.setDefinition(definition);
		return copy;
	}

	@Override
	public String getName() {
		return functionName;
	}

	@Override
	public ADQLOperand[] getParameters() {
		ADQLOperand[] params = new ADQLOperand[parameters.size()];
		int i = 0;
		for(ADQLOperand op : parameters)
			params[i++] = op;
		return params;
	}

	@Override
	public int getNbParameters() {
		return parameters.size();
	}

	@Override
	public ADQLOperand getParameter(int index) throws ArrayIndexOutOfBoundsException {
		return parameters.get(index);
	}

	/**
	 * Function to override if you want to check the parameters of this user
	 * defined function.
	 *
	 * @see adql.query.operand.function.ADQLFunction#setParameter(int, adql.query.operand.ADQLOperand)
	 *
	 * @since 2.0
	 */
	@Override
	public ADQLOperand setParameter(int index, ADQLOperand replacer) throws ArrayIndexOutOfBoundsException, NullPointerException, Exception {
		ADQLOperand oldParam = parameters.set(index, replacer);
		setPosition(null);
		return oldParam;
	}

	/**
	 * Translate this User Defined Function into the language supported by the
	 * given translator.
	 *
	 * <p><b>VERY IMPORTANT:</b>
	 * 	This function <b>MUST NOT use</b>
	 * 	{@link ADQLTranslator#translate(UserDefinedFunction)} to translate
	 * 	itself. The given {@link ADQLTranslator} <b>must be used ONLY</b> to
	 * 	translate UDF's operands.
	 * </p>
	 *
	 * <p>Implementation example:</p>
	 * <pre>
	 * public String translate(final ADQLTranslator caller) throws TranslationException{
	 * 	StringBuilder sql = new StringBuilder(functionName);
	 * 	sql.append('(');
	 * 	for(int i = 0; i < parameters.size(); i++){
	 *		if (i > 0)
	 *			sql.append(',').append(' ');
	 * 		sql.append(caller.translate(parameters.get(i)));
	 *	}
	 *	sql.append(')');
	 *	return sql.toString();
	 * }
	 * </pre>
	 *
	 * @param caller	Translator to use in order to translate <b>ONLY</b>
	 *              	function parameters.
	 *
	 * @return	The translation of this UDF into the language supported by the
	 *        	given translator,
	 *        	or NULL to let the calling translator apply a default
	 *        	translation.
	 *
	 * @throws TranslationException	If one of the parameters can not be
	 *                             	translated.
	 *
	 * @since 1.3
	 */
	public String translate(final ADQLTranslator caller) throws TranslationException {
		// Use the custom translator if any is specified:
		if (definition != null && definition.withCustomTranslation()) {
			try {
				FunctionTranslator translator = definition.createTranslator();
				return translator.translate(this, caller);
			} catch(TranslationException te) {
				throw te;
			} catch(Exception ex) {
				throw new TranslationException("Impossible to translate the function \"" + getName() + "\" (" + toADQL() + ")! Cause: error with a custom FunctionTranslator: \"" + ((ex instanceof InvocationTargetException) ? "[" + ex.getCause().getClass().getSimpleName() + "] " + ex.getCause().getMessage() : ex.getMessage()) + "\".", ex);
			}
		}
		// Otherwise, no translation needed (let the caller use a default translation):
		else
			return null;
	}

}
