/*
 * @(#)AbstractFunction.java   2.5.0   6 September 2006
 *
 * Copyright 2006
 * College of Computer and Information Science
 * Northeastern University
 * Boston, MA  02115
 *
 * The Java Power Tools software may be used for educational
 * purposes as long as this copyright notice is retained intact
 * at the top of all source files.
 *
 * To discuss possible commercial use of this software, 
 * contact Richard Rasala at Northeastern University, 
 * College of Computer and Information Science,
 * 617-373-2462 or rasala@ccs.neu.edu.
 *
 * The Java Power Tools software has been designed and built
 * in collaboration with Viera Proulx and Jeff Raab.
 *
 * Should this software be modified, the words "Modified from 
 * Original" must be included as a comment below this notice.
 *
 * All publication rights are retained.  This software or its 
 * documentation may not be published in any media either
 * in whole or in part without explicit permission.
 *
 * This software was created with support from Northeastern 
 * University and from NSF grant DUE-9950829.
 */

package edu.neu.ccs.parser;

import java.text.ParseException;

import edu.neu.ccs.*;

/**
 * <p>Class <code>AbstractFunction</code> encapsulates a
 * function definition and its corresponding
 * <CODE>String</CODE> name for for use with a parser.</p>
 *
 * <p>This class is <i>abstract</i> since the method
 * <code>functionCall</code> must be defined in a
 * derived class.</p>
 * 
 * <p>As of 2.5.0, the class <code>AbstractParser</code>
 * was made concrete and renamed <code>BaseParser</code>.
 * At that time, its static inner classes were extracted
 * and made standalone.  This class was formerly named
 * <code>Procedure</code> but the name
 * <code>AbstractFunction</code> seemed more descriptive.</p>
 *
 * <p>This class should not be confused with the class
 * <code>Function</code> in <code>edu.neu.ccs</code>.
 * The latter class is defined solely to collect several
 * interfaces that are useful in classifying
 * mathematical functions.</p>
 * 
 * @author  Richard Rasala
 * @author  Jeff Raab
 */ 
public abstract class AbstractFunction {
    
    /** The name representing this function. */
    private String name = "";
    
    /** The number of arguments required by this function. */
    private int arguments = 0;
    
    
    /////////////////
    // Constructor //
    /////////////////
    
    /**
     * <p>Constructs a function 
     * with the given <code>String</code> name
     * and given number of expected arguments.</p>
     * 
     * <p>The given name is trimmed prior to being
     * used to define the function name.</p>
     * 
     * <p>Throws <code>IllegalArgumentException</code>
     * if the name is not valid when tested by
     * <code>BaseParser.isPossibleIdentifier</code> or
     * if the given arguments value is negative.</p>
     *
     * @param name the identifier for this function
     * @param arguments the required number of arguments
     * @throws IllegalArgumentException
     */
    public AbstractFunction(String name, int arguments) {
        String message = null;
        
        if (name == null) {
            message = "A function name must not be null";
        }
        else{
            name = name.trim();
            
            if (name.length() == 0) {
                message = "A function name must not be empty";
            }
            
            else if (! BaseParser.isPossibleIdentifier(name)) {
                message = name + " is not a valid identifier\n"
                    + "and therefore may not be a function name";
            }
            
            else if (arguments < 0){
                message = "function " + name
                    + " cannot be defined with "
                    + arguments + " arguments";
            }
        }
        
        if (message != null)
            throw new IllegalArgumentException(message);
        
        this.name = name;
        this.arguments = arguments;
    }
    
    
    /////////////
    // Methods //
    /////////////
    
    /**
     * <p>Applies the function 
     * using the provided array of argument values
     * whose length must equal the number of arguments
     * and returns its result.</p>
     *
     * @param values the array of argument values
     */
    public abstract Object functionCall(Object[] values)
        throws ParseException;
    
    
    /**
     * Returns the <code>String</code> name of this function.
     */
    public final String name() {
        return name;
    }
    
    
    /**
     * Returns the number of arguments of this function.
     */
    public final int arguments() {
        return arguments;
    }
    
    
    /**
     * Throws a ParseExpection if:
     * 
     * <ul>
     *   <li>the values are <code>null</code></li>
     *   <li>the length of values is not equal to arguments</li>
     * </ul>
     * 
     * @param values the array of argument values
     */
     public final void checkValues(Object[] values)
        throws ParseException
     {
        if (values == null)
            throw new ParseException(
                "Null arguments to function " + name
                , 0);
        
        if (values.length != arguments)
            throw new ParseException(
                "Function " + name + " expects "
                + arguments   + " argument(s) but was given "
                + values.length + " argument(s)"
                , 0);
     }
     
     
     /**
      * Throws a ParseExpection if:
      * 
      * <ul>
      *   <li>the values are <code>null</code></li>
      *   <li>the length of values is not equal to arguments</li>
      *   <li>the elements in values are not of type XObject</li>
      * </ul>
      * 
      * @param values the array of argument values
      */
     public final void checkValuesAsXObject(Object[] values)
        throws ParseException
     {
        checkValues(values);
        
        for (int i = 0; i < arguments; i++)
            if (! (values[i] instanceof XObject))
                throw new ParseException(
                    "Function "
                    + name
                    + " expects XObject argument in position "
                    + i
                    , 0);
     }
     
     
     /**
      * Throws a ParseExpection if:
      * 
      * <ul>
      *   <li>the values are <code>null</code></li>
      *   <li>the length of values is not equal to arguments</li>
      *   <li>the elements in values are not of type XNumber</li>
      * </ul>
      * 
      * @param values the array of argument values
      */
     public final void checkValuesAsNumeric(Object[] values)
        throws ParseException
     {
        checkValues(values);
        
        for (int i = 0; i < arguments; i++)
            if (! (values[i] instanceof XNumber))
                throw new ParseException(
                    "Function "
                    + name
                    + " expects numeric argument in position "
                    + i
                    , 0);
     }
     
     
     /**
      * <p>Constructs an <code>AbstractFunction</code> object
      * using the given name
      * and the given<code>Function.NoArg</code>
      * for its implementation.</p>
      * 
      * <p>Naturally, this function will require 0 arguments.</p>
      * 
      * @param name the function name to be used in a parser
      * @param f the implementation function
      * @throws IllegalArgumentException
      */
     public static AbstractFunction makeNoArg
         (String name, final Function.NoArg f)
     {
         return new AbstractFunction(name, 0) {
             {
                 if (f == null) {
                     String message = "makeNoArg"
                         + " must be passed a non-null "
                         + "Function.NoArg";
                     
                     throw new IllegalArgumentException(message);
                 }
             }
             
             public Object functionCall(Object[] values)
                 throws ParseException
             {
                 checkValuesAsNumeric(values);
                 
                 return new XDouble(f.evaluate());
             }
             
         };
     }
     
     
     /**
      * <p>Constructs an <code>AbstractFunction</code> object
      * using the given name
      * and the given<code>Function.OneArg</code>
      * for its implementation.</p>
      * 
      * <p>Naturally, this function will require 1 argument.</p>
      * 
      * @param name the function name to be used in a parser
      * @param f the implementation function
      * @throws IllegalArgumentException
      */
     public static AbstractFunction makeOneArg
         (String name, final Function.OneArg f)
     {
         return new AbstractFunction(name, 1) {
             {
                 if (f == null) {
                     String message = "makeOneArg"
                         + " must be passed a non-null "
                         + "Function.OneArg";
                     
                     throw new IllegalArgumentException(message);
                 }
             }
             
             public Object functionCall(Object[] values)
                 throws ParseException
             {
                 checkValuesAsNumeric(values);
                 
                 double arg0 = ((XNumber) values[0]).doubleValue();
                  
                 return new XDouble(f.evaluate(arg0));
             }
             
         };
     }
     
     
     /**
      * <p>Constructs an <code>AbstractFunction</code> object
      * using the given name
      * and the given<code>Function.TwoArg</code>
      * for its implementation.</p>
      * 
      * <p>Naturally, this function will require 2 arguments.</p>
      * 
      * @param name the function name to be used in a parser
      * @param f the implementation function
      * @throws IllegalArgumentException
      */
     public static AbstractFunction makeTwoArg
         (String name, final Function.TwoArg f)
     {
         return new AbstractFunction(name, 2) {
             {
                 if (f == null) {
                     String message = "makeTwoArg"
                         + " must be passed a non-null "
                         + "Function.TwoArg";
                     
                     throw new IllegalArgumentException(message);
                 }
             }
             
             public Object functionCall(Object[] values)
                 throws ParseException
             {
                 checkValuesAsNumeric(values);
                 
                 double arg0 = ((XNumber) values[0]).doubleValue();
                 double arg1 = ((XNumber) values[1]).doubleValue();
                  
                 return new XDouble(f.evaluate(arg0, arg1));
             }
             
         };
     }
     
     
     /**
      * <p>Constructs an <code>AbstractFunction</code> object
      * using the given name
      * and the given<code>Function.ThreeArg</code>
      * for its implementation.</p>
      * 
      * <p>Naturally, this function will require 3 arguments.</p>
      * 
      * @param name the function name to be used in a parser
      * @param f the implementation function
      * @throws IllegalArgumentException
      */
     public static AbstractFunction makeThreeArg
         (String name, final Function.ThreeArg f)
     {
         return new AbstractFunction(name, 3) {
             {
                 if (f == null) {
                     String message = "makeThreeArg"
                         + " must be passed a non-null "
                         + "Function.ThreeArg";
                     
                     throw new IllegalArgumentException(message);
                 }
             }
             
             public Object functionCall(Object[] values)
                 throws ParseException
             {
                 checkValuesAsNumeric(values);
                 
                 double arg0 = ((XNumber) values[0]).doubleValue();
                 double arg1 = ((XNumber) values[1]).doubleValue();
                 double arg2 = ((XNumber) values[2]).doubleValue();
                  
                 return new XDouble(f.evaluate(arg0, arg1, arg2));
             }
             
         };
     }
     
     
     /**
      * <p>Constructs an <code>AbstractFunction</code> object
      * using the given name
      * and the given<code>Function.FourArg</code>
      * for its implementation.</p>
      * 
      * <p>Naturally, this function will require 1 argument.</p>
      * 
      * @param name the function name to be used in a parser
      * @param f the implementation function
      * @throws IllegalArgumentException
      */
     public static AbstractFunction makeFourArg
         (String name, final Function.FourArg f)
     {
         return new AbstractFunction(name, 4) {
             {
                 if (f == null) {
                     String message = "makeFourArg"
                         + " must be passed a non-null "
                         + "Function.FourArg";
                     
                     throw new IllegalArgumentException(message);
                 }
             }
             
             public Object functionCall(Object[] values)
                 throws ParseException
             {
                 checkValuesAsNumeric(values);
                 
                 double arg0 = ((XNumber) values[0]).doubleValue();
                 double arg1 = ((XNumber) values[1]).doubleValue();
                 double arg2 = ((XNumber) values[2]).doubleValue();
                 double arg3 = ((XNumber) values[3]).doubleValue();
                  
                 return new XDouble(f.evaluate(arg0, arg1, arg2, arg3));
             }
             
         };
     }
     
}

