package soot.toolkits.graph.interaction;

/*-
 * #%L
 * Soot - a J*va Optimization Framework
 * %%
 * Copyright (C) 2004 Jennifer Lhotak
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 2.1 of the
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Lesser Public License for more details.
 * 
 * You should have received a copy of the GNU General Lesser Public
 * License along with this program.  If not, see
 * <http://www.gnu.org/licenses/lgpl-2.1.html>.
 * #L%
 */

import java.util.ArrayList;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import soot.Body;
import soot.G;
import soot.PhaseOptions;
import soot.Singletons;
import soot.SootMethod;
import soot.Transform;
import soot.jimple.toolkits.annotation.callgraph.CallGraphGrapher;
import soot.options.Options;
import soot.toolkits.graph.DirectedGraph;

public class InteractionHandler {
  private static final Logger logger = LoggerFactory.getLogger(InteractionHandler.class);

  public InteractionHandler(Singletons.Global g) {
  }

  public static InteractionHandler v() {
    return G.v().soot_toolkits_graph_interaction_InteractionHandler();
  }

  private ArrayList<Object> stopUnitList;

  public ArrayList<Object> getStopUnitList() {
    return stopUnitList;
  }

  public void addToStopUnitList(Object elem) {
    if (stopUnitList == null) {
      stopUnitList = new ArrayList<Object>();
    }
    stopUnitList.add(elem);
  }

  public void removeFromStopUnitList(Object elem) {
    if (stopUnitList.contains(elem)) {
      stopUnitList.remove(elem);
    }
  }

  public void handleNewAnalysis(Transform t, Body b) {
    // here save current phase name and only send if actual data flow analysis exists
    if (PhaseOptions.getBoolean(PhaseOptions.v().getPhaseOptions(t.getPhaseName()), "enabled")) {
      String name = t.getPhaseName() + " for method: " + b.getMethod().getName();
      currentPhaseName(name);
      currentPhaseEnabled(true);
      doneCurrent(false);
    } else {
      currentPhaseEnabled(false);
      setInteractThisAnalysis(false);
    }
  }

  public void handleCfgEvent(DirectedGraph<?> g) {
    if (currentPhaseEnabled()) {
      logger.debug("Analyzing: " + currentPhaseName());
      doInteraction(new InteractionEvent(IInteractionConstants.NEW_ANALYSIS, currentPhaseName()));
    }
    if (isInteractThisAnalysis()) {
      doInteraction(new InteractionEvent(IInteractionConstants.NEW_CFG, g));
    }
  }

  public void handleStopAtNodeEvent(Object u) {
    if (isInteractThisAnalysis()) {
      doInteraction(new InteractionEvent(IInteractionConstants.STOP_AT_NODE, u));
    }
  }

  public void handleBeforeAnalysisEvent(Object beforeFlow) {
    if (isInteractThisAnalysis()) {
      if (autoCon()) {
        doInteraction(new InteractionEvent(IInteractionConstants.NEW_BEFORE_ANALYSIS_INFO_AUTO, beforeFlow));
      } else {
        doInteraction(new InteractionEvent(IInteractionConstants.NEW_BEFORE_ANALYSIS_INFO, beforeFlow));
      }
    }
  }

  public void handleAfterAnalysisEvent(Object afterFlow) {
    if (isInteractThisAnalysis()) {
      if (autoCon()) {
        doInteraction(new InteractionEvent(IInteractionConstants.NEW_AFTER_ANALYSIS_INFO_AUTO, afterFlow));
      } else {
        doInteraction(new InteractionEvent(IInteractionConstants.NEW_AFTER_ANALYSIS_INFO, afterFlow));
      }
    }
  }

  public void handleTransformDone(Transform t, Body b) {
    doneCurrent(true);
    if (isInteractThisAnalysis()) {
      doInteraction(new InteractionEvent(IInteractionConstants.DONE, null));
    }
  }

  public void handleCallGraphStart(Object info, CallGraphGrapher grapher) {
    setGrapher(grapher);
    doInteraction(new InteractionEvent(IInteractionConstants.CALL_GRAPH_START, info));
    if (!isCgReset()) {
      handleCallGraphNextMethod();
    } else {
      setCgReset(false);
      handleReset();
    }
  }

  public void handleCallGraphNextMethod() {
    if (!cgDone()) {
      getGrapher().setNextMethod(getNextMethod());
      getGrapher().handleNextMethod();
    }
  }

  private boolean cgReset = false;

  public void setCgReset(boolean v) {
    cgReset = v;
  }

  public boolean isCgReset() {
    return cgReset;
  }

  public void handleReset() {
    if (!cgDone()) {
      getGrapher().reset();
    }
  }

  public void handleCallGraphPart(Object info) {
    doInteraction(new InteractionEvent(IInteractionConstants.CALL_GRAPH_PART, info));
    if (!isCgReset()) {
      handleCallGraphNextMethod();
    } else {
      setCgReset(false);
      handleReset();
    }
  }

  private CallGraphGrapher grapher;

  private void setGrapher(CallGraphGrapher g) {
    grapher = g;
  }

  private CallGraphGrapher getGrapher() {
    return grapher;
  }

  private SootMethod nextMethod;

  public void setNextMethod(SootMethod m) {
    nextMethod = m;
  }

  private SootMethod getNextMethod() {
    return nextMethod;
  }

  private synchronized void doInteraction(InteractionEvent event) {
    getInteractionListener().setEvent(event);
    getInteractionListener().handleEvent();

  }

  public synchronized void waitForContinue() {
    try {
      this.wait();
    } catch (InterruptedException e) {
      logger.debug("" + e.getMessage());
    }

  }

  private boolean interactThisAnalysis;

  public void setInteractThisAnalysis(boolean b) {
    interactThisAnalysis = b;
  }

  public boolean isInteractThisAnalysis() {
    return interactThisAnalysis;
  }

  private boolean interactionCon;

  public synchronized void setInteractionCon() {
    this.notify();
  }

  public boolean isInteractionCon() {
    return interactionCon;
  }

  private IInteractionListener interactionListener;

  public void setInteractionListener(IInteractionListener listener) {
    interactionListener = listener;
  }

  public IInteractionListener getInteractionListener() {
    return interactionListener;
  }

  private String currentPhaseName;

  public void currentPhaseName(String name) {
    currentPhaseName = name;
  }

  public String currentPhaseName() {
    return currentPhaseName;
  }

  private boolean currentPhaseEnabled;

  public void currentPhaseEnabled(boolean b) {
    currentPhaseEnabled = b;
  }

  public boolean currentPhaseEnabled() {
    return currentPhaseEnabled;
  }

  private boolean cgDone = false;

  public void cgDone(boolean b) {
    cgDone = b;
  }

  public boolean cgDone() {
    return cgDone;
  }

  private boolean doneCurrent;

  public void doneCurrent(boolean b) {
    doneCurrent = b;
  }

  public boolean doneCurrent() {
    return doneCurrent;
  }

  private boolean autoCon;

  public void autoCon(boolean b) {
    autoCon = b;
  }

  public boolean autoCon() {
    return autoCon;
  }

  public void stopInteraction(boolean b) {
    Options.v().set_interactive_mode(false);
  }

}
