
package AST;

public class ASTNode<T extends ASTNode> implements Cloneable, Iterable<T> {

  public void traverse() {
    for(int i = 0; i < getNumChild(); i++) {
      child(i).traverse();
    }
  }

  public ASTNode child(int i) {
    if (getChild(i).mayHaveRewrite()) {
      return rewriteChild(i);
    }
    return getChild(i);
  }

  protected java.util.Map rewriteChild_int_visited;
  protected java.util.Map rewriteChild_int_values;
  protected List rewriteChild_int_list;
  protected java.util.Map rewriteChild_int_initialized;
  protected java.util.Map rewriteChild_int_computed;

  public ASTNode rewriteChild(int i) {
    // Cache check
    Object _parameters = Integer.valueOf(i);
    if(rewriteChild_int_visited == null) {
      rewriteChild_int_visited = new java.util.HashMap(4);
      rewriteChild_int_visited.put(_parameters, -1);
    }
    if(rewriteChild_int_values == null) rewriteChild_int_values = new java.util.HashMap(4);
    if(rewriteChild_int_computed == null) rewriteChild_int_computed = new java.util.HashMap(4);
    if(rewriteChild_int_initialized == null) rewriteChild_int_initialized = new java.util.HashMap(4);
    if(rewriteChild_int_computed.containsKey(_parameters)) {
      return (ASTNode)rewriteChild_int_values.get(_parameters); 
    }
    // Initialize
    ASTNode$State state = state();
    if (!rewriteChild_int_initialized.containsKey(_parameters)) {
      rewriteChild_int_initialized.put(_parameters, true);
      ASTNode new_rewriteChild_value = getChild(i).fullCopy();
      if (new_rewriteChild_value != null) {
        if(rewriteChild_int_list == null) {
          rewriteChild_int_list = new List();
          rewriteChild_int_list.setParent(this);
        }
        new_rewriteChild_value.setParent(rewriteChild_int_list);
        new_rewriteChild_value.childIndex = i;
      }
      rewriteChild_int_values.put(_parameters, new_rewriteChild_value);
    }
    if (!state.IN_CIRCLE) {
      Tracer.traceEnterCircularNTACase1(this, "ASTNode.rewriteChild", "" + i, 
          nodeToString((ASTNode)rewriteChild_int_values.get(_parameters)));
      state.IN_CIRCLE = true;
      do {
        rewriteChild_int_visited.put(_parameters, state.CIRCLE_INDEX);
        state.CHANGE = false;
        state.push(ASTNode$State.REWRITE_CHANGE);
        ASTNode new_rewriteChild_value = rewriteChild_compute(i);
        if (state.pop() == ASTNode$State.REWRITE_CHANGE) {
          Tracer.traceCircularNTACase1Change(this, "ASTNode.rewriteChild", "" + i, 
              nodeToString((ASTNode)rewriteChild_int_values.get(_parameters)) + "->" + 
              nodeToString(new_rewriteChild_value));
          state.CHANGE = true;
          if (new_rewriteChild_value != null) {
            if(rewriteChild_int_list == null) {
              rewriteChild_int_list = new List();
              rewriteChild_int_list.setParent(this);
            }
            new_rewriteChild_value.setParent(rewriteChild_int_list);
            new_rewriteChild_value.childIndex = i;
          }
          rewriteChild_int_values.put(_parameters, new_rewriteChild_value);
        }
        state.CIRCLE_INDEX++;
      } while (state.CHANGE);
      Tracer.traceCached(this, "ASTNode.rewriteChild", "" + i, 
          nodeToString((ASTNode)rewriteChild_int_values.get(_parameters)));
      rewriteChild_int_computed.put(_parameters, true);
      state.LAST_CYCLE = true;
      state.push(ASTNode$State.REWRITE_CHANGE); // need to have something to pop in ASTNode.rewriteTo
      rewriteChild_compute(i);
      state.LAST_CYCLE = false;
      state.IN_CIRCLE = false;
      Tracer.traceExitCircularNTACase1(this, "ASTNode.rewriteChild", "" + i, 
          nodeToString((ASTNode)rewriteChild_int_values.get(_parameters)));
      return (ASTNode)rewriteChild_int_values.get(_parameters);
    }
    if(rewriteChild_int_visited.get(_parameters) == null || 
        (Integer)rewriteChild_int_visited.get(_parameters) != state.CIRCLE_INDEX) {
      Tracer.traceEnterCircularNTACase2(this, "ASTNode.rewriteChild", "" + i, 
          nodeToString((ASTNode)rewriteChild_int_values.get(_parameters)));
      rewriteChild_int_visited.put(_parameters, state.CIRCLE_INDEX);
      if (state.LAST_CYCLE) {
        Tracer.traceCached(this, "ASTNode.rewriteChild", "" + i, 
            nodeToString((ASTNode)rewriteChild_int_values.get(_parameters)));
        rewriteChild_int_computed.put(_parameters, true);
        state.push(ASTNode$State.REWRITE_CHANGE); // need to have something to pop in ASTNode.rewriteTo
        rewriteChild_compute(i);
        Tracer.traceExitCircularNTACase2(this, "ASTNode.rewriteChild", "" + i, 
            nodeToString((ASTNode)rewriteChild_int_values.get(_parameters)));
        return (ASTNode)rewriteChild_int_values.get(_parameters);
      }
      state.push(ASTNode$State.REWRITE_CHANGE);
      ASTNode new_rewriteChild_value = rewriteChild_compute(i);
      if (state.pop() == ASTNode$State.REWRITE_CHANGE) {
        Tracer.traceCircularNTACase2Change(this, "ASTNode.rewriteChild", "" + i,
            nodeToString((ASTNode)rewriteChild_int_values.get(_parameters)) + "->" + 
            nodeToString(new_rewriteChild_value));
        state.CHANGE = true;
        if (new_rewriteChild_value != null) {
          if(rewriteChild_int_list == null) {
            rewriteChild_int_list = new List();
            rewriteChild_int_list.setParent(this);
          }
          new_rewriteChild_value.setParent(rewriteChild_int_list);
          new_rewriteChild_value.childIndex = i;
        }
        rewriteChild_int_values.put(_parameters, new_rewriteChild_value);
      }
      Tracer.traceExitCircularNTACase2(this, "ASTNode.rewriteChild", "" + i, 
          nodeToString((ASTNode)rewriteChild_int_values.get(_parameters)));
      return (ASTNode)rewriteChild_int_values.get(_parameters);
    }
    Tracer.traceExitCircularNTACase3(this, "ASTNode.rewriteChild", "" + i, 
        nodeToString((ASTNode)rewriteChild_int_values.get(_parameters)));
    return (ASTNode)rewriteChild_int_values.get(_parameters);
  }

  private ASTNode rewriteChild_compute(int i) {
    return rewriteChild(i).rewriteTo();
  }

  public static String treeToString(ASTNode node) {
    StringBuffer buf = new StringBuffer();
    buf.append("(" + nodeToString(node));
    buf.append("," + nodeToString(node.parent));
    buf.append(",(");
    for (int i = 0; i < node.numChildren; i++) {
      buf.append(treeToString(node.children[i]));
      if (i < node.numChildren-1) {
        buf.append(",");
      }
    } 
    buf.append("))");
    return buf.toString();
  }

  public static String nodeToString(ASTNode node) {
    return (node != null ? node.getClass().getSimpleName() : "null");
  }

  @SuppressWarnings({"unchecked", "cast"})
  public ASTNode<T> clone() throws CloneNotSupportedException {
    ASTNode node = (ASTNode) super.clone();
    node.extra_visited = -1;
    return node;
  }

  @SuppressWarnings({"unchecked", "cast"})
  public ASTNode<T> copy() {
    try {
      Tracer.traceCopy(this, nodeToString(this));
      ASTNode node = (ASTNode) clone();
      node.parent = null;
      if(children != null) {
        node.children = (ASTNode[]) children.clone();
      }
      return node;
    } catch (CloneNotSupportedException e) {
      throw new Error("Error: clone not supported for " + getClass().getName());
    }
  }

  @SuppressWarnings({"unchecked", "cast"})
  public ASTNode<T> fullCopy() {
    ASTNode tree = (ASTNode) copy();
    if (children != null) {
      for (int i = 0; i < children.length; ++i) {
        ASTNode child = (ASTNode) children[i];
        if(child != null) {
          child = child.fullCopy();
          tree.setChild(child, i);
        }
      }
    }
    return tree;
  }

  public String dumpTree(int j) {
    StringBuffer s = new StringBuffer();
    for(int i = 0; i < j; i++) {
      s.append("  ");
    }
    s.append(getClass().getName() + extra() + "\n");
    for(int i = 0; i < getNumChild(); i++) {
      s.append(child(i).dumpTree(j + 1));
      //s.append(getChild(i).dumpTree(j + 1));
    }
    return s.toString();
  }

  public String dumpInitTree(int j) {
    StringBuffer s = new StringBuffer();
    for(int i = 0; i < j; i++) {
      s.append("  ");
    }
    s.append(getClass().getName() + extra() + "\n");
    for(int i = 0; i < getNumChildNoTransform(); i++) {
      s.append(getChildNoTransform(i).dumpInitTree(j + 1));
    }
    return s.toString();
  }

  public ASTNode() {
    super();
    init$Children();
  }

  public void init$Children() {
  }

  private int childIndex;

  public int getIndexOfChild(ASTNode node) {
    if (node != null && node.childIndex < numChildren
        && rewriteChild_int_values != null && node == rewriteChild_int_values.get(Integer.valueOf(node.childIndex)))
      return node.childIndex;
    if (node != null && node.childIndex < numChildren
        && node == children[node.childIndex])
      return node.childIndex;
    for(int i = 0; children != null && i < children.length; i++) {
      if(children[i] == node) {
        node.childIndex = i;
        return i;
      }
    }
    return -1;
  }

  public static final boolean generatedWithCircularEnabled = true;
  public static final boolean generatedWithCacheCycle = true;
  public static final boolean generatedWithComponentCheck = false;

  protected ASTNode parent;
  protected ASTNode[] children;

  protected static ASTNode$State state = new ASTNode$State();
  public final ASTNode$State state() {
    return state;
  }

  @SuppressWarnings("cast") 
  public T getChild(int i) {
    return this.getChildNoTransform(i);
  }

  public void addChild(T node) {
    setChild(node, getNumChildNoTransform());
  }

  @SuppressWarnings("cast")
  public final T getChildNoTransform(int i) {
      if (children == null) {
      return null;
    }
    T child = (T)children[i];
    return child;        
  }

  protected int numChildren;
  protected int numChildren() {
    return numChildren;
  }
  public int getNumChild() {
    return numChildren();
  }
  public final int getNumChildNoTransform() {
    return numChildren();
  }

  public void setChild(ASTNode node, int i) {
    if(children == null) {
      children = new ASTNode[(i+1>4 || !(this instanceof List))?i+1:4];
    } else if (i >= children.length) {
      ASTNode c[] = new ASTNode[i << 1];
      System.arraycopy(children, 0, c, 0, children.length);
      children = c;
    }
    children[i] = node;
    if(i >= numChildren) {
      numChildren = i+1;
    }
    if(node != null) { 
      node.setParent(this); 
      node.childIndex = i; 
    }
  }

  public void insertChild(ASTNode node, int i) {
    if(children == null) {
      children = new ASTNode[(i+1>4 || !(this instanceof List))?i+1:4];
      children[i] = node;
    } else {
      ASTNode c[] = new ASTNode[children.length + 1];
      System.arraycopy(children, 0, c, 0, i);
      c[i] = node;
      if(i < children.length) {
        System.arraycopy(children, i, c, i+1, children.length-i);
        for(int j = i+1; j < c.length; ++j) {
          if(c[j] != null) {
            c[j].childIndex = j;
          }
        }
      }
      children = c;
    }
    numChildren++;
    if(node != null) { 
      node.setParent(this); 
      node.childIndex = i; 
    }
  }

  public void removeChild(int i) {
    if(children != null) {
      ASTNode child = (ASTNode)children[i];
      if(child != null) {
        child.parent = null;
        child.childIndex = -1;
      }
      if (this instanceof List || this instanceof Opt) {
        System.arraycopy(children, i+1, children, i, children.length-i-1);
        children[children.length-1] = null;
        numChildren--;
        for(int j = i; j < numChildren; ++j) {
          if(children[j] != null) {
            child = (ASTNode) children[j];
            child.childIndex = j;
          }
        }
      } else {
        children[i] = null;
      }
    }
  }

  public ASTNode getParent() {
    return (ASTNode)parent;
  }

  public void setParent(ASTNode node) {
    parent = node;
  }
  
  public java.util.Iterator<T> iterator() {
    return new java.util.Iterator<T>() {
      private int counter = 0;
      public boolean hasNext() {
        return counter < getNumChild();
      }
      @SuppressWarnings("unchecked") public T next() {
        if(hasNext()) {
          return (T)child(counter++);
        } else
          return null;
      }
      public void remove() {
        throw new UnsupportedOperationException();
      }
    };
  }

  public boolean mayHaveRewrite() {
    return false;
  }

  public void flushCache() {
    extra_visited = -1;
  }

  public void flushCollectionCache() {
  }

  protected int extra_visited = -1;

  public String extra() {
    ASTNode$State state = state();
    if (extra_visited == state.CIRCLE_INDEX) {
      throw new RuntimeException("Circular definition of attr: extra in class: ast.AST.SynDecl");
    }
    extra_visited = state.CIRCLE_INDEX;
    try {  return "";  }
    finally {
      extra_visited = -1;
    }
  }

  public ASTNode rewriteTo() {    
    if(state().peek() == ASTNode$State.REWRITE_CHANGE) {
      state().pop();
      state().push(ASTNode$State.REWRITE_NOCHANGE);
    }
    return this;
  }

  public ClassDecl Define_ClassDecl_unknownType(ASTNode caller, ASTNode child) {
    Tracer.traceInhAttrEval(this, "ASTNode.child.unknownType", "", nodeToString(caller));
    return getParent().Define_ClassDecl_unknownType(this, caller);
  }

  public Variable Define_Variable_lookupVariable(ASTNode caller, ASTNode child, String name) {
    Tracer.traceInhAttrEval(this, "ASTNode.child.lookupVariable", name, nodeToString(caller));
    return getParent().Define_Variable_lookupVariable(this, caller, name);
  }

  public ClassDecl Define_ClassDecl_lookupCanonical(ASTNode caller, ASTNode child, String pack, String type) {
    Tracer.traceInhAttrEval(this, "ASTNode.child.lookupCanonical", "[" + pack + "," + type + "]", nodeToString(caller));
    return getParent().Define_ClassDecl_lookupCanonical(this, caller, pack, type);
  }

  public ClassDecl Define_ClassDecl_lookupType(ASTNode caller, ASTNode child, String name) {
    Tracer.traceInhAttrEval(this, "ASTNode.child.lookupType", name, nodeToString(caller));
    return getParent().Define_ClassDecl_lookupType(this, caller, name);
  }

  public Kind Define_Kind_kind(ASTNode caller, ASTNode child) {
    Tracer.traceInhAttrEval(this, "ASTNode.child.kind", "", nodeToString(caller));
    return getParent().Define_Kind_kind(this, caller);
  }

  public boolean Define_boolean_hasPackage(ASTNode caller, ASTNode child, String name) {
    Tracer.traceInhAttrEval(this, "ASTNode.child.hasPackage", name, nodeToString(caller));
    return getParent().Define_boolean_hasPackage(this, caller, name);
  }
}
