package ModSQL;
import java.sql.*;
 
/* $Id: Operator_Add.java,v 1.11 2003/07/09 06:54:49 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.
 */

/**
 * <p>SQL operator "+". 
 *
 * @author chris.studholme@utoronto.ca
 */
final class Operator_Add extends Operator {

  /** Sum of all constant parameters. */
  private Number constant_value=null;
  /** SQL return type of operator. */
  private int return_type;

  
  /**
   * Adds a function to the list of parameters maintained by this operator.
   * If the function is of type Operator_Negate and has double negation,
   * the double negation will be removed.  If the function is of type
   * Operator_Add, or Operator_Negate followed by Operator_Add, the functions
   * parameters (summands) are added instead of the function.
   *
   * @param function function to add
   * @throws SQLException if a database error occurs
   */
  public void addParameter(Function function) throws SQLException {
    function = removeDoubleNegation(function);
    if (function instanceof Operator_Add)
      addAllParameters(function);
    else if (function instanceof Operator_Negate &&
	     function.getParameterCount()==1 &&
	     function.getParameter(0) instanceof Operator_Add)
      addAllParametersNegate(function.getParameter(0));
    else
      super.addParameter(function);
  }


  /**
   * <p>Prepare the function for use.  This method determines the return
   * type of the operator, evaluates all constant parameters and computes
   * the sum of the constant parameters.
   *
   * @throws SQLException if the parameters are invalid
   */
  public void optimize() throws SQLException {
    super.optimize();
    if (parameters.length<=0) 
      throw new SQLException("'+' operator requires at least one parameter");

    // figure out return type
    return_type = Types.NULL;
    for (int i=0; i<parameters.length; ++i) 
      return_type = getCompatableType(return_type,parameters[i].getSQLType());

    switch (return_type) {
    case Types.NULL:
    case Types.TINYINT:
    case Types.SMALLINT:
    case Types.INTEGER:
    case Types.BIGINT:
    case Types.REAL:
    case Types.FLOAT:
    case Types.DOUBLE:
      break;
    default:
      throw new SQLException("invalid parameter type in '+' operator");
    }
    
    // compute constant value
    if (evaluateConstantParameters()) {
      constant_value = new Byte((byte)0);
      for (int i=0; i<parameter_constant.length; ++i)
	if (parameter_constant[i])
	  constant_value = NumberMath.add(constant_value,
					  (Number)parameter_value[i]);
      constant_value = (Number)convertToSQLType(constant_value,return_type);
    }
    else
      constant_value = null;
  }

  /**
   * Returns "#ADD".
   *
   * @return name of function 
   */
  public String functionName() {
    return "#ADD";
  }


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

  /**
   * Return the SQL type of the value that this function expects to return.
   * The data returned by the function must either be of this type, or null.
   *
   * @return SQL type of data to be returned
   */
  public int getSQLType() throws SQLException {
    return return_type;
  }


  /**
   * Returns -1 as this operator will never return a String object.
   *
   * @return maximum size of String returned or -1 if unknown
   */
  public int getMaxResultSize() {
    return -1;
  }


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


  /**
   * Evaluate parameters and compute sum.
   *
   * @param aggregate passed to parameters
   * @return result object
   * @throws SQLException if a database-access error occurs
   * @throws EndOfTable if thrown by a parameter
   */
  public Object evaluate(boolean aggregate) throws SQLException, EndOfTable {
    if (constant_value==null || return_type==Types.NULL)
      return null;
    Number sum = constant_value;
    for (int i=0; i<evaluate_order.length; ++i) {
      if (!parameter_constant[evaluate_order[i]]) {
	Number term = 
	  (Number)parameters[evaluate_order[i]].evaluate(aggregate);
	if (term==null)
	  return null;
	sum = NumberMath.add(sum,term);
      }
    }
    return convertToSQLType(sum,return_type);
  }

  /**
   * Evaluate parameters and compute sum.
   *
   * @param match_op how the value should be matched
   * @param match_value desired value
   * @return result object
   * @throws SQLException if a database-access error occurs
   * @throws EndOfTable if thrown by a parameter
   */
  public Object evaluate(int match_op, Object match_value)
    throws SQLException, EndOfTable {
    if (constant_value==null || return_type==Types.NULL)
      return null;
    Number sum = constant_value;
    for (int i=0; i<evaluate_order.length; ++i) {
      if (!parameter_constant[evaluate_order[i]]) {
	Number term;
	if (i<evaluate_order.length-1) {
	  if (match_op==MATCH_NE && match_value==null)
	    term = (Number)parameters[evaluate_order[i]].
	      evaluate(MATCH_NE,null);
	  else
	    term = (Number)parameters[evaluate_order[i]].evaluate(false);
	}
	else if (match_value==null)
	  term = (Number)parameters[evaluate_order[i]].
	    evaluate(match_op,null);
	else {
	  // match_value[!]=sum+next ==> next match_op (match_value-sum)
	  Number mv = NumberMath.subtract((Number)match_value,sum);
	  term = (Number)parameters[evaluate_order[i]].
	    evaluate(match_op,mv);
	}
	if (term==null)
	  return null;
	sum = NumberMath.add(sum,term);
      }
    }
    return convertToSQLType(sum,return_type);
  }
};
