/**
   * Apply a function to the children of the specified node. This also handles indexing into
   * matrices and arrays, which look like function calls.
   *
   * <p>In the simplest cases, if the function is being applied to an expression that evaluated to a
   * FunctionToken, an ArrayToken, or a MatrixToken, then the function application is simply applied
   * to the available arguments.
   *
   * <p>More complex is if the function is being applied to an expression that does not evaluate as
   * above, resulting in three cases: Of primary interest is a function node that represents the
   * invocation of a Java method registered with the expression parser. This method uses the
   * reflection mechanism in the CachedMethod class to find the correct method, based on the types
   * of the arguments and invoke it. See that class for information about how method arguments are
   * matched.
   *
   * <p>A second case is the eval() function, which is handled specially in this method. The
   * argument to the function is evaluated, and the parsed as a string using the expression parser.
   * The result is then evaluated *in this evaluator*. This has the effect that any identifiers are
   * evaluated in the same scope as the original expression.
   *
   * <p>A third case is the matlab() function, which is also handled specially in this method,
   * allowing the evaluation of expressions in matlab if matlab is installed. The format of the
   * function is covered in {@link ptolemy.data.expr.MatlabUtilities#evaluate(String, Set,
   * ParserScope)} .
   *
   * @param node The specified node.
   * @exception IllegalActionException If an evaluation error occurs.
   */
  public void visitFunctionApplicationNode(ASTPtFunctionApplicationNode node)
      throws IllegalActionException {
    // First check to see if the name references a valid variable.
    ptolemy.data.Token value = null;
    String functionName = node.getFunctionName();

    if ((functionName != null) && (_scope != null)) {
      value = _scope.get(functionName);
    }

    // The first child contains the function name as an id.  It is
    // ignored, and not evaluated unless necessary.
    int argCount = node.jjtGetNumChildren() - 1;
    Type[] argTypes = new Type[argCount];
    ptolemy.data.Token[] argValues = new ptolemy.data.Token[argCount];

    // First try to find a signature using argument token values.
    for (int i = 0; i < argCount; i++) {
      // Save the resulting value.
      _evaluateChild(node, i + 1);

      ptolemy.data.Token token = _evaluatedChildToken;
      argValues[i] = token;
      argTypes[i] = token.getType();
    }

    if ((value != null) || (functionName == null)) {
      // The value of the first child should be either a FunctionToken,
      // an ArrayToken, or a MatrixToken.
      ptolemy.data.Token result;

      // Evaluate it, if necessary.
      if (value == null) {
        value = _evaluateChild(node, 0);
      }

      if (value instanceof ArrayToken) {
        if (argCount == 1) {
          result = _evaluateArrayIndex(node, value, argValues[0]);
        } else {
          // FIXME need better error message when the first child
          // is, say, an array expression
          throw new IllegalActionException(
              "Wrong number of indices " + "when referencing " + node.getFunctionName());
        }
      } else if (value instanceof MatrixToken) {
        if (argCount == 2) {
          result = _evaluateMatrixIndex(node, value, argValues[0], argValues[1]);
        } else {
          // FIXME need better error message when the first child
          // is, say, a matrix expression
          throw new IllegalActionException(
              "Wrong number of indices " + "when referencing " + node.getFunctionName());
        }
      } else if (value instanceof FunctionToken) {
        FunctionToken function = (FunctionToken) value;

        // check number of children against number of arguments of
        // function
        if (function.getNumberOfArguments() != argCount) {
          throw new IllegalActionException(
              "Wrong number of " + "arguments when applying function " + value.toString());
        }

        result = function.apply(argValues);
      } else {
        // FIXME: It might be the a parameter is
        // shadowing a built-in function, in which
        // case, thrown an exception seems bogus.

        // The value cannot be indexed or applied
        // throw exception.
        throw new IllegalActionException("Cannot index or apply arguments to " + value.toString());
      }

      _evaluatedChildToken = (result);
      return;
    }

    if (node.getFunctionName().compareTo("eval") == 0) {
      if (argCount == 1) {
        ptolemy.data.Token token = argValues[0];

        if (token instanceof StringToken) {
          // Note that we do not want to store a reference to
          // the parser, because parsers take up alot of memory.
          PtParser parser = new PtParser();
          ASTPtRootNode tree = parser.generateParseTree(((StringToken) token).stringValue());

          // Note that we evaluate the recursed parse tree
          // in the same scope as this parse tree.
          tree.visit(this);

          //  _evaluatedChildToken = (tree.getToken());
          // FIXME cache?
          return;
        }
      }

      throw new IllegalActionException(
          "The function \"eval\" is"
              + " reserved for reinvoking the parser, and takes"
              + " exactly one String argument.");
    }

    if (node.getFunctionName().compareTo("matlab") == 0) {
      _evaluateChild(node, 1);

      ptolemy.data.Token token = _evaluatedChildToken;

      if (token instanceof StringToken) {
        String expression = ((StringToken) token).stringValue();
        ParseTreeFreeVariableCollector collector = new ParseTreeFreeVariableCollector();
        Set freeVariables = collector.collectFreeVariables(node, _scope);
        _evaluatedChildToken = MatlabUtilities.evaluate(expression, freeVariables, _scope);
        return;
      } else {
        throw new IllegalActionException(
            "The function \"matlab\" is"
                + " reserved for invoking the matlab engine, and takes"
                + " a string matlab expression argument followed by"
                + " a list of variable names that the matlab expression"
                + " refers to.");
      }
    }

    // If not a special function, then reflect the name of the function.
    ptolemy.data.Token result = _functionCall(node.getFunctionName(), argTypes, argValues);
    _evaluatedChildToken = (result);
  }
  /**
   * Set the type of the given node to be the return type of the function determined for the given
   * node.
   *
   * @param node The specified node.
   * @exception IllegalActionException If an inference error occurs.
   */
  public void visitFunctionApplicationNode(ASTPtFunctionApplicationNode node)
      throws IllegalActionException {
    int argCount = node.jjtGetNumChildren() - 1;
    String functionName = node.getFunctionName();

    // Get the child types.
    Type[] childTypes = new Type[argCount];

    for (int i = 0; i < argCount; i++) {
      childTypes[i] = _inferChild(node, i + 1);

      if (childTypes[i] == null) {
        throw new RuntimeException("node " + node + " has null type.");
      }
    }

    Type baseType = null;

    if ((_scope != null) && (functionName != null)) {
      baseType = _scope.getType(functionName);
    }

    if ((baseType != null) || (functionName == null)) {
      baseType = _inferChild(node, 0);

      // Handle as an array or matrix index into a named
      // variable reference.
      if (baseType instanceof FunctionType) {
        _setType(node, ((FunctionType) baseType).getReturnType());
        return;
      } else if (argCount == 1) {
        if (baseType instanceof ArrayType) {
          _setType(node, ((ArrayType) baseType).getElementType());
          return;
        } else {
          _assert(
              true,
              node,
              "Cannot use array "
                  + "indexing on '"
                  + node.getFunctionName()
                  + "' because it does not have an array type.");
        }
      } else if (argCount == 2) {
        if (baseType instanceof MatrixType) {
          _setType(node, ((MatrixType) baseType).getElementType());
          return;
        } else {
          _assert(
              true,
              node,
              "Cannot use matrix "
                  + "indexing on '"
                  + node.getFunctionName()
                  + "' because it does not have a matrix type.");
        }
      }

      throw new IllegalActionException(
          "Wrong number of indices " + "when referencing " + functionName);
    }

    // Psuedo-temporary hack for casts....
    if ((functionName.compareTo("cast") == 0) && (argCount == 2)) {
      ASTPtRootNode castTypeNode = ((ASTPtRootNode) node.jjtGetChild(0 + 1));
      ParseTreeEvaluator parseTreeEvaluator = new ParseTreeEvaluator();

      try {
        ptolemy.data.Token t = parseTreeEvaluator.evaluateParseTree(castTypeNode, _scope);
        _setType(node, t.getType());
      } catch (IllegalActionException ex) {
        _setType(node, childTypes[0]);
      }

      return;

      // Note: We used to just do this, but in some case is it
      // useful to have functions which are type constructors...
      // Hence the above code.
      //  _setType(node,
      //     ((ASTPtRootNode) node.jjtGetChild(0 + 1)).getType());
      //  return;
    }

    // A hack, because the result of the 'fix' function is
    // dependent on its arguments, which should be constant.
    if ((functionName.compareTo("fix") == 0) && (argCount == 3)) {
      ASTPtRootNode lengthNode = ((ASTPtRootNode) node.jjtGetChild(1 + 1));
      ASTPtRootNode integerBitsNode = ((ASTPtRootNode) node.jjtGetChild(2 + 1));
      ParseTreeEvaluator parseTreeEvaluator = new ParseTreeEvaluator();

      try {
        ptolemy.data.Token length = parseTreeEvaluator.evaluateParseTree(lengthNode, _scope);

        ptolemy.data.Token integerBits =
            parseTreeEvaluator.evaluateParseTree(integerBitsNode, _scope);
        _setType(
            node,
            new FixType(
                new Precision(
                    ((ScalarToken) length).intValue(), ((ScalarToken) integerBits).intValue())));
        return;
      } catch (Throwable throwable) {
        // Do nothing... rely on the regular method resolution
        // to generate the right type.
      }
    }

    if (functionName.compareTo("eval") == 0) {
      // We can't infer the type of eval expressions...
      _setType(node, BaseType.GENERAL);
      return;
    }

    if (functionName.compareTo("matlab") == 0) {
      // We can't infer the type of matlab expressions...
      _setType(node, BaseType.GENERAL);
      return;
    }

    // Otherwise, try to reflect the method name.
    CachedMethod cachedMethod;

    try {
      cachedMethod = CachedMethod.findMethod(functionName, childTypes, CachedMethod.FUNCTION);
    } catch (Exception ex) {
      // Deal with what happens if the method is not found.
      // FIXME: hopefully this is monotonic???
      _setType(node, BaseType.UNKNOWN);
      return;
    }

    if (cachedMethod.isValid()) {
      Type type = cachedMethod.getReturnType();
      _setType(node, type);
    } else {
      // If we reach this point it means the function was not found on
      // the search path.
      StringBuffer buffer = new StringBuffer();

      for (int i = 0; i < childTypes.length; i++) {
        if (i == 0) {
          buffer.append(childTypes[i].toString());
        } else {
          buffer.append(", " + childTypes[i].toString());
        }
      }

      throw new IllegalActionException(
          "No matching function " + node.getFunctionName() + "( " + buffer + " ).");
    }
  }