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

import com.ibm.wala.classLoader.CallSiteReference;
import com.ibm.wala.classLoader.IMethod;
import com.ibm.wala.ipa.callgraph.AnalysisOptions;
import com.ibm.wala.ipa.callgraph.CGNode;
import com.ibm.wala.ipa.callgraph.Context;
import com.ibm.wala.ipa.callgraph.IAnalysisCacheView;
import com.ibm.wala.ipa.callgraph.impl.ExplicitCallGraph;
import com.ibm.wala.util.debug.Assertions;
import com.ibm.wala.util.intset.BasicNaturalRelation;
import com.ibm.wala.util.intset.BitVectorIntSet;
import com.ibm.wala.util.intset.IBinaryNaturalRelation;
import com.ibm.wala.util.intset.IntIterator;
import com.ibm.wala.util.intset.IntSet;
import com.ibm.wala.util.intset.MutableSharedBitVectorIntSet;
import com.ibm.wala.util.intset.MutableSparseIntSet;
import java.util.Set;

/**
 * A call graph implementation where some edges are delegated to other call sites, since they are
 * guaranteed to be the same.
 */
public class DelegatingExplicitCallGraph extends ExplicitCallGraph {

  /** delegateR(x,y) means that for at least one site, node number y delegates to node number x. */
  private final IBinaryNaturalRelation delegateR = new BasicNaturalRelation();

  public DelegatingExplicitCallGraph(
      IMethod fakeRootClass, AnalysisOptions options, IAnalysisCacheView cache) {
    super(fakeRootClass, options, cache);
  }

  /**
   * In this implementation, super.targets is a mapping from call site -&gt; Object, where Object is
   * a
   *
   * <ul>
   *   <li>CGNode if we've discovered exactly one target for the site
   *   <li>or an IntSet of node numbers if we've discovered more than one target for the site.
   *   <li>a CallSite if we're delegating these edges to another node
   * </ul>
   */
  public class DelegatingCGNode extends ExplicitNode {

    protected DelegatingCGNode(IMethod method, Context C) {
      super(method, C);
    }

    @Override
    public MutableSharedBitVectorIntSet getAllTargetNumbers() {
      MutableSharedBitVectorIntSet result =
          new MutableSharedBitVectorIntSet(super.getAllTargetNumbers());
      for (Object n : targets) {
        if (n instanceof CallSite) {
          ExplicitNode delegate = (ExplicitNode) ((CallSite) n).getNode();
          IntSet s =
              DelegatingExplicitCallGraph.this.getPossibleTargetNumbers(
                  delegate, ((CallSite) n).getSite());
          if (s != null) {
            result.addAll(s);
          }
        }
      }
      return result;
    }

    @Override
    public Set<CGNode> getPossibleTargets(CallSiteReference site) {
      Object result = targets.get(site.getProgramCounter());
      if (result != null && result instanceof CallSite) {
        CallSite p = (CallSite) result;
        CGNode n = p.getNode();
        CallSiteReference s = p.getSite();
        return DelegatingExplicitCallGraph.this.getPossibleTargets(n, s);
      } else {
        return super.getPossibleTargets(site);
      }
    }

    @Override
    public IntSet getPossibleTargetNumbers(CallSiteReference site) {
      Object t = targets.get(site.getProgramCounter());
      if (t != null && t instanceof CallSite) {
        CallSite p = (CallSite) t;
        DelegatingCGNode n = (DelegatingCGNode) p.getNode();
        CallSiteReference s = p.getSite();
        return n.getPossibleTargetNumbers(s);
      } else {
        return super.getPossibleTargetNumbers(site);
      }
    }

    private boolean hasTarget(int y) {
      if (super.getAllTargetNumbers().contains(y)) {
        return true;
      } else {
        for (Object n : targets) {
          if (n instanceof CallSite) {
            ExplicitNode delegate = (ExplicitNode) ((CallSite) n).getNode();
            IntSet s =
                DelegatingExplicitCallGraph.this.getPossibleTargetNumbers(
                    delegate, ((CallSite) n).getSite());
            if (s != null && s.contains(y)) {
              return true;
            }
          }
        }
      }
      return false;
    }

    @Override
    public int getNumberOfTargets(CallSiteReference site) {
      Object result = targets.get(site.getProgramCounter());
      if (result != null && result instanceof CallSite) {
        CallSite p = (CallSite) result;
        CGNode n = p.getNode();
        CallSiteReference s = p.getSite();
        return DelegatingExplicitCallGraph.this.getNumberOfTargets(n, s);
      } else {
        return super.getNumberOfTargets(site);
      }
    }

    public void delegate(
        CallSiteReference site, CGNode delegateNode, CallSiteReference delegateSite) {
      CallSite d = new CallSite(delegateSite, delegateNode);
      targets.set(site.getProgramCounter(), d);
      int y = getCallGraph().getNumber(this);
      int x = getCallGraph().getNumber(delegateNode);
      delegateR.add(x, y);
    }
  }

  @Override
  protected ExplicitNode makeNode(IMethod method, Context context) {
    return new DelegatingCGNode(method, context);
  }

  private class DelegatingEdgeManager extends ExplicitEdgeManager {

    @Override
    public void addEdge(CGNode src, CGNode dst) {
      // we assume that this is called from ExplicitNode.addTarget().
      // so we only have to track the inverse edge.
      // this structure is pretty fragile .. we only explicitly represent the
      // edges when NOT delegating.
      // see getPredNodeNumbers() below which recovers.
      super.addEdge(src, dst);
    }

    @Override
    public void removeAllIncidentEdges(CGNode node) {
      Assertions.UNREACHABLE();
    }

    @Override
    public void removeIncomingEdges(CGNode node) {
      Assertions.UNREACHABLE();
    }

    @Override
    public void removeOutgoingEdges(CGNode node) {
      Assertions.UNREACHABLE();
    }

    @Override
    public boolean hasEdge(CGNode src, CGNode dst) {
      if (super.hasEdge(src, dst)) {
        return true;
      } else {
        DelegatingCGNode s = (DelegatingCGNode) src;
        int y = getNumber(dst);
        return s.hasTarget(y);
      }
    }

    @Override
    public int getPredNodeCount(CGNode N) {
      IntSet s = getPredNodeNumbers(N);
      return s == null ? 0 : s.size();
    }

    @Override
    public IntSet getPredNodeNumbers(CGNode node) {
      IntSet superR = super.getPredNodeNumbers(node);
      if (superR == null) {
        return null;
      } else {
        MutableSparseIntSet result = MutableSparseIntSet.make(superR);
        BitVectorIntSet allPossiblePreds = new BitVectorIntSet(superR);
        for (IntIterator it = superR.intIterator(); it.hasNext(); ) {
          int x = it.next();
          IntSet ySet = delegateR.getRelated(x);
          if (ySet != null) {
            allPossiblePreds.addAll(ySet);
          }
        }
        for (IntIterator it = allPossiblePreds.intIterator(); it.hasNext(); ) {
          int y = it.next();
          DelegatingCGNode yNode = (DelegatingCGNode) getNode(y);
          if (hasEdge(yNode, node)) {
            result.add(y);
          }
        }
        return result;
      }
    }
  }

  @Override
  protected ExplicitEdgeManager makeEdgeManger() {
    return new DelegatingEdgeManager();
  }
}
