/* Generated by JTB 1.5.1 */
package grammars.a.sgvis;

import grammars.a.Token;
import java.util.ArrayList;
import java.util.Iterator;

import grammars.a.sgtree.*;

/**
 * A skeleton output formatter for your language grammar.<br>
 * Using the add() method along with force(), indent(), and outdent(),<br>
 * you can easily specify how this visitor will format the given syntax tree.<br>
 * See the JTB documentation for more details.
 * <p>
 * Pass your syntax tree to this visitor, and then to the TreeDumper visitor<br>
 * in order to "pretty print" your tree.
 */
public class TreeFormatter extends DepthFirstVoidVisitor {

  /** The list of formatting commands */
  private final ArrayList<FormatCommand> cmdQueue = new ArrayList<>();
  /** True if line to be wrapped, false otherwise */
  private boolean lineWrap;
  /** The wrap width */
  private final int wrapWidth;
  /** The indentation amount */
  private final int indentAmt;
  /** The current line number */
  private int curLine = 1;
  /** The current column number */
  private int curColumn = 1;
  /** The current indentation */
  private int curIndent = 0;
  /** The default indentation */
  private static int INDENT_AMT = 2;

  /**
   * Constructor with a default indentation amount of {@link #INDENT_AMT} and no line-wrap.
   */
  public TreeFormatter() { this(INDENT_AMT, 0); }

  /**
   * Constructor using an indent amount and a line width used to wrap long lines.<br>
   * If a token's beginColumn value is greater than the specified wrapWidth,<br>
   * it will be moved to the next line andindented one extra level.<br>
   * To turn off line-wrapping, specify a wrapWidth of 0.
   *
   * @param aIndentAmt - Amount of spaces per indentation level
   * @param aWrapWidth - Wrap lines longer than wrapWidth. 0 for no wrap
   */
  public TreeFormatter(final int aIndentAmt, final int aWrapWidth) {
    this.indentAmt = aIndentAmt;
    this.wrapWidth = aWrapWidth;

    if (wrapWidth > 0)
       lineWrap = true;
    else
       lineWrap = false;
  }

  /**
   * Accepts a INodeList object.
   *
   * @param n - the node list to process
   */
  protected void processList(final INodeList n) {
    processList(n, null);
  }

  /**
   * Accepts a INodeList object and performs a format command (if non null)<br>
   * between each node in the list (but not after the last node).
   *
   * @param n - the node list to process
   * @param cmd - the format command
   */
  protected void processList(final INodeList n, final FormatCommand cmd) {
    for (final Iterator<INode> e = n.elements(); e.hasNext();) {
       e.next().accept(this);
       if (cmd != null && e.hasNext())
        cmdQueue.add(cmd);
    }
  }

  /**
   * Inserts one line break and indents the next line to the current indentation level.<br>
   * Use "add(force());".
   *
   * @return the corresponding FormatCommand
   */
  protected static FormatCommand force() { return force(1); }

  /**
   * Inserts a given number of line breaks and indents the next line to the current indentation level.<br>
   * Use "add(force(i));".
   *
   * @param i - the number of line breaks
   * @return the corresponding FormatCommand
   */
  protected static FormatCommand force(final int i) {
    return new FormatCommand(FormatCommand.FORCE, i);
  }

  /**
   * Increases the indentation level by one.<br>
   * Use "add(indent());".
   *
   * @return the corresponding FormatCommand
   */
  protected static FormatCommand indent() { return indent(1); }

  /**
   * Increases the indentation level by a given number.<br>
   * Use "add(indent(i));".
   *
   * @param i - the number of indentation levels to add
   * @return the corresponding FormatCommand
   */
  protected static FormatCommand indent(final int i) {
    return new FormatCommand(FormatCommand.INDENT, i);
  }

  /**
   * Reduces the indentation level by one.<br>
   * Use "add(outdent());".
   *
   * @return the corresponding FormatCommand
   */
  protected static FormatCommand outdent() { return outdent(1); }

  /**
   * Reduces the indentation level by a given number.<br>
   * Use "add(outdent(i));".
   *
   * @param i - the number of indentation levels to substract
   * @return the corresponding FormatCommand
   */
  protected static FormatCommand outdent(final int i) {
    return new FormatCommand(FormatCommand.OUTDENT, i);
  }

  /**
   * Adds one space between tokens.<br>
   * Use "add(space());".
   *
   * @return the corresponding FormatCommand
   */
  protected static FormatCommand space() { return space(1); }

  /**
   * Adds a given number of spaces between tokens.<br>
   * Use "add(space(i));".
   *
   * @param i - the number of spaces to add
   * @return the corresponding FormatCommand
   */
  protected static FormatCommand space(final int i) {
    return new FormatCommand(FormatCommand.SPACE, i);
  }

  /**
   * Use this method to add FormatCommands to the command queue to be executed<br>
   * when the next token in the tree is visited.
   *
   * @param cmd - the FormatCommand to be added
   */
  protected void add(final FormatCommand cmd) {
    cmdQueue.add(cmd);
  }

  /**
   * Executes the commands waiting in the command queue,<br>
   * then inserts the proper location information into the current Token.
   * <p>
   * If there are any special tokens preceding this token,<br>
   * they will be given the current location information.<br>
   * The token will follow on the next line, at the proper indentation level.<br>
   * If this is not the behavior you want from special tokens,<br>
   * feel free to modify this method.
   */
  @Override
  public void visit(final Token n) {
    for (FormatCommand cmd : cmdQueue) {
      switch (cmd.getCommand()) {
      case FormatCommand.FORCE :
        curLine += cmd.getNumCommands();
        curColumn = curIndent + 1;
        break;
      case FormatCommand.INDENT :
        curIndent += indentAmt * cmd.getNumCommands();
        break;
      case FormatCommand.OUTDENT :
        if (curIndent >= indentAmt)
        curIndent -= indentAmt * cmd.getNumCommands();
        break;
      case FormatCommand.SPACE :
        curColumn += cmd.getNumCommands();
        break;
      default :
        throw new TreeFormatterException("Invalid value in command queue.");
      }
    }

    cmdQueue.removeAll(cmdQueue);

    //
    // Handle all special tokens preceding this Token
    //
    if (n.numSpecials() > 0)
      for (Token e : n.specialTokens) {
       Token special = e;

       //
       // Place the token
       // Move cursor to next line after the special token
       // Don't update curColumn - want to keep current indent level
       //
       placeToken(special, curLine, curColumn);
       curLine = special.endLine + 1;
      }

    placeToken(n, curLine, curColumn);
    curLine = n.endLine;
    curColumn = n.endColumn;
  }

  /**
   * Inserts token location (beginLine, beginColumn, endLine, endColumn)<br>
   * information into the Token.<br>
   * Takes into account line-wrap. Does not update curLine and curColumn.
   *
   * @param n - the Token to insert
   * @param aLine - the insertion line number
   * @param aColumn - the insertion column number
   */
  private void placeToken(final Token n, final int aLine, final int aColumn) {
    final int length = n.image.length();
    int line = aLine;
    int column = aColumn;

    //
    // Find beginning of token.  Only line-wrap for single-line tokens
    //
    if (!lineWrap || n.image.indexOf('\n') != -1 ||
       column + length <= wrapWidth)
       n.beginColumn = column;
    else {
       ++line;
       column = curIndent + indentAmt + 1;
       n.beginColumn = column;
    }

    n.beginLine = line;

    //
    // Find end of token; don't count '\n' if it's the last character
    //
    for (int i = 0; i < length; ++i) {
       if (n.image.charAt(i) == '\n' && i < length - 1) {
        ++line;
        column = 1;
       }
       else
        ++column;
    }

    n.endLine = line;
    n.endColumn = column;
  }

  //
  // User-generated visitor methods below
  //

  /**
   * token -> "class"<br>
   * className -> className()<br>
   * token1 -> "{"<br>
   * nodeListOptional -> ( %0 method()<br>
   * ................ .. | %1 instruction() )*<br>
   * token2 -> "}"<br>
   * token3 -> "."<br>
   * jc_0 -> jc_0()<br>
   * token4 -> < EOF ><br>
   * s: -999135908<br>
   */
  @Override
  public void visit(final ASTclassDeclaration123 n) {
    n.token.accept(this);
    n.className.accept(this);
    n.token1.accept(this);
    if (n.nodeListOptional.present()) {
      processList(n.nodeListOptional);
    }
    n.token2.accept(this);
    n.token3.accept(this);
    n.jc_0.accept(this);
    n.token4.accept(this);
  }

  /**
   * token -> < ID ><br>
   * s: 562244549<br>
   */
  @Override
  public void visit(final ASTclassName123 n) {
    n.token.accept(this);
  }

  /**
   * methodName -> methodName()<br>
   * token -> "("<br>
   * nodeList -> ( instruction() )+<br>
   * token1 -> ")"<br>
   * s: 207950942<br>
   */
  @Override
  public void visit(final ASTmethod123 n) {
    n.methodName.accept(this);
    n.token.accept(this);
    processList(n.nodeList);
    n.token1.accept(this);
  }

  /**
   * token -> < ID ><br>
   * s: 562244549<br>
   */
  @Override
  public void visit(final ASTmethodName123 n) {
    n.token.accept(this);
  }

  /**
   * nodeChoice -> . %0 #0 < ID > #1 ";"<br>
   * .......... .. | %1 ","<br>
   * s: -427851232<br>
   */
  @Override
  public void visit(final ASTinstruction123 n) {
    n.nodeChoice.accept(this);
  }

  /**
   * token -> < ID ><br>
   * s: 562244549<br>
   */
  @Override
  public void visit(final ASTbp_jual123 n) {
    n.token.accept(this);
  }

  /**
   * token -> < ID ><br>
   * s: 562244549<br>
   */
  @Override
  public void visit(final ASTbp_hm123 n) {
    n.token.accept(this);
  }

  /**
   * s: 0<br>
   */
  @SuppressWarnings("unused")
  @Override
  public void visit(final ASTjc_0123 n) {
    // empty node, nothing to generate
  }

}

/**
 * Stores a format command.
 */
class FormatCommand {

  /** Line break format code */
  public static final int FORCE = 0;
  /** Indentation format code */
  public static final int INDENT = 1;
  /** Unindentation format code */
  public static final int OUTDENT = 2;
  /** Spacing format code */
  public static final int SPACE = 3;

  /** The format command code */
  private int command;
  /** The format command repetition number */
  private int numCommands;

  /**
   * Constructor with class members.
   *
   * @param aCmd - the command code
   * @param aNumCmd - the command repetition number
   */
  FormatCommand(final int aCmd, final int aNumCmd) {
    this.command = aCmd;
    this.numCommands = aNumCmd;
  }

  /**
   * @return the command code
   */
  public int getCommand()  { return command; }

  /**
   * @return the command repetition number
   */
  public int getNumCommands()  { return numCommands; }

  /**
   * Sets the command code.
   *
   * @param i - the command code
   */
  public void setCommand(final int i)  { command = i; }

  /**
   * Sets the command repetition number.
   *
   * @param i - the command repetition number
   */
  public void setNumCommands(final int i)  { numCommands = i; }

}

/**
 * The TreeFormatter exception class.
 */
class TreeFormatterException extends RuntimeException {

  /** The serial version UID */
  private static final long serialVersionUID = 1L;

  /**
   * Constructor with no message.
   */
  TreeFormatterException()  { super(); }

  /**
   * Constructor with a given message.
   *
   * @param s - the exception message
   */
  TreeFormatterException(final String s)  { super(s); }

}
