/*
 * Copyright (c) 2013 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.cast.java.ipa.slicer;

import com.ibm.wala.cast.ir.ssa.AstAssertInstruction;
import com.ibm.wala.cast.java.ipa.modref.AstJavaModRef;
import com.ibm.wala.ipa.callgraph.CGNode;
import com.ibm.wala.ipa.callgraph.CallGraph;
import com.ibm.wala.ipa.callgraph.impl.PartialCallGraph;
import com.ibm.wala.ipa.callgraph.propagation.InstanceKey;
import com.ibm.wala.ipa.callgraph.propagation.PointerAnalysis;
import com.ibm.wala.ipa.slicer.NormalStatement;
import com.ibm.wala.ipa.slicer.SDG;
import com.ibm.wala.ipa.slicer.Slicer;
import com.ibm.wala.ipa.slicer.Statement;
import com.ibm.wala.ssa.IR;
import com.ibm.wala.ssa.SSAArrayLoadInstruction;
import com.ibm.wala.ssa.SSAArrayStoreInstruction;
import com.ibm.wala.ssa.SSAGetInstruction;
import com.ibm.wala.ssa.SSAInstruction;
import com.ibm.wala.ssa.SSAMonitorInstruction;
import com.ibm.wala.ssa.SSAPutInstruction;
import com.ibm.wala.util.CancelException;
import com.ibm.wala.util.collections.Pair;
import com.ibm.wala.util.graph.traverse.DFS;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.function.Predicate;

public class AstJavaSlicer {

  /*
   * Use the passed-in SDG
   */
  public static Collection<Statement> computeBackwardSlice(SDG<?> sdg, Collection<Statement> ss)
      throws IllegalArgumentException, CancelException {
    return computeSlice(sdg, ss, true);
  }

  /** @param ss a collection of statements of interest */
  public static Collection<Statement> computeSlice(
      SDG<?> sdg, Collection<Statement> ss, boolean backward) throws CancelException {
    return new Slicer().slice(sdg, ss, backward);
  }

  public static Set<Statement> gatherStatements(
      CallGraph CG, Collection<CGNode> partialRoots, Predicate<SSAInstruction> filter) {
    Set<Statement> result = new HashSet<>();
    for (CGNode n : DFS.getReachableNodes(CG, partialRoots)) {
      IR nir = n.getIR();
      if (nir != null) {
        SSAInstruction insts[] = nir.getInstructions();
        for (int i = 0; i < insts.length; i++) {
          if (filter.test(insts[i])) {
            result.add(new NormalStatement(n, i));
          }
        }
      }
    }

    return result;
  }

  public static Set<Statement> gatherAssertions(CallGraph CG, Collection<CGNode> partialRoots) {
    return gatherStatements(CG, partialRoots, AstAssertInstruction.class::isInstance);
  }

  public static Set<Statement> gatherMonitors(CallGraph CG, Collection<CGNode> partialRoots) {
    return gatherStatements(CG, partialRoots, SSAMonitorInstruction.class::isInstance);
  }

  public static Set<Statement> gatherWrites(CallGraph CG, Collection<CGNode> partialRoots) {
    return gatherStatements(
        CG,
        partialRoots,
        o -> (o instanceof SSAPutInstruction) || (o instanceof SSAArrayStoreInstruction));
  }

  public static Set<Statement> gatherReads(CallGraph CG, Collection<CGNode> partialRoots) {
    return gatherStatements(
        CG,
        partialRoots,
        o -> (o instanceof SSAGetInstruction) || (o instanceof SSAArrayLoadInstruction));
  }

  public static Pair<Collection<Statement>, SDG<? extends InstanceKey>> computeAssertionSlice(
      CallGraph CG,
      PointerAnalysis<? extends InstanceKey> pa,
      Collection<CGNode> partialRoots,
      boolean multiThreadedCode)
      throws IllegalArgumentException, CancelException {
    CallGraph pcg = PartialCallGraph.make(CG, new LinkedHashSet<>(partialRoots));
    SDG<? extends InstanceKey> sdg =
        new SDG<>(
            pcg,
            pa,
            new AstJavaModRef<>(),
            Slicer.DataDependenceOptions.FULL,
            Slicer.ControlDependenceOptions.FULL);
    // System.err.println(("SDG:\n" + sdg));
    Set<Statement> stmts = gatherAssertions(CG, partialRoots);
    if (multiThreadedCode) {
      // Grab anything that has "side effects" under JMM
      stmts.addAll(gatherReads(CG, partialRoots));
      stmts.addAll(gatherWrites(CG, partialRoots));
      stmts.addAll(gatherMonitors(CG, partialRoots));
    }
    return Pair.make(AstJavaSlicer.computeBackwardSlice(sdg, stmts), sdg);
  }
}
