/*
 * 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.shrikeBT;

/** This class represents method invocation instructions. */
public class InvokeInstruction extends Instruction implements IInvokeInstruction {
  protected String type;

  protected String classType;

  protected String methodName;

  InvokeInstruction(short opcode, String type, String classType, String methodName) {
    super(opcode);
    this.type = type;
    this.classType = classType;
    this.methodName = methodName;
  }

  public static InvokeInstruction make(
      String type, String className, String methodName, Dispatch mode) throws NullPointerException {
    if (type == null) {
      throw new NullPointerException("type must not be null");
    }
    if (className == null) {
      throw new NullPointerException("className must not be null");
    }
    if (methodName == null) {
      throw new NullPointerException("methodName must not be null");
    }
    if (mode == null) {
      throw new NullPointerException("mode must not be null");
    }
    short opcode = 0;
    switch (mode) {
      case VIRTUAL:
        opcode = OP_invokevirtual;
        break;
      case SPECIAL:
        opcode = OP_invokespecial;
        break;
      case STATIC:
        opcode = OP_invokestatic;
        break;
      case INTERFACE:
        opcode = OP_invokeinterface;
        break;
      default:
        assert false;
    }
    return new InvokeInstruction(opcode, type, className, methodName);
  }

  ConstantPoolReader getLazyConstantPool() {
    return null;
  }

  static final class Lazy extends InvokeInstruction {
    private final ConstantPoolReader cp;

    private final int index;

    Lazy(short opcode, ConstantPoolReader cp, int index) {
      super(opcode, null, null, null);
      this.index = index;
      this.cp = cp;
    }

    @Override
    ConstantPoolReader getLazyConstantPool() {
      return cp;
    }

    int getCPIndex() {
      return index;
    }

    @Override
    public String getClassType() {
      if (classType == null) {
        classType = cp.getConstantPoolMemberClassType(index);
      }
      return classType;
    }

    @Override
    public String getMethodName() {
      if (methodName == null) {
        methodName = cp.getConstantPoolMemberName(index);
      }
      return methodName;
    }

    @Override
    public String getMethodSignature() {
      if (type == null) {
        type = cp.getConstantPoolMemberType(index);
      }
      return type;
    }
  }

  static InvokeInstruction make(ConstantPoolReader cp, int index, int mode) {
    if (mode < OP_invokevirtual || mode > OP_invokeinterface) {
      throw new IllegalArgumentException("Unknown mode: " + mode);
    }
    return new Lazy((short) mode, cp, index);
  }

  @Override
  public final boolean equals(Object o) {
    if (o instanceof InvokeInstruction) {
      InvokeInstruction i = (InvokeInstruction) o;
      return i.getMethodSignature().equals(getMethodSignature())
          && i.getClassType().equals(getClassType())
          && i.getMethodName().equals(getMethodName())
          && i.opcode == opcode;
    } else {
      return false;
    }
  }

  @Override
  public String getClassType() {
    return classType;
  }

  @Override
  public String getMethodName() {
    return methodName;
  }

  @Override
  public String getMethodSignature() {
    return type;
  }

  public final int getInvocationMode() {
    return opcode;
  }

  public final String getInvocationModeString() {
    switch (opcode) {
      case Constants.OP_invokestatic:
        return "STATIC";
      case Constants.OP_invokeinterface:
        return "INTERFACE";
      case Constants.OP_invokespecial:
        return "SPECIAL";
      case Constants.OP_invokevirtual:
        return "VIRTUAL";
      default:
        throw new Error("Unknown mode: " + opcode);
    }
  }

  @Override
  public final int hashCode() {
    return getMethodSignature().hashCode()
        + 9011 * getClassType().hashCode()
        + 317 * getMethodName().hashCode()
        + opcode * 3188;
  }

  @Override
  public final int getPoppedCount() {
    return (opcode == Constants.OP_invokestatic ? 0 : 1)
        + Util.getParamsCount(getMethodSignature());
  }

  @Override
  public final String getPushedType(String[] types) {
    String t = Util.getReturnType(getMethodSignature());
    if (t.equals(Constants.TYPE_void)) {
      return null;
    } else {
      return t;
    }
  }

  @Override
  public final byte getPushedWordSize() {
    String t = getMethodSignature();
    int index = t.lastIndexOf(')');
    return Util.getWordSize(t, index + 1);
  }

  @Override
  public final void visit(IInstruction.Visitor v) throws NullPointerException {
    v.visitInvoke(this);
  }

  @Override
  public final String toString() {
    return "Invoke("
        + getInvocationModeString()
        + ','
        + getClassType()
        + ','
        + getMethodName()
        + ','
        + getMethodSignature()
        + ')';
  }

  @Override
  public boolean isPEI() {
    return true;
  }

  @Override
  public Dispatch getInvocationCode() {
    switch (opcode) {
      case Constants.OP_invokestatic:
        return Dispatch.STATIC;
      case Constants.OP_invokeinterface:
        return Dispatch.INTERFACE;
      case Constants.OP_invokespecial:
        return Dispatch.SPECIAL;
      case Constants.OP_invokevirtual:
        return Dispatch.VIRTUAL;
      default:
        throw new Error("Unknown mode: " + opcode);
    }
  }
}
