package adql.query;

/*
 * 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 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
 */

import java.util.Vector;

import adql.query.constraint.ADQLConstraint;

/**
 * Represents a clause which deals with {@link ADQLConstraint}s
 * (i.e. WHERE, HAVING).
 *
 * <p>
 * 	The logical operators AND (see {@link ClauseConstraints#AND}) and OR
 * 	(see {@link ClauseConstraints#OR}) are managed in a separated list by this
 * 	class. To add a constraint you can use the default add functions or use the
 * 	one defined by this class:
 * </p>
 * <ul>
 * 	<li>{@link ClauseConstraints#add(String, ADQLConstraint) add(String, ADQLConstraint}:
 * 		which lets you specify the logical operator between the added constraint
 * 		(index=size()) and the previous one (index=size()-1) in the list.</li>
 * 	<li>{@link ClauseConstraints#add(int, String, ADQLConstraint) add(int, String, ADQLConstraint}:
 * 		which lets you specify the logical operator between the added constraint
 * 		(index) and the previous one (index-1) in the list.</li>
 * </ul>
 *
 * <p>
 * 	If no logical separator is specified the default one is used
 * 	(see {@link ClauseConstraints#getDefaultSeparator() getDefaultSeparator()}).
 * 	The only way to set this default separator is during the
 * 	{@link ClauseConstraints} creation (see
 * 	{@link ClauseConstraints#ClauseConstraints(String, String) ClauseConstraints(String, String)}).
 * </p>
 *
 * @author Gr&eacute;gory Mantelet (CDS)
 * @version 01/2012
 *
 * @see adql.query.constraint.ConstraintsGroup
 */
public class ClauseConstraints extends ClauseADQL<ADQLConstraint> {

	/** The logical separator OR <i>(By default, the default separator (see
	 * {@link ClauseConstraints#getDefaultSeparator() getDefaultSeparator()}) is
	 * equals to OR)</i>. */
	public final static String OR = "OR";

	/** The logical separator AND. */
	public final static String AND = "AND";

	/** The logical separator used when none is specified during a constraint
	 * insertion <i>(by default = {@link ClauseConstraints#OR OR})</i>. */
	protected final String defaultSeparator;

	/** The used separators for the constraints list
	 * (vSeparators.size() = size()-1 ; vSeparators.get(i) gives the separator
	 * between get(i-1) and get(i)). */
	private Vector<String> vSeparators = new Vector<String>();

	/**
	 * Builds a constraints list with only its name (which will prefix the whole
	 * list).
	 *
	 * <p><i><b>Note:</b>
	 * 	The default separator is automatically set to OR.
	 * </i></p>
	 *
	 * @param name	The name/prefix of the list.
	 */
	public ClauseConstraints(String name) {
		super(name);
		defaultSeparator = OR;
	}

	/**
	 * Builds a constraints list with its name and its default separator.
	 *
	 * <p><i><b>Note:</b>
	 * 	if the given separator is no {@link ClauseConstraints#AND} or
	 * 	{@link ClauseConstraints#OR}, the default separator is automatically set
	 * 	to {@link ClauseConstraints#OR}.
	 * </i></p>
	 *
	 * @param name			The name/prefix of the list.
	 * @param logicalSep	The constraints separator (a logical separator:
	 *                  	{@link ClauseConstraints#AND} or
	 *                  	{@link ClauseConstraints#OR}).
	 *
	 * @see ClauseConstraints#checkSeparator(String)
	 */
	public ClauseConstraints(String name, String logicalSep) {
		super(name);
		defaultSeparator = checkSeparator(logicalSep);
	}

	/**
	 * Builds a ClauseConstraints by copying the given one.
	 *
	 * @param toCopy		The ClauseConstraints to copy.
	 * @throws Exception	If there is an error during the copy.
	 *
	 * @see ClauseConstraints#checkSeparator(String)
	 */
	public ClauseConstraints(ClauseConstraints toCopy) throws Exception {
		super(toCopy);
		defaultSeparator = checkSeparator(toCopy.defaultSeparator);
	}

	/**
	 * Gets the default constraints separator ({@link ClauseConstraints#AND} or
	 * {@link ClauseConstraints#OR}).
	 *
	 * @return	The default separator.
	 */
	public final String getDefaultSeparator() {
		return defaultSeparator;
	}

	/**
	 * Checks/Converts the given separator in one of the two logical separators.
	 * If the given separator is neither AND nor OR, then the returned separator
	 * is {@link ClauseConstraints#OR OR}.
	 *
	 * @param sepToCheck	The separator to check/convert.
	 *
	 * @return				The understood separator (OR if neither AND nor OR).
	 */
	public final static String checkSeparator(String sepToCheck) {
		if (sepToCheck != null && sepToCheck.equalsIgnoreCase(AND))
			return AND;
		else
			return OR;
	}

	/**
	 * @see adql.query.ADQLList#add(adql.query.ADQLObject)
	 * @see ClauseConstraints#add(String, ADQLConstraint)
	 */
	@Override
	public boolean add(ADQLConstraint constraint) throws NullPointerException {
		return add(defaultSeparator, constraint);
	}

	/**
	 * @see adql.query.ADQLList#add(int, adql.query.ADQLObject)
	 * @see ClauseConstraints#add(int, String, ADQLConstraint)
	 */
	@Override
	public void add(int index, ADQLConstraint constraint) throws NullPointerException, ArrayIndexOutOfBoundsException {
		add(index, defaultSeparator, constraint);
	}

	/**
	 * Adds the given constraint with the given separator. The separator is
	 * added just before the added constraint, that is to say between the last
	 * constraint of the list and the added one.
	 *
	 * @param logicalSep	The separator to add just before the given constraint.
	 * @param constraint	The constraint to add.
	 *
	 * @return	<code>true</code> if the constraint has been successfully added,
	 *        	<code>false</code> otherwise.
	 *
	 * @throws NullPointerException		If the given constraint is NULL.
	 *
	 * @see ADQLList#add(ADQLObject)
	 * @see ClauseConstraints#checkSeparator(String)
	 */
	public boolean add(String logicalSep, ADQLConstraint constraint) throws NullPointerException {
		boolean added = super.add(constraint);
		if (added && size() > 1)
			vSeparators.add(checkSeparator(logicalSep));
		return added;
	}

	/**
	 * Adds the given constraint with the given separator at the given position
	 * in the constraints list. The separator is added just before the added
	 * constraint, that is to say between the (index-1)-th constraint of the
	 * list and the added one (at the index position).
	 *
	 * @param index			Position at which the given constraint must be
	 *             			added.
	 * @param logicalSep	The constraints separator to add just before the
	 *                  	given constraint.
	 * @param constraint	The constraint to add.
	 *
	 * @throws NullPointerException				If the given constraint is NULL.
	 * @throws ArrayIndexOutOfBoundsException	If the given index is incorrect
	 *                                       	(index < 0 || index >= size()).
	 *
	 * @see ADQLList#add(int, ADQLObject)
	 * @see ClauseConstraints#checkSeparator(String)
	 */
	public void add(int index, String logicalSep, ADQLConstraint constraint) throws NullPointerException, ArrayIndexOutOfBoundsException {
		super.add(index, constraint);
		if (index > 0)
			vSeparators.add(index - 1, checkSeparator(logicalSep));
	}

	/**
	 * @see adql.query.ADQLList#set(int, adql.query.ADQLObject)
	 * @see ClauseConstraints#set(int, String, ADQLConstraint)
	 */
	@Override
	public ADQLConstraint set(int index, ADQLConstraint constraint) throws NullPointerException, ArrayIndexOutOfBoundsException {
		return set(index, null, constraint);
	}

	/**
	 * Replaces the specified constraint by the given one with the given
	 * constraint separator. The separator is added just before the added
	 * constraint, that is to say between the (index-1)-th constraint of the
	 * list and the added one (at the index position).
	 *
	 * @param index			Position of the constraint to replace.
	 * @param logicalSep	The separator to insert just before the given
	 *                  	constraint (if NULL, the previous separator is kept).
	 * @param constraint	The replacer.
	 *
	 * @return	The replaced constraint.
	 *
	 * @throws NullPointerException				If the given constraint is NULL.
	 * @throws ArrayIndexOutOfBoundsException	If the given index is incorrect
	 *                                       	(index < 0 || index >= size()).
	 *
	 * @see ADQLList#set(int, ADQLObject)
	 */
	public ADQLConstraint set(int index, String logicalSep, ADQLConstraint constraint) throws NullPointerException, ArrayIndexOutOfBoundsException {
		ADQLConstraint replaced = super.set(index, constraint);
		if (replaced != null && logicalSep != null && index > 0)
			vSeparators.set(index - 1, logicalSep);
		return replaced;
	}

	@Override
	public void clear() {
		super.clear();
		vSeparators.clear();
	}

	@Override
	public ADQLConstraint remove(int index) throws ArrayIndexOutOfBoundsException {
		ADQLConstraint removed = super.remove(index);
		if (removed != null)
			if (index > 0)
				vSeparators.remove(index - 1);
			else if (index == 0)
				vSeparators.remove(index);
		return removed;
	}

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

	/**
	 * Only two values in this case: the both logical separators:
	 * {@link ClauseConstraints#AND AND} and {@link ClauseConstraints#OR OR}.
	 *
	 * @see adql.query.ADQLList#getPossibleSeparators()
	 */
	@Override
	public String[] getPossibleSeparators() {
		return new String[]{ AND, OR };
	}

	@Override
	public String getSeparator(int index) throws ArrayIndexOutOfBoundsException {
		if (index <= 0 || index > size())
			throw new ArrayIndexOutOfBoundsException("Impossible to get the logical separator between the item " + (index - 1) + " and " + index + "!");
		else
			return vSeparators.get(index - 1);
	}

}
