/*
 * 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.analysis.reflection.CloneInterpreter;
import com.ibm.wala.classLoader.CallSiteReference;
import com.ibm.wala.classLoader.IClass;
import com.ibm.wala.classLoader.IMethod;
import com.ibm.wala.classLoader.NewSiteReference;
import com.ibm.wala.fixpoint.UnaryOperator;
import com.ibm.wala.ipa.callgraph.AnalysisOptions;
import com.ibm.wala.ipa.callgraph.CGNode;
import com.ibm.wala.ipa.callgraph.ContextSelector;
import com.ibm.wala.ipa.callgraph.IAnalysisCacheView;
import com.ibm.wala.ipa.callgraph.impl.ExplicitCallGraph;
import com.ibm.wala.ipa.callgraph.impl.ExplicitCallGraph.ExplicitNode;
import com.ibm.wala.ipa.callgraph.propagation.InstanceKey;
import com.ibm.wala.ipa.callgraph.propagation.PointerKey;
import com.ibm.wala.ipa.callgraph.propagation.PointsToSetVariable;
import com.ibm.wala.ipa.callgraph.propagation.SSAContextInterpreter;
import com.ibm.wala.ipa.cha.IClassHierarchy;
import com.ibm.wala.types.Selector;
import com.ibm.wala.util.intset.IntSet;
import com.ibm.wala.util.intset.IntSetAction;
import com.ibm.wala.util.intset.IntSetUtil;
import com.ibm.wala.util.intset.MutableIntSet;

/**
 * TODO: refactor to eliminate more redundancy with SSACallGraphBuilder
 */
public class BasicRTABuilder extends AbstractRTABuilder {

  public BasicRTABuilder(IClassHierarchy cha, AnalysisOptions options, IAnalysisCacheView cache, ContextSelector contextSelector,
      SSAContextInterpreter contextInterpreter) {
    super(cha, options, cache, contextSelector, contextInterpreter);
  }

  /**
   * Perform needed bookkeeping when a new class is discovered.
   */
  @Override
  protected void updateSetsForNewClass(IClass klass, InstanceKey iKey, CGNode node, NewSiteReference n) {

    // set up the selector map to record each method that class implements
    registerImplementedMethods(klass, iKey);

    for (IClass c : klass.getAllImplementedInterfaces()) {
      registerImplementedMethods(c, iKey);
    }
    klass = klass.getSuperclass();
    while (klass != null) {
      registerImplementedMethods(klass, iKey);
      klass = klass.getSuperclass();
    }
  }

  /**
   * Record state for each method implemented by iKey.
   */
  private void registerImplementedMethods(IClass declarer, InstanceKey iKey) {
    if (DEBUG) {
      System.err.println(("registerImplementedMethods: " + declarer + ' ' + iKey));
    }
    for (IMethod M : declarer.getDeclaredMethods()) {
      Selector selector = M.getReference().getSelector();
      PointerKey sKey = getKeyForSelector(selector);
      if (DEBUG) {
        System.err.println(("Add constraint: " + selector + " U= " + iKey.getConcreteType()));
      }
      system.newConstraint(sKey, iKey);
    }
  }

  @Override
  protected PointerKey getKeyForSite(CallSiteReference site) {
    return new RTASelectorKey(site.getDeclaredTarget().getSelector());
  }

  protected RTASelectorKey getKeyForSelector(Selector selector) {
    return new RTASelectorKey(selector);
  }

  /**
   * An operator to fire when we discover a potential new callee for a virtual or interface call site.
   * 
   * This operator will create a new callee context and constraints if necessary.
   * 
   * N.B: This implementation assumes that the calling context depends solely on the dataflow information computed for the receiver.
   * TODO: generalize this to have other forms of context selection, such as CPA-style algorithms.
   */
  private final class DispatchOperator extends UnaryOperator<PointsToSetVariable> {
    private final CallSiteReference site;

    private final ExplicitCallGraph.ExplicitNode caller;

    DispatchOperator(CallSiteReference site, ExplicitNode caller) {
      this.site = site;
      this.caller = caller;
    }

    /**
     * The set of classes that have already been processed.
     */
    final private MutableIntSet previousReceivers = IntSetUtil.getDefaultIntSetFactory().make();

    @SuppressWarnings("unused")
    @Override
    public byte evaluate(PointsToSetVariable lhs, PointsToSetVariable rhs) {
      PointsToSetVariable receivers = rhs;

      // compute the set of pointers that were not previously handled
      IntSet value = receivers.getValue();
      if (value == null) {
        // this constraint was put on the work list, probably by initialization,
        // even though the right-hand-side is empty.
        // TODO: be more careful about what goes on the worklist to
        // avoid this.
        if (DEBUG) {
          System.err.println("EVAL dispatch with value null");
        }
        return NOT_CHANGED;
      }
      if (DEBUG) {
        String S = "EVAL dispatch to " + caller + ':' + site;
        System.err.println(S);
        if (DEBUG_LEVEL >= 2) {
          System.err.println(("receivers: " + value));
        }
      }
      // TODO: cache this!!!
      IClass recvClass = getClassHierarchy().lookupClass(site.getDeclaredTarget().getDeclaringClass());
      if (recvClass == null) {
        return NOT_CHANGED;
      }
      value = filterForClass(value, recvClass);
      if (DEBUG_LEVEL >= 2) {
        System.err.println(("filtered value: " + value));
      }
      IntSetAction action = ptr -> {
        if (DEBUG) {
          System.err.println(("    dispatch to ptr " + ptr));
        }
        InstanceKey iKey = system.getInstanceKey(ptr);

        CGNode target = getTargetForCall(caller, site, iKey.getConcreteType(), new InstanceKey[]{iKey});
        if (target == null) {
          // This indicates an error; I sure hope getTargetForCall
          // raised a warning about this!
          if (DEBUG) {
            System.err.println(("Warning: null target for call " + site + ' ' + iKey));
          }
          return;
        }
        if (clone2Assign) {
          if (target.getMethod().getReference().equals(CloneInterpreter.CLONE)) {
            // (mostly) ignore a call to clone: it won't affect the
            // solution, but we should probably at least have a call
            // edge
            caller.addTarget(site, target);
            return;
          }
        }

        IntSet targets = getCallGraph().getPossibleTargetNumbers(caller, site);
        if (targets != null && targets.contains(target.getGraphNodeId())) {
          // do nothing; we've previously discovered and handled this
          // receiver for this call site.
          return;
        }

        // process the newly discovered target for this call
        processResolvedCall(caller, site, target);

        if (!haveAlreadyVisited(target)) {
          markDiscovered(target);
        }
      };

      value.foreachExcluding(previousReceivers, action);

      // update the set of receivers previously considered
      previousReceivers.copySet(value);

      return NOT_CHANGED;
    }

    @Override
    public String toString() {
      return "Dispatch";
    }

    @Override
    public int hashCode() {
      return caller.hashCode() + 8707 * site.hashCode();
    }

    @Override
    public boolean equals(Object o) {
      if (o instanceof DispatchOperator) {
        DispatchOperator other = (DispatchOperator) o;
        return caller.equals(other.caller) && site.equals(other.site);
      } else {
        return false;
      }
    }
  }

  /*
   * @see
   * com.ibm.wala.ipa.callgraph.propagation.rta.AbstractRTABuilder#makeDispatchOperator(com.ibm.wala.classLoader.CallSiteReference,
   * com.ibm.wala.ipa.callgraph.CGNode)
   */
  @Override
  protected UnaryOperator<PointsToSetVariable> makeDispatchOperator(CallSiteReference site, CGNode node) {
    return new DispatchOperator(site, (ExplicitNode) node);
  }

}
