/*
 * 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;
  }

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

    final private 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
  final public 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;
  }

  final public int getInvocationMode() {
    return opcode;
  }

  final public 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
  final public int hashCode() {
    return getMethodSignature().hashCode() + 9011 * getClassType().hashCode() + 317 * getMethodName().hashCode() + opcode * 3188;
  }

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

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

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

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

  @Override
  final public 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);
    }
  }
}
