/**
   * Static method to convert an array of Ptgs in RPN order to a human readable string format in
   * infix mode.
   *
   * @param book workbook for named and 3D references
   * @param ptgs array of Ptg, can be null or empty
   * @return a human readable String
   */
  public static String toFormulaString(Workbook book, Ptg[] ptgs) {
    if (ptgs == null || ptgs.length == 0) return "#NAME";
    java.util.Stack stack = new java.util.Stack();
    AttrPtg ifptg = null;

    // Excel allows to have AttrPtg at position 0 (such as Blanks) which
    // do not have any operands. Skip them.
    stack.push(ptgs[0].toFormulaString(book));

    for (int i = 1; i < ptgs.length; i++) {
      if (!(ptgs[i] instanceof OperationPtg)) {
        stack.push(ptgs[i].toFormulaString(book));
        continue;
      }

      if (ptgs[i] instanceof AttrPtg && ((AttrPtg) ptgs[i]).isOptimizedIf()) {
        ifptg = (AttrPtg) ptgs[i];
        continue;
      }

      final OperationPtg o = (OperationPtg) ptgs[i];
      final String[] operands = new String[o.getNumberOfOperands()];

      for (int j = operands.length; j > 0; j--) {
        // TODO: catch stack underflow and throw parse exception.
        operands[j - 1] = (String) stack.pop();
      }

      stack.push(o.toFormulaString(operands));
      if (!(o instanceof AbstractFunctionPtg)) continue;

      final AbstractFunctionPtg f = (AbstractFunctionPtg) o;
      final String fname = f.getName();
      if (fname == null) continue;

      if ((ifptg != null) && (fname.equals("specialflag"))) {
        // this special case will be way different.
        stack.push(ifptg.toFormulaString(new String[] {(String) stack.pop()}));
        continue;
      }
      if (fname.equals("externalflag")) {
        final String top = (String) stack.pop();
        final int paren = top.indexOf('(');
        final int comma = top.indexOf(',');
        if (comma == -1) {
          final int rparen = top.indexOf(')');
          stack.push(top.substring(paren + 1, rparen) + "()");
        } else {
          stack.push(top.substring(paren + 1, comma) + '(' + top.substring(comma + 1));
        }
      }
    }
    // TODO: catch stack underflow and throw parse exception.
    return (String) stack.pop();
  }
  /**
   * Generates the variable function ptg for the formula.
   *
   * <p>For IF Formulas, additional PTGs are added to the tokens
   *
   * @param name
   * @param numArgs
   * @return Ptg a null is returned if we're in an IF formula, it needs extreme manipulation and is
   *     handled in this function
   */
  private AbstractFunctionPtg getFunction(String name, byte numArgs) {
    AbstractFunctionPtg retval = null;

    if (name.equals("IF")) {
      retval = new FuncVarPtg(AbstractFunctionPtg.ATTR_NAME, numArgs);

      // simulated pop, no bounds checking because this list better be populated by function()
      List argumentPointers = (List) this.functionTokens.get(0);

      AttrPtg ifPtg = new AttrPtg();
      ifPtg.setData((short) 7); // mirroring excel output
      ifPtg.setOptimizedIf(true);

      if (argumentPointers.size() != 2 && argumentPointers.size() != 3) {
        throw new IllegalArgumentException(
            "["
                + argumentPointers.size()
                + "] Arguments Found - An IF formula requires 2 or 3 arguments. IF(CONDITION, TRUE_VALUE, FALSE_VALUE [OPTIONAL]");
      }

      // Biffview of an IF formula record indicates the attr ptg goes after the condition ptgs and
      // are
      // tracked in the argument pointers
      // The beginning first argument pointer is the last ptg of the condition
      int ifIndex = tokens.indexOf(argumentPointers.get(0)) + 1;
      tokens.add(ifIndex, ifPtg);

      // we now need a goto ptgAttr to skip to the end of the formula after a true condition
      // the true condition is should be inserted after the last ptg in the first argument

      int gotoIndex = tokens.indexOf(argumentPointers.get(1)) + 1;

      AttrPtg goto1Ptg = new AttrPtg();
      goto1Ptg.setGoto(true);

      tokens.add(gotoIndex, goto1Ptg);

      if (numArgs > 2) { // only add false jump if there is a false condition

        // second goto to skip past the function ptg
        AttrPtg goto2Ptg = new AttrPtg();
        goto2Ptg.setGoto(true);
        goto2Ptg.setData((short) (retval.getSize() - 1));
        // Page 472 of the Microsoft Excel Developer's kit states that:
        // The b(or w) field specifies the number byes (or words to skip, minus 1

        tokens.add(goto2Ptg); // this goes after all the arguments are defined
      }

      // data portion of the if ptg points to the false subexpression (Page 472 of MS Excel
      // Developer's kit)
      // count the number of bytes after the ifPtg to the False Subexpression
      // doesn't specify -1 in the documentation
      ifPtg.setData((short) (getPtgSize(ifIndex + 1, gotoIndex)));

      // count all the additional (goto) ptgs but dont count itself
      int ptgCount = this.getPtgSize(gotoIndex) - goto1Ptg.getSize() + retval.getSize();
      if (ptgCount > (int) Short.MAX_VALUE) {
        throw new RuntimeException(
            "Ptg Size exceeds short when being specified for a goto ptg in an if");
      }

      goto1Ptg.setData((short) (ptgCount - 1));

    } else {

      retval = new FuncVarPtg(name, numArgs);
    }

    return retval;
  }
 public Object clone() {
   AttrPtg ptg = new AttrPtg();
   ptg.field_1_options = field_1_options;
   ptg.field_2_data = field_2_data;
   return ptg;
 }