package pipe.controllers;
import com.google.common.collect.Sets;
import pipe.controllers.application.PipeApplicationController;
import pipe.historyActions.AnimationHistory;
import pipe.utilities.gui.GuiUtils;
import uk.ac.imperial.pipe.animation.Animator;
import uk.ac.imperial.pipe.models.petrinet.Transition;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.HashSet;
import java.util.Set;
/**
* This class is used to process clicks by the user to manually step
* through enabled transitions in the net.
*/
public class GUIAnimator {
/**
* Timer used for spacing between random transition firings
*/
private final Timer timer = new Timer(0, new TimedTransitionActionListener());
/**
* Petri net animator
*/
private final Animator animator;
/**
* Petri net animation history which is responsible
* for displaying the fired transitions and stepping forwards/backwards
*/
private final AnimationHistory animationHistory;
/**
* Main PIPE application controller
*/
private final PipeApplicationController applicationController;
/**
* Number of transitions fired in the current sequence
*/
private int numberSequences = 0;
/**
* Constructor
* @param animator Petri net animator
* @param animationHistory History for animation
* @param applicationController Pipe main application controller
*/
public GUIAnimator(Animator animator, AnimationHistory animationHistory,
PipeApplicationController applicationController) {
this.animator = animator;
this.animationHistory = animationHistory;
this.applicationController = applicationController;
}
/**
* Saves the current petri net token counts for restoring later.
* When exit animation mode we expect the petri net to return to
* it's original state, hence this method must be called before animating.
* <p>
* Also marks the enabled transitions in the petri net.
* </p>
*/
public void startAnimation() {
saveCurrentTokenState();
markEnabledTransitions(new HashSet<Transition>(), animator.getEnabledTransitions());
}
/**
* Saves the current tokens in places
*/
private void saveCurrentTokenState() {
animator.saveState();
}
/**
* Computes transitions which need to be disabled because they are no longer enabled and
* those that need to be enabled because they have been newly enabled.
*/
private void markEnabledTransitions(Set<Transition> previouslyEnabled, Set<Transition> enabled) {
for (Transition transition : Sets.difference(previouslyEnabled, enabled)) {
transition.disable();
}
for (Transition transition : Sets.difference(enabled, previouslyEnabled)) {
transition.enable();
}
}
/**
* Starts a random firing sequence for the specified number of transitions
*/
public void startRandomFiring() {
animationHistory.clearStepsForward();
if (getNumberSequences() > 0) {
// stop animation
setNumberSequences(0);
} else {
try {
String s = JOptionPane.showInputDialog("Enter number of firings to perform", "1");
this.numberSequences = Integer.parseInt(s);
s = JOptionPane.showInputDialog("Enter time delay between firing /ms", "50");
timer.setDelay(Integer.parseInt(s));
timer.start();
} catch (NumberFormatException e) {
GuiUtils.displayErrorMessage(null, "Error in animator: " + e.getMessage());
}
}
}
/**
*
* @return the number of transitions in the sequence
*/
public synchronized int getNumberSequences() {
return numberSequences;
}
/**
*
* @param numberSequences set the number of transitions in the sequene
*/
public synchronized void setNumberSequences(int numberSequences) {
this.numberSequences = numberSequences;
}
/**
* Randomly fires one of the enabled transitions.
*/
public void doRandomFiring() {
Transition transition = animator.getRandomEnabledTransition();
fireTransition(transition);
}
/**
* This method keeps track of a fired transition in the AnimationHistoryView
* object, enables transitions after the recent firing, and properly displays
* the transitions.
*
* @param transition to be fired
*/
public void fireTransition(Transition transition) {
Set<Transition> previouslyEnabled = animator.getEnabledTransitions();
animationHistory.clearStepsForward();
animationHistory.addHistoryItem(transition);
animator.fireTransition(transition);
Set<Transition> enabled = animator.getEnabledTransitions();
markEnabledTransitions(previouslyEnabled, enabled);
}
/**
* Steps back through previously fired transitions
*/
public void stepBack() {
if (animationHistory.isStepBackAllowed()) {
Transition transition = animationHistory.getCurrentTransition();
animationHistory.stepBackwards();
animator.fireTransitionBackwards(transition);
}
}
/**
* Steps forward through previously fired transitions
*/
public void stepForward() {
if (isStepForwardAllowed()) {
int nextPosition = animationHistory.getCurrentPosition() + 1;
Transition transition = animationHistory.getTransition(nextPosition);
animator.fireTransition(transition);
animationHistory.stepForward();
}
}
/**
*
* @return true if a step forward can happen in the animation history
*/
public boolean isStepForwardAllowed() {
return animationHistory.isStepForwardAllowed();
}
/**
*
* @return true if a step backward can happen in the animation history
*/
public boolean isStepBackAllowed() {
return animationHistory.isStepBackAllowed();
}
/**
* Finishes the animation
* Resets the petri net state to before animation
*/
public void finish() {
restoreModel();
animationHistory.clear();
}
/**
* Restores all places to their original token counts.
* Disables all transitions
*/
private void restoreModel() {
animator.reset();
for (Transition transition : animator.getEnabledTransitions()) {
transition.disable();
}
}
/**
* Listens for the timer to run down to 0 and then performs the action
*/
private class TimedTransitionActionListener implements ActionListener {
/**
* When the timer runs down to zero this will be triggered.
*
* It performs a random firing of a single transition
* @param actionEvent
*/
@Override
public void actionPerformed(ActionEvent actionEvent) {
PetriNetController controller = applicationController.getActivePetriNetController();
if (getNumberSequences() < 1 || !controller.isInAnimationMode()) {
timer.stop();
return;
}
doRandomFiring();
setNumberSequences(getNumberSequences() - 1);
}
}
}