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

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import com.ibm.wala.fixedpoint.impl.GeneralStatement;
import com.ibm.wala.fixpoint.AbstractOperator;
import com.ibm.wala.fixpoint.AbstractStatement;
import com.ibm.wala.fixpoint.IFixedPointStatement;
import com.ibm.wala.fixpoint.IFixedPointSystem;
import com.ibm.wala.fixpoint.IVariable;
import com.ibm.wala.fixpoint.UnaryOperator;
import com.ibm.wala.fixpoint.UnaryStatement;
import com.ibm.wala.util.collections.CompoundIterator;
import com.ibm.wala.util.collections.EmptyIterator;
import com.ibm.wala.util.collections.Filter;
import com.ibm.wala.util.collections.FilterIterator;
import com.ibm.wala.util.collections.HashSetFactory;
import com.ibm.wala.util.collections.SmallMap;
import com.ibm.wala.util.debug.Assertions;
import com.ibm.wala.util.debug.UnimplementedError;
import com.ibm.wala.util.graph.AbstractNumberedGraph;
import com.ibm.wala.util.graph.Graph;
import com.ibm.wala.util.graph.INodeWithNumber;
import com.ibm.wala.util.graph.NumberedEdgeManager;
import com.ibm.wala.util.graph.NumberedGraph;
import com.ibm.wala.util.graph.NumberedNodeManager;
import com.ibm.wala.util.graph.impl.DelegatingNumberedNodeManager;
import com.ibm.wala.util.graph.impl.SparseNumberedEdgeManager;
import com.ibm.wala.util.graph.traverse.Topological;
import com.ibm.wala.util.heapTrace.HeapTracer;
import com.ibm.wala.util.intset.BasicNaturalRelation;
import com.ibm.wala.util.intset.IBinaryNaturalRelation;
import com.ibm.wala.util.intset.IntIterator;
import com.ibm.wala.util.intset.IntPair;
import com.ibm.wala.util.intset.IntSet;

/**
 * A dataflow graph implementation specialized for propagation-based pointer analysis
 */
public class PropagationGraph implements IFixedPointSystem<PointsToSetVariable> {

  private final static boolean DEBUG = false;

  private final static boolean VERBOSE = false;

  /**
   * Track nodes (PointsToSet Variables and AbstractEquations)
   */
  private final NumberedNodeManager<INodeWithNumber> nodeManager = new DelegatingNumberedNodeManager<INodeWithNumber>();

  /**
   * Track edges (equations) that are not represented implicitly
   */
  private final NumberedEdgeManager<INodeWithNumber> edgeManager = new SparseNumberedEdgeManager<INodeWithNumber>(nodeManager, 2,
      BasicNaturalRelation.SIMPLE);

  private final DelegateGraph delegateGraph = new DelegateGraph();

  private final HashSet<AbstractStatement> delegateStatements = HashSetFactory.make();

  /**
   * special representation for implicitly represented unary equations. This is a map from UnaryOperator ->
   * IBinaryNonNegativeIntRelation.
   * 
   * for UnaryOperator op, let R be implicitMap.get(op) then (i,j) \in R implies i op j is an equation in the graph
   * 
   */
  private final SmallMap<UnaryOperator<PointsToSetVariable>, IBinaryNaturalRelation> implicitUnaryMap = new SmallMap<UnaryOperator<PointsToSetVariable>, IBinaryNaturalRelation>();

  /**
   * The inverse of relations in the implicit map
   * 
   * for UnaryOperator op, let R be invImplicitMap.get(op) then (i,j) \in R implies j op i is an equation in the graph
   */
  private final SmallMap<UnaryOperator<PointsToSetVariable>, IBinaryNaturalRelation> invImplicitUnaryMap = new SmallMap<UnaryOperator<PointsToSetVariable>, IBinaryNaturalRelation>();

  /**
   * Number of implicit unary equations registered
   */
  private int implicitUnaryCount = 0;

  /**
   * @return a relation in map m corresponding to a key
   */
  private IBinaryNaturalRelation findOrCreateRelation(Map<UnaryOperator<PointsToSetVariable>, IBinaryNaturalRelation> m,
      UnaryOperator<PointsToSetVariable> key) {
    IBinaryNaturalRelation result = m.get(key);
    if (result == null) {
      result = makeRelation(key);
      m.put(key, result);
    }
    return result;
  }

  /**
   * @return a Relation object to track implicit equations using the operator
   */
  private IBinaryNaturalRelation makeRelation(AbstractOperator op) {
    byte[] implementation = null;
    if (op instanceof AssignOperator) {
      // lots of assignments.
      implementation = new byte[] { BasicNaturalRelation.SIMPLE_SPACE_STINGY, BasicNaturalRelation.SIMPLE_SPACE_STINGY };
    } else {
      // assume sparse assignments with any other operator.
      implementation = new byte[] { BasicNaturalRelation.SIMPLE_SPACE_STINGY };
    }
    return new BasicNaturalRelation(implementation, BasicNaturalRelation.SIMPLE);
  }

  /**
   * @author sfink
   * 
   *         A graph which tracks explicit equations.
   * 
   *         use this with care ...
   */
  private class DelegateGraph extends AbstractNumberedGraph<INodeWithNumber> {

    private int equationCount = 0;

    private int varCount = 0;

    @Override
    public void addNode(INodeWithNumber o) {
      Assertions.UNREACHABLE("Don't call me");
    }

    public void addEquation(AbstractStatement<PointsToSetVariable, ?> eq) {
      assert !containsStatement(eq);
      equationCount++;
      super.addNode(eq);
    }

    public void addVariable(PointsToSetVariable v) {
      if (!containsVariable(v)) {
        varCount++;
        super.addNode(v);
      }
    }

    /*
     * @see com.ibm.wala.util.graph.AbstractGraph#getNodeManager()
     */
    @Override
    protected NumberedNodeManager<INodeWithNumber> getNodeManager() {
      return nodeManager;
    }

    /*
     * @see com.ibm.wala.util.graph.AbstractGraph#getEdgeManager()
     */
    @Override
    protected NumberedEdgeManager<INodeWithNumber> getEdgeManager() {
      return edgeManager;
    }

    protected int getEquationCount() {
      return equationCount;
    }

    protected int getVarCount() {
      return varCount;
    }

  }

  /**
   * @throws IllegalArgumentException if eq is null
   */
  public void addStatement(GeneralStatement<PointsToSetVariable> eq) {
    if (eq == null) {
      throw new IllegalArgumentException("eq is null");
    }
    PointsToSetVariable lhs = eq.getLHS();
    delegateGraph.addEquation(eq);
    delegateStatements.add(eq);
    if (lhs != null) {
      delegateGraph.addVariable(lhs);
      delegateGraph.addEdge(eq, lhs);
    }
    for (int i = 0; i < eq.getRHS().length; i++) {
      PointsToSetVariable v = (PointsToSetVariable) eq.getRHS()[i];
      if (v != null) {
        delegateGraph.addVariable(v);
        delegateGraph.addEdge(v, eq);
      }
    }
  }

  public void addStatement(UnaryStatement<PointsToSetVariable> eq) throws IllegalArgumentException {
    if (eq == null) {
      throw new IllegalArgumentException("eq == null");
    }
    if (useImplicitRepresentation(eq)) {
      addImplicitStatement(eq);
    } else {
      PointsToSetVariable lhs = eq.getLHS();
      PointsToSetVariable rhs = eq.getRightHandSide();
      delegateGraph.addEquation(eq);
      delegateStatements.add(eq);
      if (lhs != null) {
        delegateGraph.addVariable(lhs);
        delegateGraph.addEdge(eq, lhs);
      }
      delegateGraph.addVariable(rhs);
      delegateGraph.addEdge(rhs, eq);
    }
  }

  /**
   * @return true iff this equation should be represented implicitly in this data structure
   */
  private boolean useImplicitRepresentation(IFixedPointStatement s) {
    AbstractStatement eq = (AbstractStatement) s;
    AbstractOperator op = eq.getOperator();
    return (op instanceof AssignOperator || op instanceof PropagationCallGraphBuilder.FilterOperator);
  }

  public void removeVariable(PointsToSetVariable p) {
    assert getNumberOfStatementsThatDef(p) == 0;
    assert getNumberOfStatementsThatUse(p) == 0;
    delegateGraph.removeNode(p);
  }

  private void addImplicitStatement(UnaryStatement<PointsToSetVariable> eq) {
    if (DEBUG) {
      System.err.println(("addImplicitStatement " + eq));
    }
    delegateGraph.addVariable(eq.getLHS());
    delegateGraph.addVariable(eq.getRightHandSide());
    int lhs = eq.getLHS().getGraphNodeId();
    int rhs = eq.getRightHandSide().getGraphNodeId();
    if (DEBUG) {
      System.err.println(("lhs rhs " + lhs + " " + rhs));
    }
    IBinaryNaturalRelation R = findOrCreateRelation(implicitUnaryMap, eq.getOperator());
    boolean b = R.add(lhs, rhs);
    if (b) {
      implicitUnaryCount++;
      IBinaryNaturalRelation iR = findOrCreateRelation(invImplicitUnaryMap, eq.getOperator());
      iR.add(rhs, lhs);
    }
  }

  private void removeImplicitStatement(UnaryStatement<PointsToSetVariable> eq) {
    if (DEBUG) {
      System.err.println(("removeImplicitStatement " + eq));
    }
    int lhs = eq.getLHS().getGraphNodeId();
    int rhs = eq.getRightHandSide().getGraphNodeId();
    if (DEBUG) {
      System.err.println(("lhs rhs " + lhs + " " + rhs));
    }
    IBinaryNaturalRelation R = findOrCreateRelation(implicitUnaryMap, eq.getOperator());
    R.remove(lhs, rhs);
    IBinaryNaturalRelation iR = findOrCreateRelation(invImplicitUnaryMap, eq.getOperator());
    iR.remove(rhs, lhs);
    implicitUnaryCount--;
  }

  @SuppressWarnings("unchecked")
  public Iterator<AbstractStatement> getStatements() {
    Iterator<AbstractStatement> it = new FilterIterator(delegateGraph.iterator(), new Filter() {
      public boolean accepts(Object x) {
        return x instanceof AbstractStatement;
      }
    });
    return new CompoundIterator<AbstractStatement>(it, new GlobalImplicitIterator());
  }

  /**
   * Iterator of implicit equations that use a particular variable.
   */
  private final class ImplicitUseIterator implements Iterator<AbstractStatement> {

    final PointsToSetVariable use;

    final IntIterator defs;

    final UnaryOperator<PointsToSetVariable> op;

    ImplicitUseIterator(UnaryOperator<PointsToSetVariable> op, PointsToSetVariable use, IntSet defs) {
      this.op = op;
      this.use = use;
      this.defs = defs.intIterator();
    }

    public boolean hasNext() {
      return defs.hasNext();
    }

    public AbstractStatement next() {
      int l = defs.next();
      PointsToSetVariable lhs = (PointsToSetVariable) delegateGraph.getNode(l);
      UnaryStatement temp = op.makeEquation(lhs, use);
      if (DEBUG) {
        System.err.print(("XX Return temp: " + temp));
        System.err.println(("lhs rhs " + l + " " + use.getGraphNodeId()));
      }
      return temp;
    }

    public void remove() {
      // TODO Auto-generated method stub
      Assertions.UNREACHABLE();
    }
  }

  /**
   * Iterator of implicit equations that def a particular variable.
   */
  private final class ImplicitDefIterator implements Iterator<AbstractStatement> {

    final PointsToSetVariable def;

    final IntIterator uses;

    final UnaryOperator<PointsToSetVariable> op;

    ImplicitDefIterator(UnaryOperator<PointsToSetVariable> op, IntSet uses, PointsToSetVariable def) {
      this.op = op;
      this.def = def;
      this.uses = uses.intIterator();
    }

    public boolean hasNext() {
      return uses.hasNext();
    }

    public AbstractStatement next() {
      int r = uses.next();
      PointsToSetVariable rhs = (PointsToSetVariable) delegateGraph.getNode(r);
      UnaryStatement temp = op.makeEquation(def, rhs);
      if (DEBUG) {
        System.err.print(("YY Return temp: " + temp));
      }
      return temp;
    }

    public void remove() {
      // TODO Auto-generated method stub
      Assertions.UNREACHABLE();
    }
  }

  /**
   * Iterator of all implicit equations
   */
  private class GlobalImplicitIterator implements Iterator<AbstractStatement> {

    private final Iterator<UnaryOperator<PointsToSetVariable>> outerKeyDelegate = implicitUnaryMap.keySet().iterator();

    private Iterator innerDelegate;

    private UnaryOperator<PointsToSetVariable> currentOperator;

    GlobalImplicitIterator() {
      advanceOuter();
    }

    /**
     * advance to the next operator
     */
    private void advanceOuter() {
      innerDelegate = null;
      while (outerKeyDelegate.hasNext()) {
        currentOperator = outerKeyDelegate.next();
        IBinaryNaturalRelation R = implicitUnaryMap.get(currentOperator);
        Iterator it = R.iterator();
        if (it.hasNext()) {
          innerDelegate = it;
          return;
        }
      }
    }

    public boolean hasNext() {
      return innerDelegate != null;
    }

    public AbstractStatement next() {
      IntPair p = (IntPair) innerDelegate.next();
      int lhs = p.getX();
      int rhs = p.getY();
      UnaryStatement result = currentOperator.makeEquation((PointsToSetVariable) delegateGraph.getNode(lhs),
          (PointsToSetVariable) delegateGraph.getNode(rhs));
      if (!innerDelegate.hasNext()) {
        advanceOuter();
      }
      return result;
    }

    public void remove() {
      // TODO Auto-generated method stub
      Assertions.UNREACHABLE();

    }
  }

  public void removeStatement(IFixedPointStatement<PointsToSetVariable> eq) throws IllegalArgumentException {
    if (eq == null) {
      throw new IllegalArgumentException("eq == null");
    }
    if (useImplicitRepresentation(eq)) {
      removeImplicitStatement((UnaryStatement<PointsToSetVariable>) eq);
    } else {
      delegateStatements.remove(eq);
      delegateGraph.removeNodeAndEdges(eq);
    }
  }

  public void reorder() {
    VariableGraphView graph = new VariableGraphView();

    Iterator<PointsToSetVariable> order = Topological.makeTopologicalIter(graph);

    int number = 0;
    while (order.hasNext()) {
      Object elt = order.next();
      if (elt instanceof IVariable) {
        IVariable v = (IVariable) elt;
        v.setOrderNumber(number++);
      }
    }
  }

  /**
   * A graph of just the variables in the system. v1 -> v2 iff there exists equation e s.t. e uses v1 and e defs v2.
   * 
   * Note that this graph trickily and fragilely reuses the nodeManager from the delegateGraph, above. This will work ok as long as
   * every variable is inserted in the delegateGraph.
   */
  private class VariableGraphView extends AbstractNumberedGraph<PointsToSetVariable> {

    /*
     * @see com.ibm.wala.util.graph.Graph#removeNodeAndEdges(java.lang.Object)
     */
    @Override
    public void removeNodeAndEdges(PointsToSetVariable N) {
      Assertions.UNREACHABLE();
    }

    /*
     * @see com.ibm.wala.util.graph.NodeManager#iterateNodes()
     */
    @Override
    public Iterator<PointsToSetVariable> iterator() {
      return getVariables();
    }

    /*
     * @see com.ibm.wala.util.graph.NodeManager#getNumberOfNodes()
     */
    @Override
    public int getNumberOfNodes() {
      return delegateGraph.getVarCount();
    }

    /*
     * @see com.ibm.wala.util.graph.NodeManager#addNode(java.lang.Object)
     */
    @Override
    public void addNode(PointsToSetVariable n) {
      Assertions.UNREACHABLE();

    }

    /*
     * @see com.ibm.wala.util.graph.NodeManager#removeNode(java.lang.Object)
     */
    @Override
    public void removeNode(PointsToSetVariable n) {
      Assertions.UNREACHABLE();

    }

    /*
     * @see com.ibm.wala.util.graph.NodeManager#containsNode(java.lang.Object)
     */
    @Override
    public boolean containsNode(PointsToSetVariable N) {
      Assertions.UNREACHABLE();
      return false;
    }

    /*
     * @see com.ibm.wala.util.graph.EdgeManager#getPredNodes(java.lang.Object)
     */
    @Override
    public Iterator<PointsToSetVariable> getPredNodes(PointsToSetVariable v) {
      final Iterator eqs = getStatementsThatDef(v);
      return new Iterator<PointsToSetVariable>() {
        Iterator<INodeWithNumber> inner;

        public boolean hasNext() {
          return eqs.hasNext() || (inner != null);
        }

        public PointsToSetVariable next() {
          if (inner != null) {
            PointsToSetVariable result = (PointsToSetVariable)inner.next();
            if (!inner.hasNext()) {
              inner = null;
            }
            return result;
          } else {
            AbstractStatement eq = (AbstractStatement) eqs.next();
            if (useImplicitRepresentation(eq)) {
              return (PointsToSetVariable) ((UnaryStatement) eq).getRightHandSide();
            } else {
              inner = delegateGraph.getPredNodes(eq);
              return next();
            }
          }
        }

        public void remove() {
          // TODO Auto-generated method stub
          Assertions.UNREACHABLE();
        }
      };
    }

    /*
     * @see com.ibm.wala.util.graph.EdgeManager#getPredNodeCount(java.lang.Object)
     */
    @SuppressWarnings("unused")
    public int getPredNodeCount(INodeWithNumber N) {
      PointsToSetVariable v = (PointsToSetVariable) N;
      int result = 0;
      for (Iterator eqs = getStatementsThatDef(v); eqs.hasNext();) {
        AbstractStatement eq = (AbstractStatement) eqs.next();
        if (useImplicitRepresentation(eq)) {
          result++;
        } else {
          result += delegateGraph.getPredNodeCount(N);
        }
      }
      return result;
    }

    /*
     * @see com.ibm.wala.util.graph.EdgeManager#getSuccNodes(java.lang.Object)
     */
    @Override
    public Iterator<PointsToSetVariable> getSuccNodes(PointsToSetVariable v) {
      final Iterator eqs = getStatementsThatUse(v);
      return new Iterator<PointsToSetVariable>() {
        PointsToSetVariable nextResult;
        {
          advance();
        }

        public boolean hasNext() {
          return nextResult != null;
        }

        public PointsToSetVariable next() {
          PointsToSetVariable result = nextResult;
          advance();
          return result;
        }

        private void advance() {
          nextResult = null;
          while (eqs.hasNext() && nextResult == null) {
            AbstractStatement eq = (AbstractStatement) eqs.next();
            nextResult = (PointsToSetVariable) eq.getLHS();
          }
        }

        public void remove() {
          // TODO Auto-generated method stub
          Assertions.UNREACHABLE();
        }
      };
    }

    /*
     * @see com.ibm.wala.util.graph.EdgeManager#getSuccNodeCount(java.lang.Object)
     */
    @Override
    public int getSuccNodeCount(PointsToSetVariable v) {
      int result = 0;
      for (Iterator eqs = getStatementsThatUse(v); eqs.hasNext();) {
        AbstractStatement eq = (AbstractStatement) eqs.next();
        IVariable lhs = eq.getLHS();
        if (lhs != null) {
          result++;
        }
      }
      return result;
    }

    /*
     * @see com.ibm.wala.util.graph.EdgeManager#addEdge(java.lang.Object, java.lang.Object)
     */
    @Override
    public void addEdge(PointsToSetVariable src, PointsToSetVariable dst) {
      Assertions.UNREACHABLE();
    }

    /*
     * @see com.ibm.wala.util.graph.EdgeManager#removeAllIncidentEdges(java.lang.Object)
     */
    @Override
    public void removeAllIncidentEdges(PointsToSetVariable node) {
      Assertions.UNREACHABLE();
    }

    /*
     * @see com.ibm.wala.util.graph.AbstractGraph#getNodeManager()
     */
    @Override
    @SuppressWarnings("unchecked")
    protected NumberedNodeManager getNodeManager() {
      return nodeManager;
    }

    /*
     * @see com.ibm.wala.util.graph.AbstractGraph#getEdgeManager()
     */
    @Override
    @SuppressWarnings("unchecked")
    protected NumberedEdgeManager getEdgeManager() {
      // TODO Auto-generated method stub
      Assertions.UNREACHABLE();
      return null;
    }

  }

  @SuppressWarnings("unchecked")
  public Iterator<AbstractStatement> getStatementsThatUse(PointsToSetVariable v) {
    if (v == null) {
      throw new IllegalArgumentException("v is null");
    }
    int number = v.getGraphNodeId();
    if (number == -1) {
      return EmptyIterator.instance();
    }
    Iterator<INodeWithNumber> result = delegateGraph.getSuccNodes(v);
    for (int i = 0; i < invImplicitUnaryMap.size(); i++) {
      UnaryOperator op = invImplicitUnaryMap.getKey(i);
      IBinaryNaturalRelation R = (IBinaryNaturalRelation) invImplicitUnaryMap.getValue(i);
      IntSet s = R.getRelated(number);
      if (s != null) {
        result = new CompoundIterator<INodeWithNumber>(new ImplicitUseIterator(op, v, s), result);
      }
    }
    List<AbstractStatement> list = new ArrayList<AbstractStatement>();
    while (result.hasNext()) {
      list.add((AbstractStatement) result.next());
    }
    return list.iterator();
  }

  @SuppressWarnings("unchecked")
  public Iterator<AbstractStatement> getStatementsThatDef(PointsToSetVariable v) {
    if (v == null) {
      throw new IllegalArgumentException("v is null");
    }
    int number = v.getGraphNodeId();
    if (number == -1) {
      return EmptyIterator.instance();
    }
    Iterator<INodeWithNumber> result = delegateGraph.getPredNodes(v);
    for (int i = 0; i < implicitUnaryMap.size(); i++) {
      UnaryOperator op = implicitUnaryMap.getKey(i);
      IBinaryNaturalRelation R = (IBinaryNaturalRelation) implicitUnaryMap.getValue(i);
      IntSet s = R.getRelated(number);
      if (s != null) {
        result = new CompoundIterator<INodeWithNumber>(new ImplicitDefIterator(op, s, v), result);
      }
    }

    List<AbstractStatement> list = new ArrayList<AbstractStatement>();
    while (result.hasNext()) {
      list.add((AbstractStatement) result.next());
    }
    return list.iterator();
  }

  /**
   * Note that this implementation consults the implicit relation for each and every operator cached. This will be inefficient if
   * there are many implicit operators.
   * 
   * @throws IllegalArgumentException if v is null
   * 
   */
  public int getNumberOfStatementsThatUse(PointsToSetVariable v) {
    if (v == null) {
      throw new IllegalArgumentException("v is null");
    }
    int number = v.getGraphNodeId();
    if (number == -1) {
      return 0;
    }
    int result = delegateGraph.getSuccNodeCount(v);
    for (Iterator it = invImplicitUnaryMap.keySet().iterator(); it.hasNext();) {
      UnaryOperator op = (UnaryOperator) it.next();
      IBinaryNaturalRelation R = invImplicitUnaryMap.get(op);
      IntSet s = R.getRelated(number);
      if (s != null) {
        result += s.size();
      }
    }
    return result;
  }

  public int getNumberOfStatementsThatDef(PointsToSetVariable v) {
    if (v == null) {
      throw new IllegalArgumentException("v is null");
    }
    int number = v.getGraphNodeId();
    if (number == -1) {
      return 0;
    }
    int result = delegateGraph.getPredNodeCount(v);
    for (Iterator it = implicitUnaryMap.keySet().iterator(); it.hasNext();) {
      UnaryOperator op = (UnaryOperator) it.next();
      IBinaryNaturalRelation R = implicitUnaryMap.get(op);
      IntSet s = R.getRelated(number);
      if (s != null) {
        result += s.size();
      }
    }
    return result;
  }

  @SuppressWarnings("unchecked")
  public Iterator<PointsToSetVariable> getVariables() {
    Iterator<PointsToSetVariable> it = new FilterIterator(delegateGraph.iterator(), new Filter() {
      public boolean accepts(Object x) {
        return x instanceof IVariable;
      }
    });
    return it;
  }

  /*
   * @see com.ibm.wala.util.debug.VerboseAction#performVerboseAction()
   */
  public void performVerboseAction() {
    if (VERBOSE) {
      System.err.println(("stats for " + getClass()));
      System.err.println(("number of variables: " + delegateGraph.getVarCount()));
      System.err.println(("implicit equations: " + (implicitUnaryCount)));
      System.err.println(("explicit equations: " + delegateGraph.getEquationCount()));
      System.err.println("implicit map:");
      int count = 0;
      int totalBytes = 0;
      for (Iterator it = implicitUnaryMap.entrySet().iterator(); it.hasNext();) {
        count++;
        Map.Entry e = (Map.Entry) it.next();
        IBinaryNaturalRelation R = (IBinaryNaturalRelation) e.getValue();
        System.err.println(("entry " + count));
        R.performVerboseAction();
        HeapTracer.Result result = HeapTracer.traceHeap(Collections.singleton(R), false);
        totalBytes += result.getTotalSize();
      }
      System.err.println(("bytes in implicit map: " + totalBytes));
    }
  }

  public boolean containsStatement(IFixedPointStatement<PointsToSetVariable> eq) throws IllegalArgumentException {
    if (eq == null) {
      throw new IllegalArgumentException("eq == null");
    }
    if (useImplicitRepresentation(eq)) {
      UnaryStatement<PointsToSetVariable> ueq = (UnaryStatement<PointsToSetVariable>) eq;
      return containsImplicitStatement(ueq);
    } else {
      return delegateStatements.contains(eq);
    }
  }

  /**
   * @return true iff the graph already contains this equation
   */
  private boolean containsImplicitStatement(UnaryStatement<PointsToSetVariable> eq) {
    if (!containsVariable(eq.getLHS())) {
      return false;
    }
    if (!containsVariable(eq.getRightHandSide())) {
      return false;
    }
    int lhs = eq.getLHS().getGraphNodeId();
    int rhs = eq.getRightHandSide().getGraphNodeId();
    UnaryOperator op = eq.getOperator();
    IBinaryNaturalRelation R = implicitUnaryMap.get(op);
    if (R != null) {
      return R.contains(lhs, rhs);
    } else {
      return false;
    }
  }

  public boolean containsVariable(PointsToSetVariable v) {
    return delegateGraph.containsNode(v);
  }

  public void addStatement(IFixedPointStatement<PointsToSetVariable> statement) throws IllegalArgumentException, UnimplementedError {
    if (statement == null) {
      throw new IllegalArgumentException("statement == null");
    }
    if (statement instanceof UnaryStatement) {
      addStatement((UnaryStatement<PointsToSetVariable>) statement);
    } else if (statement instanceof GeneralStatement) {
      addStatement((GeneralStatement<PointsToSetVariable>) statement);
    } else {
      Assertions.UNREACHABLE("unexpected: " + statement.getClass());
    }
  }

  /**
   * A graph of just the variables in the system. v1 -> v2 iff there exists an assignment equation e s.t. e uses v1 and e defs v2.
   * 
   */
  public NumberedGraph<PointsToSetVariable> getAssignmentGraph() {
    return new FilteredConstraintGraphView() {

      @Override
      boolean isInteresting(AbstractStatement eq) {
        return eq instanceof AssignEquation;
      }
    };
  }

  /**
   * A graph of just the variables in the system. v1 -> v2 iff there exists an Assingnment or Filter equation e s.t. e uses v1 and e
   * defs v2.
   * 
   */
  public Graph<PointsToSetVariable> getFilterAssignmentGraph() {
    return new FilteredConstraintGraphView() {

      @Override
      boolean isInteresting(AbstractStatement eq) {
        return eq instanceof AssignEquation || eq.getOperator() instanceof PropagationCallGraphBuilder.FilterOperator;
      }
    };
  }

  /**
   * NOTE: do not use this method unless you really know what you are doing. Functionality is fragile and may not work in the
   * future.
   */
  public Graph<PointsToSetVariable> getFlowGraphIncludingImplicitConstraints() {
    return new VariableGraphView();
  }

  /**
   * A graph of just the variables in the system. v1 -> v2 that are related by def-use with "interesting" operators
   * 
   */
  private abstract class FilteredConstraintGraphView extends AbstractNumberedGraph<PointsToSetVariable> {

    abstract boolean isInteresting(AbstractStatement eq);

    /*
     * @see com.ibm.wala.util.graph.Graph#removeNodeAndEdges(java.lang.Object)
     */
    @Override
    public void removeNodeAndEdges(PointsToSetVariable N) {
      Assertions.UNREACHABLE();
    }

    /*
     * @see com.ibm.wala.util.graph.NodeManager#iterateNodes()
     */
    @Override
    public Iterator<PointsToSetVariable> iterator() {
      return getVariables();
    }

    /*
     * @see com.ibm.wala.util.graph.NodeManager#getNumberOfNodes()
     */
    @Override
    public int getNumberOfNodes() {
      return delegateGraph.getVarCount();
    }

    /*
     * @see com.ibm.wala.util.graph.NodeManager#addNode(java.lang.Object)
     */
    @Override
    public void addNode(PointsToSetVariable n) {
      Assertions.UNREACHABLE();

    }

    /*
     * @see com.ibm.wala.util.graph.NodeManager#removeNode(java.lang.Object)
     */
    @Override
    public void removeNode(PointsToSetVariable n) {
      Assertions.UNREACHABLE();

    }

    /*
     * @see com.ibm.wala.util.graph.NodeManager#containsNode(java.lang.Object)
     */
    @Override
    public boolean containsNode(PointsToSetVariable N) {
      Assertions.UNREACHABLE();
      return false;
    }

    /*
     * @see com.ibm.wala.util.graph.EdgeManager#getPredNodes(java.lang.Object)
     */
    @Override
    public Iterator<PointsToSetVariable> getPredNodes(PointsToSetVariable v) {
      final Iterator eqs = getStatementsThatDef(v);
      return new Iterator<PointsToSetVariable>() {
        PointsToSetVariable nextResult;
        {
          advance();
        }

        public boolean hasNext() {
          return nextResult != null;
        }

        public PointsToSetVariable next() {
          PointsToSetVariable result = nextResult;
          advance();
          return result;
        }

        private void advance() {
          nextResult = null;
          while (eqs.hasNext() && nextResult == null) {
            AbstractStatement eq = (AbstractStatement) eqs.next();
            if (isInteresting(eq)) {
              nextResult = (PointsToSetVariable) ((UnaryStatement) eq).getRightHandSide();
            }
          }
        }

        public void remove() {
          Assertions.UNREACHABLE();
        }
      };
    }

    /*
     * @see com.ibm.wala.util.graph.EdgeManager#getPredNodeCount(java.lang.Object)
     */
    @Override
    public int getPredNodeCount(PointsToSetVariable v) {
      int result = 0;
      for (Iterator eqs = getStatementsThatDef(v); eqs.hasNext();) {
        AbstractStatement eq = (AbstractStatement) eqs.next();
        if (isInteresting(eq)) {
          result++;
        }
      }
      return result;
    }

    /*
     * @see com.ibm.wala.util.graph.EdgeManager#getSuccNodes(java.lang.Object)
     */
    @Override
    public Iterator<PointsToSetVariable> getSuccNodes(PointsToSetVariable v) {
      final Iterator eqs = getStatementsThatUse(v);
      return new Iterator<PointsToSetVariable>() {
        PointsToSetVariable nextResult;
        {
          advance();
        }

        public boolean hasNext() {
          return nextResult != null;
        }

        public PointsToSetVariable next() {
          PointsToSetVariable result = nextResult;
          advance();
          return result;
        }

        private void advance() {
          nextResult = null;
          while (eqs.hasNext() && nextResult == null) {
            AbstractStatement eq = (AbstractStatement) eqs.next();
            if (isInteresting(eq)) {
              nextResult = (PointsToSetVariable) ((UnaryStatement) eq).getLHS();
            }
          }
        }

        public void remove() {
          // TODO Auto-generated method stub
          Assertions.UNREACHABLE();
        }
      };
    }

    /*
     * @see com.ibm.wala.util.graph.EdgeManager#getSuccNodeCount(java.lang.Object)
     */
    @Override
    public int getSuccNodeCount(PointsToSetVariable v) {
      int result = 0;
      for (Iterator eqs = getStatementsThatUse(v); eqs.hasNext();) {
        AbstractStatement eq = (AbstractStatement) eqs.next();
        if (isInteresting(eq)) {
          result++;
        }
      }
      return result;
    }

    /*
     * @see com.ibm.wala.util.graph.EdgeManager#addEdge(java.lang.Object, java.lang.Object)
     */
    @Override
    public void addEdge(PointsToSetVariable src, PointsToSetVariable dst) {
      Assertions.UNREACHABLE();
    }

    /*
     * @see com.ibm.wala.util.graph.EdgeManager#removeAllIncidentEdges(java.lang.Object)
     */
    @Override
    public void removeAllIncidentEdges(PointsToSetVariable node) {
      Assertions.UNREACHABLE();
    }

    /*
     * @see com.ibm.wala.util.graph.AbstractGraph#getNodeManager()
     */
    @Override
    @SuppressWarnings("unchecked")
    protected NumberedNodeManager getNodeManager() {
      return nodeManager;
    }

    /*
     * @see com.ibm.wala.util.graph.AbstractGraph#getEdgeManager()
     */
    @Override
    protected NumberedEdgeManager<PointsToSetVariable> getEdgeManager() {
      Assertions.UNREACHABLE();
      return null;
    }
  }

  public String spaceReport() {
    StringBuffer result = new StringBuffer("PropagationGraph\n");
    result.append("ImplicitEdges:" + countImplicitEdges() + "\n");
    // for (Iterator it = implicitUnaryMap.values().iterator(); it.hasNext(); )
    // {
    // result.append(it.next() + "\n");
    // }
    return result.toString();
  }

  private int countImplicitEdges() {
    int result = 0;
    for (Iterator it = new GlobalImplicitIterator(); it.hasNext();) {
      it.next();
      result++;
    }
    return result;
  }

}
