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 2011-2019 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
 *                       Astronomisches Rechen Institute (ARI)
 */

import adql.parser.feature.LanguageFeature;
import adql.query.ADQLObject;
import adql.query.TextPosition;
import adql.query.operand.ADQLOperand;

/**
 * It represents any SQL function (COUNT, MAX, MIN, AVG, SUM, etc...).
 *
 * @author Gr&eacute;gory Mantelet (CDS)
 * @version 2.0 (07/2019)
 *
 * @see SQLFunctionType
 */
public class SQLFunction extends ADQLFunction {

	/** Type of this SQL function. */
	private final SQLFunctionType type;

	/** The only parameter of this function (may be null). */
	private ADQLOperand param = null;

	/** Distinct values of the parameter ? */
	private boolean distinct = false;

	/**
	 * Creates a SQL function with one parameter.
	 *
	 * @param t							Type of the function.
	 * @param operand					The only parameter of this function.
	 * @throws NullPointerException	 	If the given is NULL
	 *                             		or if the given operand is NULL EXCEPT
	 *                             		when the given type is
	 *                             		{@link SQLFunctionType#COUNT_ALL}.
	 */
	public SQLFunction(SQLFunctionType t, ADQLOperand operand) throws NullPointerException {
		this(t, operand, false);
	}

	/**
	 * Creates a SQL function with one parameter.
	 *
	 * @param t							Type of the function.
	 * @param operand					The only parameter of this function.
	 * @param distinctValues			<code>true</code> if the quantifier
	 *                      			DISTINCT must be used,
	 *                      			<code>false</code> otherwise (ALL).
	 * @throws NullPointerException	 	If the given is NULL
	 *                             		or if the given operand is NULL EXCEPT
	 *                             		when the given type is
	 *                             		{@link SQLFunctionType#COUNT_ALL}.
	 */
	public SQLFunction(SQLFunctionType t, ADQLOperand operand, boolean distinctValues) throws NullPointerException {
		if (t == null)
			throw new NullPointerException("Impossible to build a SQLFunction without its type (COUNT, SUM, AVG, ...)!");
		else
			type = t;

		if (type == SQLFunctionType.COUNT_ALL)
			param = null;
		else if (operand == null)
			throw new NullPointerException("Impossible to build the SQL function \"" + type.name() + "\" without the operand on which it must apply!");
		else
			param = operand;

		distinct = distinctValues;
	}

	/**
	 * Builds a SQL function by copying the given one.
	 *
	 * @param toCopy		The SQL function to copy.
	 * @throws Exception	If there is an error during the copy.
	 */
	public SQLFunction(SQLFunction toCopy) throws Exception {
		type = toCopy.type;
		param = (ADQLOperand)toCopy.param.getCopy();
		distinct = toCopy.distinct;
		setPosition((toCopy.getPosition() == null) ? null : new TextPosition(toCopy.getPosition()));
		;
	}

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

	/**
	 * Indicates whether values of the parameter must be distinct or not.
	 *
	 * @return	<code>true</code> means distinct values,
	 *        	<code>false</code> else.
	 */
	public final boolean isDistinct() {
		return distinct;
	}

	/**
	 * Tells if distinct values of the given parameter must be taken.
	 *
	 * @param distinctValues	<code>true</code> means distinct values,
	 *                      	<code>false</code> else.
	 */
	public void setDistinct(boolean distinctValues) {
		distinct = distinctValues;
		setPosition(null);
	}

	/**
	 * Gets the type (COUNT, SUM, AVG, ...) of this function.
	 *
	 * @return	Its type.
	 */
	public final SQLFunctionType getType() {
		return type;
	}

	@Override
	public ADQLObject getCopy() throws Exception {
		return new SQLFunction(this);
	}

	@Override
	public String getName() {
		return type.name();
	}

	@Override
	public final boolean isNumeric() {
		return true;
	}

	@Override
	public final boolean isString() {
		return false;
	}

	@Override
	public final boolean isGeometry() {
		return false;
	}

	@Override
	public ADQLOperand[] getParameters() {
		if (param != null)
			return new ADQLOperand[]{ param };
		else
			return new ADQLOperand[0];
	}

	@Override
	public int getNbParameters() {
		return (type == SQLFunctionType.COUNT_ALL) ? 0 : 1;
	}

	@Override
	public ADQLOperand getParameter(int index) throws ArrayIndexOutOfBoundsException {
		if (index < 0 || index >= getNbParameters())
			throw new ArrayIndexOutOfBoundsException("No " + index + "-th parameter for the function \"" + type.name() + "\"!");
		else
			return param;
	}

	@Override
	public ADQLOperand setParameter(int index, ADQLOperand replacer) throws ArrayIndexOutOfBoundsException, NullPointerException, Exception {
		if (index < 0 || index >= getNbParameters())
			throw new ArrayIndexOutOfBoundsException("No " + index + "-th parameter for the function \"" + type.name() + "\"!");

		if (replacer == null)
			throw new NullPointerException("Impossible to remove the only required parameter of the function \"" + type.name() + "\"!");

		ADQLOperand replaced = param;
		param = replacer;
		setPosition(null);

		return replaced;
	}

	@Override
	public String toADQL() {
		if (type == SQLFunctionType.COUNT_ALL)
			return "COUNT(" + (distinct ? "DISTINCT " : "") + "*)";
		else
			return getName() + "(" + (distinct ? "DISTINCT " : "") + param.toADQL() + ")";
	}

}
