package extra;

import java.io.PrintStream;
import java.io.InputStream;
import java.io.FileInputStream;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.EOFException;

/**
 * This is a simple utility to print the contents of a heap dump
 * generated by Avian's heapdump.cpp in a human-readable format.
 */
public class PrintDump {
  private static final int Root = 0;
  private static final int Size = 1;
  private static final int ClassName = 2;
  private static final int Push = 3;
  private static final int Pop = 4;

  private static void indent(PrintStream out, int level) {
    for (; level > 0; --level) out.print("  ");
  }

  private static int readInt(InputStream in) throws IOException {
    int b1 = in.read();
    int b2 = in.read();
    int b3 = in.read();
    int b4 = in.read();
    if (b4 == -1) throw new EOFException();
    return (int) ((b1 << 24) | (b2 << 16) | (b3 << 8) | (b4));    
  }

  private static String readString(InputStream in) throws IOException {
    int count = readInt(in);
    byte[] b = new byte[count];
    int offset = 0;
    int c;
    while ((c = in.read(b, offset, b.length - offset)) != -1
           && offset < b.length)
    {
      offset += c;
    }
    if (offset != b.length) throw new EOFException();
    return new String(b);
  }

  private static void pipe(InputStream in, PrintStream out)
    throws IOException
  {
    boolean done = false;
    boolean popped = false;
    int level = 0;
    while (! done) {
      int flag = in.read();
      switch (flag) {
      case Root: {
        out.print("\nroot " + readInt(in));
        popped = false;
      } break;

      case ClassName: {
        out.print(" class " + readString(in));
      } break;

      case Push: {
        ++ level;
        out.println();
        indent(out, level);
        if (! popped) {
          out.print("first ");
        }
        out.print("child " + readInt(in));
        popped = false;
      } break;

      case Pop: {
        -- level;
        popped = true;
      } break;

      case Size: {
        out.print(" size " + readInt(in));
      } break;

      case -1:
        out.println();
        out.flush();
        done = true;
        break;

      default:
        throw new RuntimeException("bad flag: " + flag);
      }
    }
  }
  
  public static void main(String[] args) throws Exception {
    pipe(new BufferedInputStream(new FileInputStream(args[0])), System.out);
  }
}