package pipe.controllers;
import pipe.historyActions.MultipleEdit;
import pipe.historyActions.arc.AddArcPathPoint;
import pipe.historyActions.arc.ArcPathPointType;
import pipe.historyActions.arc.DeleteArcPathPoint;
import pipe.historyActions.arc.SetArcWeightAction;
import uk.ac.imperial.pipe.models.petrinet.Connectable;
import uk.ac.imperial.pipe.models.petrinet.Arc;
import uk.ac.imperial.pipe.models.petrinet.ArcPoint;
import uk.ac.imperial.pipe.parsers.FunctionalResults;
import uk.ac.imperial.pipe.parsers.UnparsableException;
import javax.swing.event.UndoableEditListener;
import javax.swing.undo.UndoableEdit;
import java.awt.geom.Point2D;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* Controller for the underlying arc model
* @param <S> source model
* @param <T> target model
*/
public class ArcController<S extends Connectable, T extends Connectable> extends AbstractPetriNetComponentController<Arc<S, T>>
{
/**
* Underlying model
*/
private final Arc<S, T> arc;
/**
* PetriNetController in order to determine if arc expressions are valid
*/
//TODO: I cant at the moment think of a better way to do this since the arc model
// does not know anything about the petri net in which it resides
private final PetriNetController petriNetController;
/**
* Constructor
* @param arc underlying model
* @param petriNetController Petri net controller for the Petri net the arc is housed in
* @param listener undo event listener
*/
ArcController(Arc<S, T> arc, PetriNetController petriNetController, UndoableEditListener listener) {
super(arc, listener);
this.arc = arc;
this.petriNetController = petriNetController;
}
/**
* Sets the weight for the current arc
* @param token token id
* @param expr weight for the associated token on the underlying arc
* @throws UnparsableException if the weight could not be parsed or is not an integer
*/
public void setWeight(String token, String expr) throws UnparsableException {
throwExceptionIfWeightNotValid(expr);
registerUndoableEdit(updateWeightForArc(token, expr));
}
/**
*
* @param expr weight expression
* @throws UnparsableException if the weight could not be parsed or is not an integer
*/
private void throwExceptionIfWeightNotValid(String expr) throws UnparsableException {
FunctionalResults<Double> result = petriNetController.parseFunctionalExpression(expr);
if (result.hasErrors()) {
StringBuilder errorMessage = new StringBuilder();
for (String error : result.getErrors()) {
errorMessage.append(error).append("\n");
}
throw new UnparsableException(errorMessage.toString());
} else if (valueIsNotInteger(result.getResult())) {
throw new UnparsableException("Value is not an integer, please surround expression with floor or ceil");
}
}
/**
*
* @param value to evaluate
* @return true if the value was an integer
*/
private boolean valueIsNotInteger(double value) {
return value % 1 != 0;
}
/**
* Loop through the weights and throw an exception if its not valid
* @param weights
* @throws UnparsableException if the weight could not be parsed or is not an integer
*/
private void throwExceptionIfWeightsNotValid(Map<String, String> weights) throws UnparsableException {
for (Map.Entry<String, String> entry : weights.entrySet()) {
throwExceptionIfWeightNotValid(entry.getValue());
}
}
/**
* Creates a historyItem for updating weight and applies it
* @param token token id to associate the expression with
* @param expr new weight expression for the arc
* @return the UndoableEdit associated with this action
*/
private UndoableEdit updateWeightForArc(String token,
String expr) {
String oldWeight = arc.getWeightForToken(token);
arc.setWeight(token, expr);
return new SetArcWeightAction<>(arc, token, oldWeight, expr);
}
/**
* Set the new token weights
* @param newWeights map token id -> functional expression
* @throws UnparsableException if the weight could not be parsed or is not an integer
*/
public void setWeights(Map<String, String> newWeights) throws UnparsableException {
throwExceptionIfWeightsNotValid(newWeights);
List<UndoableEdit> edits = new LinkedList<>();
for (Map.Entry<String, String> entry : newWeights.entrySet()) {
edits.add(updateWeightForArc(entry.getKey(), entry.getValue()));
}
registerUndoableEdit(new MultipleEdit(edits));
}
/**
*
* @param token to evaluate
* @return functional expression for the token
*/
public String getWeightForToken(String token) {
return arc.getWeightForToken(token);
}
/**
*
* @return target model
*/
public Connectable getTarget() {
return arc.getTarget();
}
/**
* Toggle arc point between straight and curved
* @param arcPoint to be toggled
*/
public void toggleArcPointType(ArcPoint arcPoint) {
arcPoint.setCurved(!arcPoint.isCurved());
registerUndoableEdit(new ArcPathPointType(arcPoint));
}
/**
* Add another arc point between this point and the next
* @param arcPoint at which to split
*/
public void splitArcPoint(ArcPoint arcPoint) {
ArcPoint nextPoint = arc.getNextPoint(arcPoint);
double x = (arcPoint.getPoint().getX() + nextPoint.getPoint().getX())/2;
double y = (arcPoint.getPoint().getY() + nextPoint.getPoint().getY())/2;
Point2D point = new Point2D.Double(x,y);
ArcPoint newPoint = new ArcPoint(point, arcPoint.isCurved());
arc.addIntermediatePoint(newPoint);
UndoableEdit splitEdit = new AddArcPathPoint<>(arc, newPoint);
registerUndoableEdit(splitEdit);
}
/**
* Add an arc point at this location
* @param point to add
*/
public void addPoint(Point2D point) {
ArcPoint newPoint = new ArcPoint(point, false);
arc.addIntermediatePoint(newPoint);
registerUndoableEdit(new AddArcPathPoint<>(arc, newPoint));
}
/**
* Delete this arc point
* @param point to delete
*/
public void deletePoint(ArcPoint point) {
arc.removeIntermediatePoint(point);
registerUndoableEdit(new DeleteArcPathPoint<>(arc, point));
}
}