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

import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;

import com.ibm.wala.ssa.ConstantValue;
import com.ibm.wala.ssa.SSAInstruction;
import com.ibm.wala.types.MemberReference;
import com.ibm.wala.types.MethodReference;
import com.ibm.wala.types.TypeReference;
import com.ibm.wala.util.collections.HashMapFactory;
import com.ibm.wala.util.warnings.Warning;

/**
 * Summary information for a method.
 */
public class MethodSummary {

  protected final static SSAInstruction[] NO_STATEMENTS = new SSAInstruction[0];

  /**
   * The method summarized
   */
  final private MethodReference method;

  /**
   * List of statements that define this method summary
   */
  private ArrayList<SSAInstruction> statements;

  /**
   * Map: value number -> constant
   */
  private Map<Integer, ConstantValue> constantValues;

  /**
   * The next available program counter value.
   */
  private int nextProgramCounter = 0;

  /**
   * Some reason this method summary indicates a problem.
   */
  private String poison;

  /**
   * An indication of how severe the poison problem is.
   */
  private byte poisonLevel;

  /**
   * Is this a static method?
   */
  private boolean isStatic = false;

  /**
   * Is this a "factory" method?
   */
  private boolean isFactory = false;

  public MethodSummary(MethodReference method) {
    if (method == null) {
      throw new IllegalArgumentException("null method");
    }
    this.method = method;
  }
  
  public int getNumberOfStatements() {
    return (statements == null ? 0 : statements.size());
  }

  public void addStatement(SSAInstruction statement) {
    if (statements == null) {
      statements = new ArrayList<SSAInstruction>();
    }
    statements.add(statement);
  }

  public void addConstant(Integer vn, ConstantValue value) {
    if (constantValues == null)
      constantValues = HashMapFactory.make(5);
    constantValues.put(vn, value);
  }

  /**
   * Returns the method.
   * 
   * @return MethodReference
   */
  public MemberReference getMethod() {
    return method;
  }

  public boolean isNative() {
    // TODO implement this.
    return false;
  }

  /**
   * @param reason
   */
  public void addPoison(String reason) {
    this.poison = reason;
  }

  public boolean hasPoison() {
    return poison != null;
  }

  public String getPoison() {
    return poison;
  }

  public void setPoisonLevel(byte b) {
    poisonLevel = b;
    assert b == Warning.MILD || b == Warning.MODERATE || b == Warning.SEVERE;
  }

  public byte getPoisonLevel() {
    return poisonLevel;
  }

  public SSAInstruction[] getStatements() {
    if (statements == null) {
      return NO_STATEMENTS;
    } else {
      SSAInstruction[] result = new SSAInstruction[statements.size()];
      Iterator<SSAInstruction> it = statements.iterator();
      for (int i = 0; i < result.length; i++) {
        result[i] = it.next();
      }
      return result;
    }
  }

  public Map<Integer, ConstantValue> getConstants() {
    return constantValues;
  }

  /**
   * @return the number of parameters, including the implicit 'this'
   */
  public int getNumberOfParameters() {
    return (isStatic()) ? method.getNumberOfParameters() : method.getNumberOfParameters() + 1;
  }

  public boolean isStatic() {
    return isStatic;
  }

  public void setStatic(boolean b) {
    isStatic = b;
  }

  public TypeReference getReturnType() {
    return method.getReturnType();
  }

  @Override
  public String toString() {
    return "[Summary: " + method + "]";
  }

  /**
   * Note that by convention, getParameterType(0) == this for non-static methods.
   */
  public TypeReference getParameterType(int i) {
    if (isStatic()) {
      return method.getParameterType(i);
    } else {
      if (i == 0) {
        return method.getDeclaringClass();
      } else {
        return method.getParameterType(i - 1);
      }
    }
  }

  public int getNextProgramCounter() {
    return nextProgramCounter++;
  }

  /**
   * Record if this is a "factory" method; meaning it returns some object which we know little about ... usually we'll resolve this
   * based on downstream uses of the object
   * 
   * @param b
   */
  public void setFactory(boolean b) {
    this.isFactory = b;
  }

  public boolean isFactory() {
    return isFactory;
  }

}
