content: /*
   *    This program is free software; you can redistribute it and/or modify
   *    it under the terms of the GNU General Public License as published by
   *    the Free Software Foundation; either version 2 of the License, or
   *    (at your option) any later version.
   *
   *    This program 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 General Public License for more details.
   *
   *    You should have received a copy of the GNU General Public License
   *    along with this program; if not, write to the Free Software
   *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
   */
  
  /*
   *    DefineFunction.java
   *    Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
   *
   */
  
  package weka.core.pmml;
  
  import java.util.ArrayList;
  
  import org.w3c.dom.Element;
  import org.w3c.dom.Node;
  import org.w3c.dom.NodeList;
  
  import weka.core.Attribute;
  
  /**
   * Class encapsulating DefineFunction (used in TransformationDictionary).
   * 
   * @author Mark Hall (mhall{[at]}pentaho{[dot]}com
   * @version $Revision 1.0 $
   */
  public class DefineFunction extends Function {
    
    /**
     * For serialization
     */
    private static final long serialVersionUID = -1976646917527243888L;
  
    /**
     * Inner class for handling Parameters
     */
    protected class ParameterField extends FieldMetaInfo {
      /**
       * For serialization
       */
      private static final long serialVersionUID = 3918895902507585558L;
  
      protected ParameterField(Element field) {
        super(field);
      }
      
      public Attribute getFieldAsAttribute() {
        if (m_optype == Optype.CONTINUOUS) {
          return new Attribute(m_fieldName);
        }
        // return a string attribute for categorical/ordinal optypes
        return new Attribute(m_fieldName, (ArrayList<String>)null);
      }
    }
    
    /** 
     * The list of parameters expected by this function. We can use this to do 
     * some error/type checking when users call setParameterDefs() on us 
     */
    protected ArrayList<ParameterField> m_parameters = new ArrayList<ParameterField>();
    
    /** The optype for this function */
    FieldMetaInfo.Optype m_optype = FieldMetaInfo.Optype.NONE;
    
    /** The Expression for this function to use */
    protected Expression m_expression = null;
    
    public DefineFunction(Element container, TransformationDictionary transDict) throws Exception {
      
      m_functionName = container.getAttribute("name");
      
      // get the optype for this function
      String opType = container.getAttribute("optype");
      if (opType != null && opType.length() > 0) {
        for (FieldMetaInfo.Optype o : FieldMetaInfo.Optype.values()) {
          if (o.toString().equals(opType)) {
            m_optype = o;
            break;
          }
        }
      } else {
        throw new Exception("[DefineFunction] no optype specified!!");
      }
      
      m_parameterDefs = new ArrayList<Attribute>();
      
      // get all the parameters
      NodeList paramL = container.getElementsByTagName("ParameterField");
      for (int i = 0; i < paramL.getLength(); i++) {
        Node paramN = paramL.item(i);
        if (paramN.getNodeType() == Node.ELEMENT_NODE) {
          ParameterField newP = new ParameterField((Element)paramN);
          m_parameters.add(newP);
          
          // set up default parameter definitions - these will probably get replaced
          // by more informative ones (i.e. possibly nominal attributes instead of
          // string attributes) later
          m_parameterDefs.add(newP.getFieldAsAttribute());
        }
      }
      
      m_expression = Expression.getExpression(container, m_optype, m_parameterDefs, transDict);
      
      // check that the optype of the Expression is compatible with ours
      if (m_optype == FieldMetaInfo.Optype.CONTINUOUS && 
          m_expression.getOptype() != m_optype) {
        throw new Exception("[DefineFunction] optype is continuous but our Expression's optype "
            + "is not.");
      }
      
      if ((m_optype == FieldMetaInfo.Optype.CATEGORICAL || m_optype == FieldMetaInfo.Optype.ORDINAL) !=
          (m_expression.getOptype() == FieldMetaInfo.Optype.CATEGORICAL || 
           m_expression.getOptype() == FieldMetaInfo.Optype.ORDINAL)) {
        throw new Exception("[DefineFunction] optype is categorical/ordinal but our Expression's optype "
            + "is not."); 
      }
    }
    
    public void pushParameterDefs() throws Exception {
      if (m_parameterDefs == null) {
        throw new Exception("[DefineFunction] parameter definitions are null! Can't "
            + "push them to encapsulated expression.");
      }
      
      m_expression.setFieldDefs(m_parameterDefs);
    }
  
    /**
     * Get the structure of the result produced by this function.
     * 
     * @return the structure of the result produced by this function.
     */
    public Attribute getOutputDef() {
      return m_expression.getOutputDef();
    }
  
    /**
     * Returns an array of the names of the parameters expected
     * as input by this function. May return null if this function
     * can take an unbounded number of parameters (i.e. min, max, etc.).
     * 
     * @return an array of the parameter names or null if there are an
     * unbounded number of parameters.
     */
    public String[] getParameterNames() {
      String[] result = new String[m_parameters.size()];
      for (int i = 0; i < m_parameters.size(); i++) {
        result[i] = m_parameters.get(i).getFieldName();
      }
      
      return result;
    }
  
    /**
     * Get the result of applying this function.
     * 
     * @param incoming the arguments to this function (supplied in order to match that
     * of the parameter definitions
     * @return the result of applying this function. When the optype is
     * categorical or ordinal, an index into the values of the output definition
     * is returned.
     * @throws Exception if there is a problem computing the result of this function
     */
    public double getResult(double[] incoming) throws Exception {
      
      if (incoming.length != m_parameters.size()) {
        throw new IllegalArgumentException("[DefineFunction] wrong number of arguments: expected "
            + m_parameters.size() + ", recieved " + incoming.length);
      }
  
      return m_expression.getResult(incoming);
    }
  
    /**
     * Set the structure of the parameters that are expected as input by
     * this function. This must be called before getOutputDef() is called.
     * 
     * @param paramDefs the structure of the input parameters
     * @throws Exception if the number or types of parameters are not acceptable by
     * this function
     */
    public void setParameterDefs(ArrayList<Attribute> paramDefs) throws Exception {
      if (paramDefs.size() != m_parameters.size()) {
        throw new Exception("[DefineFunction] number of parameter definitions does not match "
            + "number of parameters!");
      }
      
      // check these defs against the optypes of the parameters
      for (int i = 0; i < m_parameters.size(); i++) {
        if (m_parameters.get(i).getOptype() == FieldMetaInfo.Optype.CONTINUOUS) {
          if (!paramDefs.get(i).isNumeric()) {
            throw new Exception("[DefineFunction] parameter "
                + m_parameters.get(i).getFieldName() + " is continuous, but corresponding "
                + "supplied parameter def " + paramDefs.get(i).name() + " is not!");
          }
        } else {
          if (!paramDefs.get(i).isNominal() && !paramDefs.get(i).isString()) {
            throw new Exception("[DefineFunction] parameter "
                + m_parameters.get(i).getFieldName() + " is categorical/ordinal, but corresponding "
                + "supplied parameter def " + paramDefs.get(i).name() + " is not!");
          }
        }
      }
      
      // now we need to rename these argument definitions to match the names of
      // the actual parameters
      ArrayList<Attribute> newParamDefs = new ArrayList<Attribute>();
      for (int i = 0; i < paramDefs.size(); i++) {
        Attribute a = paramDefs.get(i);
        newParamDefs.add(a.copy(m_parameters.get(i).getFieldName()));
      }
      
      m_parameterDefs = newParamDefs;
      
      // update the Expression
      m_expression.setFieldDefs(m_parameterDefs);
    }
    
    public String toString() {
      return toString("");
    }
    
    public String toString(String pad) {
      StringBuffer buff = new StringBuffer();
      
      buff.append(pad + "DefineFunction (" + m_functionName + "):\n"
          + pad + "nparameters:\n");
      
      for (ParameterField p : m_parameters) {
        buff.append(pad + p.getFieldAsAttribute() + "\n");
      }
      
      buff.append(pad + "expression:\n" + m_expression.toString(pad + "  "));
      return buff.toString();
    }
  }
  