package ModSQL;
import java.sql.Types;
import java.sql.SQLException;
import java.io.PrintStream;

/* $Id: IndirectFunction.java,v 1.10 2003/05/29 04:58:40 cvs Exp $
 *
 * Copyright (c) 2003 Chris Studholme <chris.studholme@utoronto.ca>
 *
 * May be copied or modified under the terms of the GNU General Public
 * License.  See COPYING for more information.
 */

/**
 * Wrapper for some other function.  This class is typically used to hold
 * column names before the names can be looked up.  It may also be used to
 * make a function appear in two seperate locations (such as SELECT 
 * expressions appearing in the ORDER BY clause).
 *
 * @author chris.studholme@utoronto.ca
 */
final class IndirectFunction implements Function {

  /** Text description of function (usually a column name).  */
  private String description;
  /** Function being wrapped. */
  private Function value;

  /** Make the function appear as an aggregate regardless of whether it is
   * or not (used for GROUP BY columns). */
  private boolean force_aggregate;
  /** Last value returned by function (used for forced aggregates). */
  private Object last_value;
  
  /**
   * Constructor.
   *
   * @param description text description of function (column name)
   */
  public IndirectFunction(String description) {
    this.description = description;
    value = null;
    force_aggregate = false;
    last_value = null;
  }

  /**
   * Returns string version of function or description if function is not
   * set yet.
   *
   * @return human readable description of function
   */
  public String toString() {
    return value!=null ? value.toString() : description;
  }

  /**
   * Get text description of function.
   *
   * @return text description of function
   */
  public String getDescription() {
    return description;
  }

  /**
   * Get function being wrapped.
   *
   * @return function being wrapped (null if not set)
   */
  public Function getFunction() {
    return value;
  }


  /****************  parameter handling methods  ****************/

  /**
   * Get the number of parameters.  Returns 0 as parameters cannot be
   * indirectly accessed.
   *
   * @return number of parameters
   */
  public int getParameterCount() {
    return 0;
  }
  
  /**
   * Get a particular parameter.  Throws an exception as parameters cannot
   * be indirectly accessed.
   *
   * @param index index of parameter to get
   * @return parameter (function)
   * @throws IndexOutOfBoundsException always
   */
  public Function getParameter(int index) {
    throw new IndexOutOfBoundsException("parameters not supported");
  }

  /**
   * Adds a parameter to the list of parameters.  Throws an exception as
   * parameters cannot be indirectly accessed.
   *
   * @param item function to add
   * @throws SQLException always
   */
  public void addParameter(Function item) throws SQLException {
    throw new SQLException("parameters not supported");
  }

  /**
   * Specify the order in which the parameters should be evaluated.  Throws
   * an exception as parameters cannot be indirectly accessed.
   *
   * @param index index of parameter
   * @param order number indicating order in which parameter is evaluated
   * @throws IndexOutOfBoundsException always
   */
  public void evaluateOrder(int index, int order) {
    throw new IndexOutOfBoundsException("parameters not supported");
  }


  /**
   * <p>This method responds to two objects:
   * <ul>
   * <li>{@link FunctionProvider} to lookup the description if the function has not been set yet, and
   * <li>{@link Select.GroupByList} to check for GROUP BY columns that need to be
   * forced to aggregate.
   * </ul>
   *
   * <p>Otherwise, if the function has been set, the object is passed on
   * to that function.
   *
   * @param o object to register with
   * @throws SQLException if an error occurs
   */
  public void registerWith(Object o) throws SQLException {
    if (value==null) {
      if (o instanceof FunctionProvider)
	value = ((FunctionProvider)o).lookupFunction(description);
    }
    else {
      if (o instanceof Select.GroupByList)
	force_aggregate = ((Select.GroupByList)o).inList(value);	
      value.registerWith(o);
    }
  }


  /**
   * <p>If the function has been set, it is optimized.  Otherwise, an
   * exception is thrown.
   *
   * @throws SQLException if an error occurs
   */
  public void optimize() throws SQLException {
    if (value==null)
      throw new SQLException("no value available");
    value.optimize();
  }


  /****************  result meta-data  ****************/

  /**
   * Determine if this function returns a constant value.  
   *
   * @return true if the value returned is constant
   * @throws SQLException if an error occurs
   */
  public boolean isConstant() throws SQLException {
    return value.isConstant();
  }

  /**
   * Determine if this function will return a value that is an aggregate of
   * many database rows.  This method may return true in cases where the
   * function is actually not an aggregate (in this case, aggregate has been
   * forced).
   *
   * @return true if the function aggregates several rows of data
   * @throws SQLException if an error occurs
   */
  public boolean isAggregate() throws SQLException {
    return force_aggregate ? true : value.isAggregate();
  }

  /**
   * Return the SQL type of the value that this function expects to return.
   *
   * @return SQL type of data to be returned
   * @throws SQLException if an error occurs
   */
  public int getSQLType() throws SQLException {
    return value.getSQLType();
  }

  /**
   * Return the maximum number of characters that this function expects to
   * return in a String object.  
   *
   * @return maximum size of String returned or -1 if unknown
   * @throws SQLException if an error occurs
   */
  public int getMaxResultSize() throws SQLException {
    return value.getMaxResultSize();
  }


  /****************  evaluation methods  ****************/

  /**
   * Evaluate the function and return the result.  If aggregate is true, but
   * the function is not an aggregate and aggregate is being forced, the
   * last value returned by evaluate is returned again.  In this case, if
   * evaluate() has never been called, null is returned.
   *
   * @param aggregate true to get final aggregate value
   * @return result object
   * @throws SQLException if an error occurs
   * @throws EndOfTable if thrown by the function
   */
  public Object evaluate(boolean aggregate) throws SQLException,EndOfTable {
    if (aggregate && force_aggregate && !value.isAggregate())
      return last_value;
    return last_value=value.evaluate(aggregate);
  }

  /**
   * Calls the equivalent evaluate() method for the underlying function.
   *
   * @param match_op how the value should be matched
   * @param match_value desired value
   * @return result object
   * @throws SQLException if an error occurs
   * @throws EndOfTable if thrown by the function
   */
  public Object evaluate(int match_op, Object match_value) 
    throws SQLException,EndOfTable {
    return last_value=value.evaluate(match_op,match_value);
  }

  /**
   * Calls reset() for the underlying function and set the last value
   * returned by evaluate() to null.
   *
   * @throws SQLException if an error occurs
   */
  public void reset() throws SQLException {
    last_value = null;
    value.reset();
  }

};
