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

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;

import com.ibm.wala.classLoader.IMethod;
import com.ibm.wala.ipa.callgraph.Context;
import com.ibm.wala.ssa.SSAArrayLengthInstruction;
import com.ibm.wala.ssa.SSAArrayLoadInstruction;
import com.ibm.wala.ssa.SSAArrayStoreInstruction;
import com.ibm.wala.ssa.SSACheckCastInstruction;
import com.ibm.wala.ssa.SSAConditionalBranchInstruction;
import com.ibm.wala.ssa.SSAGetInstruction;
import com.ibm.wala.ssa.SSAGotoInstruction;
import com.ibm.wala.ssa.SSAInstruction;
import com.ibm.wala.ssa.SSAInstructionFactory;
import com.ibm.wala.ssa.SSAInvokeInstruction;
import com.ibm.wala.ssa.SSAMonitorInstruction;
import com.ibm.wala.ssa.SSANewInstruction;
import com.ibm.wala.ssa.SSAPhiInstruction;
import com.ibm.wala.ssa.SSAPiInstruction;
import com.ibm.wala.ssa.SSAPutInstruction;
import com.ibm.wala.ssa.SSAReturnInstruction;
import com.ibm.wala.ssa.SSASwitchInstruction;
import com.ibm.wala.ssa.SSAThrowInstruction;
import com.ibm.wala.util.collections.ArrayIterator;
import com.ibm.wala.util.collections.HashSetFactory;
import com.ibm.wala.util.debug.Assertions;
import com.ibm.wala.util.graph.GraphIntegrity;
import com.ibm.wala.util.graph.GraphIntegrity.UnsoundGraphException;
import com.ibm.wala.util.graph.impl.NodeWithNumber;

/**
 * A {@link ControlFlowGraph} computed from a set of {@link SSAInstruction} instructions.
 * 
 * This is a funny CFG ... we assume that there are always fallthru edges, even from throws and returns. It is extremely fragile and
 * unsuited for flow-sensitive analysis.  Someday this should be nuked.
 */
public class InducedCFG extends AbstractCFG<SSAInstruction, InducedCFG.BasicBlock> {

  private static final boolean DEBUG = false;

  /**
   * A partial map from Instruction -> BasicBlock
   */
  private final BasicBlock[] i2block;

  private final Context context;

  private final SSAInstruction[] instructions;

  /**
   * TODO: we do not yet support induced CFGS with exception handlers.
   * 
   * NOTE: SIDE EFFECT!!! ... nulls out phi instructions and pi instructions in the instruction array!
   * 
   * @throws IllegalArgumentException if instructions is null
   */
  public InducedCFG(SSAInstruction[] instructions, IMethod method, Context context) {
    super(method);
    if (instructions == null) {
      throw new IllegalArgumentException("instructions is null");
    }
    if (method == null) {
      throw new IllegalArgumentException("method is null");
    }
    this.context = context;
    this.instructions = instructions;
    if (DEBUG) {
      System.err.println(("compute InducedCFG: " + method));
    }
    i2block = new BasicBlock[instructions.length];
    if (instructions.length == 0) {
      makeEmptyBlocks();
    } else {
      makeBasicBlocks();
    }
    init();
    computeEdges();

    if (DEBUG) {
      try {
        GraphIntegrity.check(this);
      } catch (UnsoundGraphException e) {
        e.printStackTrace();
        Assertions.UNREACHABLE();
      }
    }
  }

  @Override
  public int hashCode() {
    return context.hashCode() ^ getMethod().hashCode();
  }

  @Override
  public boolean equals(Object o) {
    return (o instanceof InducedCFG) && getMethod().equals(((InducedCFG) o).getMethod())
        && context.equals(((InducedCFG) o).context);
  }

  public SSAInstruction[] getInstructions() {
    return instructions;
  }

  /**
   * Compute outgoing edges in the control flow graph.
   */
  private void computeEdges() {
    for (Iterator it = iterator(); it.hasNext();) {
      BasicBlock b = (BasicBlock) it.next();
      if (b.equals(exit()))
        continue;
      b.computeOutgoingEdges();
    }
    clearPis(getInstructions());
  }

  private void clearPis(SSAInstruction[] instructions) {
    for (int i = 0; i < instructions.length; i++) {
      if (instructions[i] instanceof SSAPiInstruction) {
        instructions[i] = null;
      }
    }
  }

  /**
   * Create basic blocks for an empty method
   */
  private void makeEmptyBlocks() {
    BasicBlock b = new BasicBlock(-1);
    addNode(b);
  }

  protected BranchVisitor makeBranchVisitor(boolean[] r) {
    return new BranchVisitor(r);
  }

  protected PEIVisitor makePEIVisitor(boolean[] r) {
    return new PEIVisitor(r);
  }

  /**
   * Walk through the instructions and compute basic block boundaries.
   */
  private void makeBasicBlocks() {
    SSAInstruction[] instructions = getInstructions();
    final boolean[] r = new boolean[instructions.length];

    // Compute r so r[i] == true iff instruction i begins a basic block.
    // While doing so count the number of blocks.
    r[0] = true;
    BranchVisitor branchVisitor = makeBranchVisitor(r);
    PEIVisitor peiVisitor = makePEIVisitor(r);
    for (int i = 0; i < instructions.length; i++) {
      if (instructions[i] != null) {
        branchVisitor.setIndex(i);
        instructions[i].visit(branchVisitor);
        // TODO: deal with exception handlers
        peiVisitor.setIndex(i);
        instructions[i].visit(peiVisitor);
      }
    }

    BasicBlock b = null;
    for (int i = 0; i < r.length; i++) {
      if (r[i]) {
        b = new BasicBlock(i);
        addNode(b);
        int j = i;
        while (instructions[j] instanceof SSAPhiInstruction) {
          b.addPhi((SSAPhiInstruction) instructions[j]);
          j++;
        }

        if (DEBUG) {
          System.err.println(("Add basic block " + b));
        }
      }
      if (instructions[i] instanceof SSAPiInstruction) {
        // add it to the current basic block
        b.addPi((SSAPiInstruction) instructions[i]);
      }
      i2block[i] = b;
    }
    // allocate the exit block
    BasicBlock exit = new BasicBlock(-1);
    if (DEBUG) {
      System.err.println(("Add exit block " + exit));
    }
    addNode(exit);
    clearPhis(instructions);
  }

  /**
   * set to null any slots in the array with phi instructions
   */
  private void clearPhis(SSAInstruction[] instructions) {
    for (int i = 0; i < instructions.length; i++) {
      if (instructions[i] instanceof SSAPhiInstruction) {
        instructions[i] = null;
      }
    }
  }

  /**
   * This visitor identifies basic block boundaries induced by branch instructions.
   */
  public class BranchVisitor extends SSAInstruction.Visitor {
    final private boolean[] r;

    protected BranchVisitor(boolean[] r) {
      this.r = r;
    }

    int index = 0;

    void setIndex(int i) {
      index = i;
    }

    @Override
    public void visitGoto(SSAGotoInstruction instruction) {
      Assertions.UNREACHABLE("haven't implemented logic for goto yet.");
      breakBasicBlock(index);
    }

    @Override
    public void visitConditionalBranch(SSAConditionalBranchInstruction instruction) {
      Assertions.UNREACHABLE("haven't implemented logic for cbranch yet.");
      breakBasicBlock(index);
    }

    @Override
    public void visitSwitch(SSASwitchInstruction instruction) {
      Assertions.UNREACHABLE("haven't implemented logic for switch yet.");
      // breakBasicBlock();
      // int[] targets = instruction.getTargets();
      // for (int i = 0; i < targets.length; i++) {
      // r[targets[i]] = true;
      // }
    }

    @Override
    public void visitPhi(SSAPhiInstruction instruction) {
      // we can have more than one phi instruction in a row. break the basic block
      // only before the first one.
      if (!(instructions[index - 1] instanceof SSAPhiInstruction)) {
        breakBasicBlock(index - 1);
      }
    }

    @Override
    public void visitReturn(SSAReturnInstruction instruction) {
      breakBasicBlock(index);
    }

    @Override
    public void visitThrow(SSAThrowInstruction instruction) {
      breakBasicBlock(index);
    }

    /**
     * introduce a basic block boundary immediately after instruction number 'index' if it is not followed by pi instructions, or
     * after the pi instructions otherwise
     */
    protected void breakBasicBlock(int index) {
      int j = index + 1;
      while (j < instructions.length && instructions[j] instanceof SSAPiInstruction) {
        j++;
      }
      if (j < instructions.length && !r[j]) {
        r[j] = true;
      }
    }
  }

  // TODO: extend the following to deal with catch blocks. Right now
  // it simply breaks basic blocks at PEIs.
  public class PEIVisitor extends SSAInstruction.Visitor {
    final private boolean[] r;

    protected PEIVisitor(boolean[] r) {
      this.r = r;
    }

    int index = 0;

    void setIndex(int i) {
      index = i;
    }

    protected void breakBasicBlock() {
      int j = index + 1;
      while (j < instructions.length && instructions[j] instanceof SSAPiInstruction) {
        j++;
      }
      if (j < instructions.length && !r[j]) {
        r[j] = true;
      }
    }

    @Override
    public void visitArrayLength(SSAArrayLengthInstruction instruction) {
      breakBasicBlock();
    }

    @Override
    public void visitArrayLoad(SSAArrayLoadInstruction instruction) {
      breakBasicBlock();
    }

    @Override
    public void visitArrayStore(SSAArrayStoreInstruction instruction) {
      breakBasicBlock();
    }

    @Override
    public void visitCheckCast(SSACheckCastInstruction instruction) {
      breakBasicBlock();
    }

    @Override
    public void visitGet(SSAGetInstruction instruction) {
      breakBasicBlock();
    }

    @Override
    public void visitInvoke(SSAInvokeInstruction instruction) {
      breakBasicBlock();
    }

    @Override
    public void visitMonitor(SSAMonitorInstruction instruction) {
      breakBasicBlock();
    }

    @Override
    public void visitNew(SSANewInstruction instruction) {
      breakBasicBlock();
    }

    @Override
    public void visitPut(SSAPutInstruction instruction) {
      breakBasicBlock();
    }

    @Override
    public void visitThrow(com.ibm.wala.ssa.SSAThrowInstruction instruction) {
      breakBasicBlock();
    }
  }

  public BasicBlock getBlockForInstruction(int index) {
    if (i2block[index] == null) {
      Assertions.productionAssertion(false, "unexpected null for " + index);
    }
    return i2block[index];
  }

  // TODO: share some common parts of this implementation with the ShrikeCFG
  // implementation! right now it's clone-and-owned :(
  public class BasicBlock extends NodeWithNumber implements IBasicBlock<SSAInstruction> {

    private Collection<SSAPhiInstruction> phis;

    public Collection<SSAPhiInstruction> getPhis() {
      return phis == null ? Collections.<SSAPhiInstruction> emptyList() : Collections.unmodifiableCollection(phis);
    }

    public void addPhi(SSAPhiInstruction phiInstruction) {
      if (phis == null) {
        phis = new ArrayList<SSAPhiInstruction>(1);
      }
      phis.add(phiInstruction);
    }

    private ArrayList<SSAPiInstruction> pis;

    public Collection<SSAPiInstruction> getPis() {
      return pis == null ? Collections.<SSAPiInstruction> emptyList() : Collections.unmodifiableCollection(pis);
    }

    public void addPi(SSAPiInstruction piInstruction) {
      if (pis == null) {
        pis = new ArrayList<SSAPiInstruction>(1);
      }
      pis.add(piInstruction);
    }

    @Override
    public boolean equals(Object arg0) {
      if (arg0 != null && getClass().equals(arg0.getClass())) {
        BasicBlock other = (BasicBlock) arg0;
        return start == other.start && getMethod().equals(other.getMethod());
      } else {
        return false;
      }
    }

    private final int start;

    BasicBlock(int start) {
      this.start = start;
    }

    /**
     * Add any exceptional edges generated by the last instruction in a basic block.
     * 
     * @param last the last instruction in a basic block.
     */
    private void addExceptionalEdges(SSAInstruction last) {
      if (last.isPEI()) {
        // we don't currently model catch blocks here ... instead just link
        // to the exit block
        addExceptionalEdgeTo(exit());
      }
    }

    private void addNormalEdgeTo(BasicBlock b) {
      addNormalEdge(this, b);
    }

    private void addExceptionalEdgeTo(BasicBlock b) {
      addExceptionalEdge(this, b);
    }

    private void computeOutgoingEdges() {

      if (DEBUG) {
        System.err.println(("Block " + this + ": computeOutgoingEdges()"));
      }
      // TODO: we don't currently model branches

      SSAInstruction last = getInstructions()[getLastInstructionIndex()];
      addExceptionalEdges(last);
      // this CFG is odd in that we assume fallthru might always
      // happen .. this is because I'm too lazy to code control
      // flow in all method summaries yet.
      int normalSuccNodeNumber = getGraphNodeId() + 1;
      if (true) {
        // if (last.isFallThrough()) {
        if (DEBUG) {
          System.err.println(("Add fallthru to " + getNode(getGraphNodeId() + 1)));
        }
        addNormalEdgeTo(getNode(normalSuccNodeNumber));
      }
      if (pis != null) {
        updatePiInstrs(normalSuccNodeNumber);
      }
      if (last instanceof SSAReturnInstruction) {
        // link each return instrution to the exit block.
        BasicBlock exit = exit();
        addNormalEdgeTo(exit);
      }
    }

    /**
     * correct pi instructions with appropriate basic block numbers. we assume for now that pi instructions are always associated
     * with the normal "fall-thru" exit edge.
     */
    private void updatePiInstrs(int normalSuccNodeNumber) {
      for (int i = 0; i < pis.size(); i++) {
        SSAPiInstruction pi = pis.get(i);
        SSAInstructionFactory insts = getMethod().getDeclaringClass().getClassLoader().getInstructionFactory();
        pis.set(i, insts.PiInstruction(SSAInstruction.NO_INDEX, pi.getDef(), pi.getVal(), getGraphNodeId(), normalSuccNodeNumber, pi.getCause()));
      }
    }

    public int getFirstInstructionIndex() {
      return start;
    }

    public int getLastInstructionIndex() {
      int exitNumber = InducedCFG.this.getNumber(exit());
      if (getGraphNodeId() == exitNumber) {
        // this is the exit block
        return -2;
      }
      if (getGraphNodeId() == (exitNumber - 1)) {
        // this is the last non-exit block
        return getInstructions().length - 1;
      } else {
        BasicBlock next = getNode(getGraphNodeId() + 1);
        return next.getFirstInstructionIndex() - 1;
      }
    }

    public boolean isCatchBlock() {
      // TODO: support induced CFG with catch blocks.
      return false;
    }

    @Override
    public int hashCode() {
      return 1153 * getGraphNodeId() + getMethod().hashCode();
    }

    /**
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
      return "BB[Induced]" + getNumber() + " - " + getMethod().getSignature();
    }

    /*
     * @see com.ibm.wala.cfg.IBasicBlock#isExitBlock()
     */
    public boolean isExitBlock() {
      return getLastInstructionIndex() == -2;
    }

    /*
     * @see com.ibm.wala.cfg.IBasicBlock#isEntryBlock()
     */
    public boolean isEntryBlock() {
      return getNumber() == 0;
    }

    /*
     * @see com.ibm.wala.cfg.IBasicBlock#getMethod()
     */
    public IMethod getMethod() {
      return InducedCFG.this.getMethod();
    }

    public boolean endsInPEI() {
      return getInstructions()[getLastInstructionIndex()].isPEI();
    }

    public boolean endsInReturn() {
      return getInstructions()[getLastInstructionIndex()] instanceof SSAReturnInstruction;
    }

    /*
     * @see com.ibm.wala.cfg.IBasicBlock#getNumber()
     */
    public int getNumber() {
      return InducedCFG.this.getNumber(this);
    }

    public Iterator<SSAInstruction> iterator() {
      return new ArrayIterator<SSAInstruction>(getInstructions(), getFirstInstructionIndex(), getLastInstructionIndex());
    }
  }

  @Override
  public String toString() {
    StringBuffer s = new StringBuffer("");
    for (Iterator it = iterator(); it.hasNext();) {
      BasicBlock bb = (BasicBlock) it.next();
      s.append("BB").append(getNumber(bb)).append("\n");
      for (int j = bb.getFirstInstructionIndex(); j <= bb.getLastInstructionIndex(); j++) {
        s.append("  ").append(j).append("  ").append(getInstructions()[j]).append("\n");
      }

      Iterator<BasicBlock> succNodes = getSuccNodes(bb);
      while (succNodes.hasNext()) {
        s.append("    -> BB").append(getNumber(succNodes.next())).append("\n");
      }
    }
    return s.toString();
  }

  /**
   * Since this CFG is synthetic, for now we assume the instruction index is the same as the program counter
   * 
   * @see com.ibm.wala.cfg.ControlFlowGraph#getProgramCounter(int)
   */
  public int getProgramCounter(int index) {
    if (getInstructions().length <= index) {
      throw new IllegalArgumentException("invalid index " + index + " " + getInstructions().length);
    }
    if (getInstructions()[index] instanceof SSAInvokeInstruction) {
      return ((SSAInvokeInstruction) getInstructions()[index]).getCallSite().getProgramCounter();
    } else {
      return index;
    }
  }

  public Collection<SSAPhiInstruction> getAllPhiInstructions() {
    Collection<SSAPhiInstruction> result = HashSetFactory.make();
    for (Iterator<BasicBlock> it = iterator(); it.hasNext();) {
      BasicBlock b = it.next();
      result.addAll(b.getPhis());
    }
    return result;
  }

}
