/**
   * For a concrete method (declared in an application class), find statements containing an invoke
   * expression for which the method is one of the method in 'allEventInformation' (i.e.,
   * getLine1Number(), ...).
   *
   * @param cfg
   */
  private void doAccessControlChecks(BiDiInterproceduralCFG<Unit, SootMethod> cfg) {
    for (SootClass sc : Scene.v().getApplicationClasses()) {
      for (SootMethod sm : sc.getMethods()) {
        if (sm.isConcrete()) {
          Body body = sm.retrieveActiveBody();
          // only instrument application methods (i.e., not methods declared in PEP helper classes
          // or in a Java library classes or in an Android classes, ...)
          if (isInstrumentationNecessary(sm)) {

            // important to use snapshotIterator here
            Iterator<Unit> i = body.getUnits().snapshotIterator();
            log.debug("method: " + sm);
            while (i.hasNext()) {
              Stmt s = (Stmt) i.next();

              if (s.containsInvokeExpr()) {
                InvokeExpr invExpr = s.getInvokeExpr();
                String methodSignature = invExpr.getMethod().getSignature();

                if (allEventInformation.containsKey(methodSignature)) {
                  log.debug("statement " + s + " matches " + methodSignature + ".");
                  ResultSinkInfo sink = null;

                  outer:
                  for (ResultSinkInfo key : results.getResults().keySet()) {

                    // iterate over all the arguments of the invoke expression
                    // and check if an argument is a tainted sink. If one is
                    // set variable 'sink' to the ResultSinkInfo key.
                    for (Value v : invExpr.getArgs()) {
                      Value pathValue = key.getAccessPath().getPlainValue();
                      if (v == pathValue) {
                        sink = key;
                        log.debug("found a sink: " + pathValue);
                        break outer;
                      }
                    }
                  }

                  if (sink != null) {
                    log.debug("instrument with data flow information )" + s + ")");
                    instrumentSourceToSinkConnections(cfg, sink, s instanceof AssignStmt);
                    instrumentWithNoDataFlowInformation(
                        methodSignature, s, invExpr, body, s instanceof AssignStmt);
                  } else {
                    log.debug("instrument without data flow information (" + s + ")");
                    instrumentWithNoDataFlowInformation(
                        methodSignature, s, invExpr, body, s instanceof AssignStmt);
                  }
                }
              } // if stmt containts invoke expression
            } // loop on statements
          }
        }
      }
    }
  }
  @Override
  public void onResultsAvailable(IInfoflowCFG cfg, InfoflowResults results) {
    log.info(
        "FlowDroid has finished. Duration: "
            + (System.currentTimeMillis() - Main.startTime)
            + " ms.");
    Main.startTime = System.currentTimeMillis();
    Settings.instance.setDummyMainToLibraryClass();
    this.results = results;

    if (log.isDebugEnabled()) {
      log.debug("");
      log.debug("InfoFlow Results");
      MultiMap<ResultSinkInfo, ResultSourceInfo> r = results.getResults();
      for (ResultSinkInfo k : r.keySet()) {
        log.debug("ResultSinkInfo: " + k);

        for (ResultSourceInfo rsi : r.get(k)) {
          log.debug("  source: " + rsi);
        }
      }
      log.debug("");
    }

    log.info("Starting bytecode instrumentation.");

    log.info("Adding code to initialize PEPs.");
    Util.initializePePInAllPossibleClasses(Settings.instance.getApkPath());

    log.info("Redirect main activity");
    String mainActivityClass =
        UpdateManifestAndCodeForWaitPDP.redirectMainActivity(Settings.instance.getApkPath());
    UpdateManifestAndCodeForWaitPDP.updateWaitPDPActivity(mainActivityClass);

    log.info("Adding Policy Enforcement Points (PEPs).");
    doAccessControlChecks(cfg);

    log.info("Instrumentation is done.");

    if (Settings.mustOutputJimple()) {
      log.info("-------- Dumping Jimple bodies.");
      Main.dumpJimple();
      log.info("--------");
    }
  }
  /**
   * @param cfg
   * @param sink
   * @param assignmentStatement
   */
  private void instrumentSourceToSinkConnections(
      BiDiInterproceduralCFG<Unit, SootMethod> cfg,
      ResultSinkInfo sink,
      boolean assignmentStatement) {
    sourceSinkConnectionCounter += 1;

    // loop through the sinks
    for (ResultSinkInfo key : results.getResults().keySet()) {

      log.debug("compare: " + key);
      log.debug("     to: " + sink);

      // if the current sink is the sink at the unit tagged with 'sink'
      if (key.equals(sink)) {

        // loop through the sources
        for (ResultSourceInfo si : results.getResults().get(key)) {

          Stmt stmt = si.getSource();
          SootMethod sm = cfg.getMethodOf(stmt);
          Body body = sm.retrieveActiveBody();

          // Source instrumentation. The three type categories for the source are:
          // - callback
          // - ICC source method (i.e., Intent.getExtras())
          // - not a callback and not an ICC source method (i.e., getLine1Number())
          //
          if (isInterComponentSourceCallback(si, cfg)) {
            throw new RuntimeException("Callbacks as sources are not supported right now");
          } else if (isInterComponentSourceNoCallback(si, cfg)) {
            // only invoke expression are treated here
            if (stmt.containsInvokeExpr()) {
              // only statements that return a android.os.Bundle are currently supported
              if (stmt instanceof DefinitionStmt) {
                DefinitionStmt defStmt = (DefinitionStmt) stmt;
                Value leftValue = defStmt.getLeftOp();

                if (leftValue.getType().equals(RefType.v("android.os.Bundle"))) {
                  List<Object> args = new ArrayList<Object>();
                  args.add(IntType.v());
                  args.add(IntConstant.v(sourceSinkConnectionCounter));
                  args.add(RefType.v("android.os.Bundle"));
                  args.add(leftValue);
                  InvokeExpr invExpr =
                      Instrumentation.createJimpleStaticInvokeExpr(
                          Settings.INSTRUMENTATION_HELPER_JAVA,
                          "registerNewSourceSinkConnection",
                          args);
                  InvokeStmt invStmt = Jimple.v().newInvokeStmt(invExpr);

                  Unit instrumentationPoint = null;
                  if (stmt instanceof IdentityStmt) {
                    instrumentationPoint = getLastIdentityStmt(body);
                  } else {
                    instrumentationPoint = stmt;
                  }
                  body.getUnits().insertAfter(invStmt, instrumentationPoint);
                  log.debug("insert a: " + invStmt);
                } else {
                  System.err.println("We do only support android.os.Bundle right now!");
                }
              }
            }
          } else {

            String sourceCat = getSourceCategory(si);
            if (sourceCat != null) {
              List<Object> args = new ArrayList<Object>();
              args.add(IntType.v());
              args.add(IntConstant.v(sourceSinkConnectionCounter));
              args.add(RefType.v("java.lang.String"));
              args.add(StringConstant.v(sourceCat));
              InvokeExpr invExpr =
                  Instrumentation.createJimpleStaticInvokeExpr(
                      Settings.INSTRUMENTATION_HELPER_JAVA,
                      "registerNewSourceSinkConnection",
                      args);
              InvokeStmt invStmt = Jimple.v().newInvokeStmt(invExpr);

              Unit instrumentationPoint = null;
              if (stmt instanceof IdentityStmt) instrumentationPoint = getLastIdentityStmt(body);
              else instrumentationPoint = stmt;
              body.getUnits().insertAfter(invStmt, instrumentationPoint);
              log.debug("insert b: " + invStmt);
            }
          }

          // sink instrumentation
          if (sink.getSink().containsInvokeExpr()) {
            Body bodyOfSink = cfg.getMethodOf(key.getSink()).getActiveBody();
            InvokeExpr invExpr = sink.getSink().getInvokeExpr();
            List<Unit> generated = new ArrayList<Unit>();
            generated.addAll(
                instrumentIntentAddings(cfg, stmt, invExpr, results.getResults().get(key)));

            EventInformation sinkEventInfo =
                allEventInformation.get(invExpr.getMethod().getSignature());
            EventInformation sourceEventInfo =
                allEventInformation.get(si.getSource().getInvokeExpr().getMethod().getSignature());

            generated.addAll(
                generatePolicyEnforcementPoint(
                    key.getSink(),
                    invExpr,
                    bodyOfSink,
                    sourceSinkConnectionCounter,
                    assignmentStatement));

            log.debug("body with data flow:\n" + body);
            for (Unit u : generated) {
              log.debug("gen: " + u);
            }

            if (sinkEventInfo.isInstrumentAfterStatement())
              bodyOfSink.getUnits().insertAfter(generated, key.getSink());
            else bodyOfSink.getUnits().insertBefore(generated, key.getSink());
          } else throw new RuntimeException("Double-Check the assumption");
        } // loop through the sources
      } // if the sink at the unit is the current sink
    } // loop through the sinks
  }