package ModSQL;
import java.sql.*;
 
/* $Id: Function_String.java,v 1.7 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 function for string operations.
 *
 * <p>Function_String provides: LEFT, RIGHT, UPPER, LOWER,
 * INITCAP, LENGTH, LPAD, RPAD, and SUBSTR.
 *
 * @author chris.studholme@utoronto.ca
 */
final class Function_String extends AbstractFunction {
  
  /** Function is LEFT(). */
  public static final int LEFT    = 0;
  /** Function is RIGHT(). */
  public static final int RIGHT   = 1;
  /** Function is UPPER(). */
  public static final int UPPER   = 2;
  /** Function is LOWER(). */
  public static final int LOWER   = 3;
  /** Function is INITCAP(). */
  public static final int INITCAP = 4;
  /** Function is LENGTH(). */
  public static final int LENGTH  = 5;
  /** Function is LPAD(). */
  public static final int LPAD    = 6;
  /** Function is RPAD(). */
  public static final int RPAD    = 7;
  /** Function is SUBSTR(). */
  public static final int SUBSTR  = 8;

  /** Which function are we?  One of the above constants. */
  private int op;

  /**
   * Returns a new instance of this class if name is recognized.  The
   * instance is initialized as needed to represent the desired function.
   *
   * @param name name of function to find
   * @return instance of this class or null if name does not match
   */
  public static Function forName(String name) {
    if (name.equals("left"))    return new Function_String(LEFT);
    if (name.equals("right"))   return new Function_String(RIGHT);
    if (name.equals("upper"))   return new Function_String(UPPER);
    if (name.equals("lower"))   return new Function_String(LOWER);
    if (name.equals("initcap")) return new Function_String(INITCAP);
    if (name.equals("length"))  return new Function_String(LENGTH);
    if (name.equals("lpad"))    return new Function_String(LPAD);
    if (name.equals("rpad"))    return new Function_String(RPAD);
    if (name.equals("substr"))  return new Function_String(SUBSTR);
    return null;
  }

  /**
   * Constructor.
   *
   * @param op function type
   */
  public Function_String(int op) {
    super();
    this.op = op;
  }


  /****************  setup methods  ****************/

  /**
   * Prepare the function for use.  
   *
   * @throws SQLException if the parameters are invalid
   */
  public void optimize() throws SQLException {
    super.optimize();

    // make sure we have at least one parameter
    if (parameters.length<1)
      throw new SQLException(functionName()+
			     " requires at least one parameter");

    // make sure the first parameter is a string
    switch (parameters[0].getSQLType()) {
    case Types.NULL:
    case Types.CHAR:
    case Types.VARCHAR:
    case Types.LONGVARCHAR:
      break;
    default:
      throw new SQLException("first parameter in function "+functionName()+
			     " must be a string");
    }

    switch (op) {
    case LENGTH:
    case UPPER:
    case LOWER:  
    case INITCAP:
      if (parameters.length>1)
	throw new SQLException(functionName()+
			       " accepts at most one parameter");
      break;

    case LEFT:
    case RIGHT:
    case LPAD:
    case RPAD:
    case SUBSTR:
      if (parameters.length<2)
	throw new SQLException(functionName()+
			       " requires at least two parameters");
      // make sure second parameter is an integer
      switch (parameters[1].getSQLType()) {
      case Types.NULL:
      case Types.TINYINT:
      case Types.SMALLINT:
      case Types.INTEGER:
      case Types.BIGINT:
	break;
      default:
	throw new SQLException("second parameter in function "+functionName()+
			       " must be an integer");
      }
      // check remaining parameter
      switch (op) {
      case LEFT:
      case RIGHT:
	if (parameters.length>2)
	  throw new SQLException(functionName()+
				 " accepts at most two parameters");
	break;

      case LPAD:
      case RPAD:
      case SUBSTR:
	if (parameters.length>3)
	  throw new SQLException(functionName()+
				 " accepts at most three parameters");
	// just check the type in optimize
      }      
      break;
      
    default:
      throw new SQLException("SOFTWARE BUG: invalid operation");
    }
  }


  /****************  information methods  ****************/

  /**
   * Returns the SQL type of this function.
   *
   * @return SQL type of data to be returned
   */
  public int getSQLType() throws SQLException {
    return op==LENGTH ? Types.INTEGER : Types.VARCHAR;
  }

  /**
   * Returns the maximum size of any String computed by this function.  If
   * a maximum size cannot be determined or this function does not return
   * a String (ie. LENGTH), -1 is returned.
   *
   * @return maximum size of String returned or -1 if unknown
   */
  public int getMaxResultSize() throws SQLException {
    int param1 = parameters[0].getMaxResultSize();
    int param2 = -1;
    if (parameters.length>=2 && parameters[1].isConstant()) {
      try {
	Number n = (Number)parameters[1].evaluate(false);
	if (n!=null) param2 = n.intValue();
      }
      catch (EndOfTable e) {
	throw new SQLExceptionWithCause("SOFTWARE BUG",e);
      }
    }

    switch (op) {
    case LENGTH:
    case UPPER:
    case LOWER:  
    case INITCAP:
      return param1;

    case LPAD:
    case RPAD:
      if (param1==-1 || param2==-1) return -1;
      return Math.max(param1,param2);

    case LEFT:
    case RIGHT:
      if (param1==-1) return param2;
      if (param2==-1) return -1;
      return Math.min(param1,param2);

    case SUBSTR:
      int param3 = -1;
      if (parameters.length>=3 && parameters[2].isConstant()) {
	try {
	  Number n = (Number)parameters[2].evaluate(false);
	  if (n!=null) param3 = n.intValue();
	}
	catch (EndOfTable e) {
	  throw new SQLExceptionWithCause("SOFTWARE BUG:",e);
	}
      }
      if (param1>=0) {
	int max = param1;
	if (param2>=0) max-=param2;
	if (param3>=0 && param3<max) max=param3;
	return max;
      }
      return param3>=0 ? param3 : -1;
      
    default:
      throw new SQLException("SOFTWARE BUG: invalid operation");
    }
  }

  /**
   * Returns the name of this function for use by toString() method.
   *
   * @return name of function
   */
  public String functionName() {
    switch (op) {
    case LEFT:    return "LEFT";
    case RIGHT:   return "RIGHT";
    case UPPER:   return "UPPER";
    case LOWER:   return "LOWER";
    case INITCAP: return "INITCAP";
    case LENGTH:  return "LENGTH";
    case LPAD:    return "LPAD";
    case RPAD:    return "RPAD";
    case SUBSTR:  return "SUBSTR";
    }
    return "#UNKNOWN";
  }

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

  /**
   * <p>Evaluate parameters and compute function.
   *
   * @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 {
    String str = (String)parameters[0].evaluate(aggregate);
    if (str==null)
      return null;

    switch (op) {
    case LENGTH:
      return new Integer(str.length());

    case UPPER:
      return str.toUpperCase();
    case LOWER:  
      return str.toLowerCase();
    case INITCAP:
      throw new SQLException(functionName()+" not implemented");

    case LPAD:
    case RPAD:
    case LEFT:
    case RIGHT:
    case SUBSTR:
      Number N = (Number)parameters[1].evaluate(false);
      if (N==null) return null;
      int n = N.intValue();

      switch (op) {
      case LEFT:
	return n<str.length() ? str.substring(0,n) : str;
      case RIGHT:
	return n<str.length() ? str.substring(str.length()-n) : str;

      case LPAD:
      case RPAD:
	if (str.length()>=n)
	  return str;
	char c = ' ';
	if (parameters.length>2) {
	  String pc = (String)parameters[2].evaluate(aggregate);
	  if (pc==null)
	    return null;
	  if (pc.length()!=1)
	    throw new SQLException("single character expected in "+
				   functionName());
	  c = pc.charAt(0);
	}
	int pad_length = n-str.length();
	StringBuffer pad = new StringBuffer(pad_length);
	while (--pad_length>=0) pad.append(c);
	return op==LPAD ? pad+str : str+pad;

      case SUBSTR:
	if (parameters.length<=2) 
	  return str.substring(n-1);
	Number L = (Number)parameters[2].evaluate(false);
	if (L==null) return null;
	int l = L.intValue();
	return n-1+l>=str.length() ? 
	  str.substring(n-1) : str.substring(n-1,n-1+l);
      }
      
    default:
      throw new SQLException("SOFTWARE BUG: invalid operation");
    }
  }
};

