package annotations.io.classfile;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Handle;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodAdapter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

class MethodCodeOffsetAdapter extends MethodAdapter {
  private final ClassReader cr;
  private final int methodStart;
  private int offset = 0;
  private int codeStart = 0;
  private int attrCount = 0;

  public MethodCodeOffsetAdapter(ClassReader classReader,
      MethodVisitor methodVisitor, int start) {
    super(methodVisitor);
    char[] buf = new char[classReader.header];
    this.methodStart = start;
    cr = classReader;
    // const pool size is (not lowest) upper bound of string length
    codeStart = start;
    attrCount = classReader.readUnsignedShort(codeStart + 6);

    // find code attribute
    codeStart += 8;
    while (attrCount > 0) {
      String attrName = classReader.readUTF8(codeStart, buf);
      if ("Code".equals(attrName)) { break; }
      codeStart += 6 + classReader.readInt(codeStart + 2);
      --attrCount;
    }
  }

  private int readInt(int i) {
    return cr.readInt(codeStart + i);
  }

  public int getMethodCodeStart() { return methodStart; }

  public int getMethodCodeOffset() { return offset; }

  public int getClassCodeOffset() { return codeStart + offset; }

  @Override
  public void visitFieldInsn(int opcode,
      String owner, String name, String desc) {
    super.visitFieldInsn(opcode, owner, name, desc);
    offset += 3;
  }

  @Override
  public void visitIincInsn(int var, int increment) {
    super.visitIincInsn(var, increment);
    offset += 3;
  }

  @Override
  public void visitInsn(int opcode) {
    super.visitInsn(opcode);
    ++offset;
  }

  @Override
  public void visitIntInsn(int opcode, int operand) {
    super.visitIntInsn(opcode, operand);
    offset += opcode == Opcodes.SIPUSH ? 3 : 2;
  }

  @Override
  public void visitInvokeDynamicInsn(String name, String desc,
      Handle bsm, Object... bsmArgs) {
    super.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs);
    offset += 5;
  }

  @Override
  public void visitJumpInsn(int opcode, Label label) {
    super.visitJumpInsn(opcode, label);
    offset += 3;
  }

  @Override
  public void visitLdcInsn(Object cst) {
    super.visitLdcInsn(cst);
    offset += 2;
  }

  @Override
  public void visitLookupSwitchInsn(Label dflt, int[] keys,
      Label[] labels) {
    super.visitLookupSwitchInsn(dflt, keys, labels);
    offset += 8 - ((offset - codeStart) & 3);
    offset += 4 + 8 * readInt(offset);
  }

  @Override
  public void visitMethodInsn(int opcode,
      String owner, String name, String desc) {
    super.visitMethodInsn(opcode, owner, name, desc);
    offset += opcode == Opcodes.INVOKEINTERFACE ? 5 : 3;
  }

  @Override
  public void visitMultiANewArrayInsn(String desc, int dims) {
    super.visitMultiANewArrayInsn(desc, dims);
    offset += 4;
  }

  @Override
  public void visitTableSwitchInsn(int min, int max,
      Label dflt, Label[] labels) {
    super.visitTableSwitchInsn(min, max, dflt, labels);
    offset += 8 - ((offset - codeStart) & 3);
    offset += 4 * (readInt(offset + 4) - readInt(offset) + 3);
  }

  @Override
  public void visitTypeInsn(int opcode, String desc) {
    super.visitTypeInsn(opcode, desc);
    offset += 3;
  }

  @Override
  public void visitVarInsn(int opcode, int var) {
    super.visitVarInsn(opcode, var);
    offset += var < 4 ? 1 : 2;
  }

  @Override
  public void visitEnd() {
    offset = -1;  // invalidated
  }
}