/*
 * Copyright (c) 2002 - 2006 IBM Corporation.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 */

package com.ibm.wala.util.bytecode;

import com.ibm.wala.classLoader.IClass;
import com.ibm.wala.classLoader.IMethod;
import com.ibm.wala.shrikeBT.BytecodeConstants;

/**
 * Provides minimal abstraction layer to a stream of bytecodes from the code attribute of a method.
 */
public class BytecodeStream implements BytecodeConstants {
  private final IMethod method;

  private final IClass declaringClass;

  private final int bcLength;

  private final byte[] bcodes;

  private int bcIndex;

  private int opcode;

  private boolean wide;

  /**
   * @param m the method containing the bytecodes
   * @param bc the array of bytecodes
   * @throws IllegalArgumentException if bc is null
   * @throws IllegalArgumentException if m is null
   */
  public BytecodeStream(IMethod m, byte[] bc) {
    if (m == null) {
      throw new IllegalArgumentException("m is null");
    }
    if (bc == null) {
      throw new IllegalArgumentException("bc is null");
    }
    method = m;
    declaringClass = m.getDeclaringClass();
    bcodes = bc;
    bcLength = bc.length;
    bcIndex = 0;
  }

  /**
   * Returns the method that this bytecode stream is from
   */
  public final IMethod method() {
    return method;
  }

  /**
   * Returns the declaring class that this bytecode stream is from
   */
  public final IClass declaringClass() {
    return declaringClass;
  }

  /**
   * Returns the length of the bytecode stream Returns 0 if the method doesn't have any bytecodes (i.e. is abstract or native)
   */
  public final int length() {
    return bcLength;
  }

  /**
   * Returns the current bytecode index
   */
  public final int index() {
    return bcIndex;
  }

  /**
   * Resets the stream to the beginning
   */
  public final void reset() {
    reset(0);
  }

  /**
   * Resets the stream to a given position Use with caution
   * 
   * @param index the position to reset the stream to
   */
  public final void reset(int index) {
    bcIndex = index;
  }

  /**
   * Does the stream have more bytecodes in it?
   */
  public final boolean hasMoreBytecodes() {
    return bcIndex < bcLength;
  }

  /**
   * Returns the opcode of the next instruction in the sequence without advancing to it
   * 
   * @return the opcode of the next instruction
   * @see #nextInstruction()
   */
  public final int peekNextOpcode() {
    return getUnsignedByte(bcIndex);
  }

  /**
   * Sets up the next instruction in the sequence
   * 
   * @return the opcode of the next instruction
   * @see #peekNextOpcode()
   */
  public final int nextInstruction() {
    opcode = readUnsignedByte();
    wide = (opcode == JBC_wide);
    return opcode;
  }

  /**
   * Returns the opcode of the current instruction in the sequence Note: if skipInstruction has been called, but nextInstruction has
   * not, this method will return the opcode of the skipped instruction!
   * 
   * @return the opcode of the current instruction
   * @see #nextInstruction()
   * @see #isWide()
   */
  public final int getOpcode() {
    return opcode;
  }

  /**
   * Are we currently processing a wide instruction?
   * 
   * @return true if current instruction is wide
   * @see #nextInstruction()
   * @see #getOpcode()
   */
  public final boolean isWide() {
    return wide;
  }

  /**
   * Skips the current instruction
   * 
   * @see #skipInstruction(int,boolean)
   */
  public final void skipInstruction() {
    int len = JBC_length[opcode] - 1;
    if (wide)
      len += len;
    if (len >= 0) {
      bcIndex += len;
    } else {
      skipSpecialInstruction(opcode);
    }
  }

  /**
   * Skips the current instruction (without using the opcode field) A slightly optimized version of skipInstruction()
   * 
   * @param opc current opcode
   * @param w whether current instruction follows wide
   * @see #skipInstruction()
   */
  public final void skipInstruction(int opc, boolean w) {
    int len = JBC_length[opc] - 1;
    if (w)
      len += len;
    if (len >= 0)
      bcIndex += len;
    else
      skipSpecialInstruction(opc);
  }

  /**
   * Returns a signed byte value Used for bipush
   * 
   * @return signed byte value
   */
  public final int getByteValue() {
    return readSignedByte();
  }

  /**
   * Returns a signed short value Used for sipush
   * 
   * @return signed short value
   */
  public final int getShortValue() {
    return readSignedShort();
  }

  /**
   * Returns the number of the local (as an unsigned byte) Used for iload, lload, fload, dload, aload, istore, lstore, fstore,
   * dstore, astore, iinc, ret
   * 
   * @return local number
   * @see #getWideLocalNumber()
   */
  public final int getLocalNumber() {
    return readUnsignedByte();
  }

  /**
   * Returns the wide number of the local (as an unsigned short) Used for iload, lload, fload, dload, aload, istore, lstore, fstore,
   * dstore, astore, iinc prefixed by wide
   * 
   * @return wide local number
   * @see #getLocalNumber()
   */
  public final int getWideLocalNumber() {
    return readUnsignedShort();
  }

  /**
   * Returns an increment value (as a signed byte) Used for iinc
   * 
   * @return increment
   * @see #getWideIncrement()
   */
  public final int getIncrement() {
    return readSignedByte();
  }

  /**
   * Returns an increment value (as a signed short) Used for iinc prefixed by wide
   * 
   * @return wide increment
   * @see #getIncrement()
   */
  public final int getWideIncrement() {
    return readSignedShort();
  }

  /**
   * Returns the offset of the branch (as a signed short) Used for if&lt;cond&gt;, ificmp&lt;cond&gt;, ifacmp&lt;cond&gt;, goto, jsr
   * 
   * @return branch offset
   * @see #getWideBranchOffset()
   */
  public final int getBranchOffset() {
    return readSignedShort();
  }

  /**
   * Returns the wide offset of the branch (as a signed int) Used for goto_w, jsr_w
   * 
   * @return wide branch offset
   * @see #getBranchOffset()
   */
  public final int getWideBranchOffset() {
    return readSignedInt();
  }

  /**
   * Skips the padding of a switch instruction Used for tableswitch, lookupswitch
   */
  public final void alignSwitch() {
    int align = bcIndex & 3;
    if (align != 0)
      bcIndex += 4 - align; // eat padding
  }

  /**
   * Returns the default offset of the switch (as a signed int) Used for tableswitch, lookupswitch
   * 
   * @return default switch offset
   */
  public final int getDefaultSwitchOffset() {
    return readSignedInt();
  }

  /**
   * Returns the lowest value of the tableswitch (as a signed int) Used for tableswitch
   * 
   * @return lowest switch value
   * @see #getHighSwitchValue()
   */
  public final int getLowSwitchValue() {
    return readSignedInt();
  }

  /**
   * Returns the highest value of the tableswitch (as a signed int) Used for tableswitch
   * 
   * @return highest switch value
   * @see #getLowSwitchValue()
   */
  public final int getHighSwitchValue() {
    return readSignedInt();
  }

  /**
   * Skips the offsets of a tableswitch instruction Used for tableswitch
   * 
   * @param num the number of offsets to skip
   * @see #getTableSwitchOffset(int)
   */
  public final void skipTableSwitchOffsets(int num) {
    bcIndex += (num << 2);
  }

  /**
   * Returns the numbered offset of the tableswitch (as a signed int) Used for tableswitch The "cursor" has to be positioned at the
   * start of the offset table NOTE: Will NOT advance cursor
   * 
   * @param num the number of the offset to retrieve
   * @return switch offset
   */
  public final int getTableSwitchOffset(int num) {
    return getSignedInt(bcIndex + (num << 2));
  }

  /**
   * Returns the offset for a given value of the tableswitch (as a signed int) or 0 if the value is out of range. Used for
   * tableswitch The "cursor" has to be positioned at the start of the offset table NOTE: Will NOT advance cursor
   * 
   * @param value the value to retrieve offset for
   * @param low the lowest value of the tableswitch
   * @param high the highest value of the tableswitch
   * @return switch offset
   */
  public final int computeTableSwitchOffset(int value, int low, int high) {
    if (value < low || value > high)
      return 0;
    return getSignedInt(bcIndex + ((value - low) << 2));
  }

  /**
   * Returns the number of match-offset pairs in the lookupswitch (as a signed int) Used for lookupswitch
   * 
   * @return number of switch pairs
   */
  public final int getSwitchLength() {
    return readSignedInt();
  }

  /**
   * Skips the match-offset pairs of a lookupswitch instruction Used for lookupswitch
   * 
   * @param num the number of match-offset pairs to skip
   * @see #getLookupSwitchValue(int)
   * @see #getLookupSwitchOffset(int)
   */
  public final void skipLookupSwitchPairs(int num) {
    bcIndex += (num << 3);
  }

  /**
   * Returns the numbered offset of the lookupswitch (as a signed int) Used for lookupswitch The "cursor" has to be positioned at
   * the start of the pair table NOTE: Will NOT advance cursor
   * 
   * @param num the number of the offset to retrieve
   * @return switch offset
   * @see #getLookupSwitchValue(int)
   */
  public final int getLookupSwitchOffset(int num) {
    return getSignedInt(bcIndex + (num << 3) + 4);
  }

  /**
   * Returns the numbered value of the lookupswitch (as a signed int) Used for lookupswitch The "cursor" has to be positioned at the
   * start of the pair table NOTE: Will NOT advance cursor
   * 
   * @param num the number of the value to retrieve
   * @return switch value
   * @see #getLookupSwitchOffset(int)
   */
  public final int getLookupSwitchValue(int num) {
    return getSignedInt(bcIndex + (num << 3));
  }

  /**
   * Returns the offset for a given value of the lookupswitch (as a signed int) or 0 if the value is not in the table. Used for
   * lookupswitch The "cursor" has to be positioned at the start of the offset table NOTE: Will NOT advance cursor WARNING: Uses
   * LINEAR search. Whoever has time on their hands can re-implement this as a binary search.
   * 
   * @param value the value to retrieve offset for
   * @param num the number of match-offset pairs in the lookupswitch
   * @return switch offset
   */
  public final int computeLookupSwitchOffset(int value, int num) {
    for (int i = 0; i < num; i++)
      if (getSignedInt(bcIndex + (i << 3)) == value)
        return getSignedInt(bcIndex + (i << 3) + 4);
    return 0;
  }

  /**
   * Skips the extra stuff after an invokeinterface instruction Used for invokeinterface
   */
  public final void alignInvokeInterface() {
    bcIndex += 2; // eat superfluous stuff
  }

  /**
   * Returns the element type (primitive) of the array (as an unsigned byte) Used for newarray
   * 
   * @return array element type
   */
  public final int getArrayElementType() {
    return readUnsignedByte();
  }

  /**
   * Returns the dimension of the array (as an unsigned byte) Used for multianewarray
   * 
   * @return array dimension
   */
  public final int getArrayDimension() {
    return readUnsignedByte();
  }

  /**
   * Returns the opcode of the wide instruction Used for wide Can be one of iload, lload, fload, dload, aload, istore, lstore,
   * fstore, dstore, astore, iinc
   * 
   * @return the opcode of the wide instruction
   */
  public final int getWideOpcode() {
    opcode = readUnsignedByte();
    return opcode;
  }

  /**
   * Returns the constant pool index of a constant (as an unsigned byte) Used for ldc
   * 
   * @return constant index
   * @see #getWideConstantIndex()
   */
  public final int getConstantIndex() {
    return readUnsignedByte();
  }

  /**
   * Returns the wide constant pool index of a constant (as an unsigned short) Used for ldc_w, ldc2_w
   * 
   * @return wide constant index
   * @see #getConstantIndex()
   */
  public final int getWideConstantIndex() {
    return readUnsignedShort();
  }

  // // HELPER FUNCTIONS

  // Skip a tableswitch or a lookupswitch instruction
  private void skipSpecialInstruction(int opcode) {
    switch (opcode) {
    case JBC_tableswitch: {
      alignSwitch();
      getDefaultSwitchOffset();
      int l = getLowSwitchValue();
      int h = getHighSwitchValue();
      skipTableSwitchOffsets(h - l + 1); // jump offsets
    }
      break;
    case JBC_lookupswitch: {
      alignSwitch();
      getDefaultSwitchOffset();
      int n = getSwitchLength();
      skipLookupSwitchPairs(n); // match-offset pairs
    }
      break;
    case JBC_wide: {
      int oc = getWideOpcode();
      int len = JBC_length[oc] - 1;
      bcIndex += len + len;
    }
      break;
    default:
      throw new NullPointerException();
    }
  }

  // // READ BYTECODES
  private final byte readSignedByte() {
    return bcodes[bcIndex++];
  }

  private final int readUnsignedByte() {
    return (bcodes[bcIndex++] & 0xFF);
  }

  private final int getUnsignedByte(int index) {
    return (bcodes[index] & 0xFF);
  }

  private final int readSignedShort() {
    int i = bcodes[bcIndex++] << 8;
    i |= (bcodes[bcIndex++] & 0xFF);
    return i;
  }

  private final int readUnsignedShort() {
    int i = (bcodes[bcIndex++] & 0xFF) << 8;
    i |= (bcodes[bcIndex++] & 0xFF);
    return i;
  }

  private final int readSignedInt() {
    int i = bcodes[bcIndex++] << 24;
    i |= (bcodes[bcIndex++] & 0xFF) << 16;
    i |= (bcodes[bcIndex++] & 0xFF) << 8;
    i |= (bcodes[bcIndex++] & 0xFF);
    return i;
  }

  private final int getSignedInt(int index) {
    int i = bcodes[index++] << 24;
    i |= (bcodes[index++] & 0xFF) << 16;
    i |= (bcodes[index++] & 0xFF) << 8;
    i |= (bcodes[index] & 0xFF);
    return i;
  }
}
