package nl.utwente.viskell.ui.components;
import com.google.common.collect.ImmutableList;
import javafx.fxml.FXML;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import nl.utwente.viskell.haskell.expr.*;
import nl.utwente.viskell.haskell.expr.Case.Alternative;
import nl.utwente.viskell.haskell.type.*;
import nl.utwente.viskell.ui.BlockContainer;
import nl.utwente.viskell.ui.ToplevelPane;
import java.util.*;
import java.util.stream.Collectors;
/**
* An evaluation block with multiple guarded alternatives.
*
*/
public class ChoiceBlock extends Block {
/** The alternatives inside this block */
protected List<Lane> lanes;
/** The output anchor of this block */
protected OutputAnchor output;
/** The container Node for the Lanes */
@FXML protected Pane altSpace;
/** The label with the result type of this choiceblock. */
@FXML private Label signature;
/** The container Node for the OutputAnchor */
@FXML protected Pane resultSpace;
/** The input space of for this choiceblock */
@FXML protected Pane inputSpace;
/** The list of input anchors of this choiceblock */
protected List<ChoiceInputAnchor> inputAnchors;
public ChoiceBlock(ToplevelPane pane) {
super(pane);
this.loadFXML("ChoiceBlock");
lanes = new ArrayList<>();
output = new OutputAnchor(this, new Binder("choiceoutput"));
inputAnchors = new ArrayList<>();
resultSpace.getChildren().add(output);
dragContext.setGoToForegroundOnContact(false);
inputSpace.setMinHeight(BASELINE_OFFSET_SAME_AS_HEIGHT);
addLane();
addLane();
}
@SuppressWarnings("UnusedParameters")
public static ChoiceBlock fromBundleFragment(ToplevelPane pane, Map<String, Object> bundleFragment) {
return new ChoiceBlock(pane);
}
@Override
public List<InputAnchor> getAllInputs() {
return inputAnchors.stream().map(i -> i.anchor).collect(Collectors.toList());
}
@Override
public List<OutputAnchor> getAllOutputs() {
return Collections.singletonList(output);
}
@Override
public List<ConnectionAnchor> getAllAnchors() {
List<ConnectionAnchor> result = new ArrayList<>();
result.add(this.output);
for (Lane lane : this.lanes) {
result.addAll(lane.getAllAnchors());
}
return result;
}
@Override
public Optional<Block> getNewCopy() {
// copying the internal is too complex for now
return Optional.empty();
}
@Override
protected void refreshAnchorTypes() {
lanes.stream().forEach(lane -> lane.refreshAnchorTypes());
// TODO make sure the last edited lane gets unified last to prevent
// that large parts of a program become invalid in case of a type error,
// but rather only the lane in which the edit took place.
List<TypeVar> typeList = new ArrayList<>();
TypeVar resultType = TypeScope.unique("choice_res");
output.setExactRequiredType(resultType);
for (int i = 0; i < inputAnchors.size(); ++i) {
TypeVar argType = TypeScope.unique("choice_arg"+i);
typeList.add(argType);
inputAnchors.get(i).anchor.setExactRequiredType(argType);
}
for (Lane lane : lanes) {
for (int i = 0; i < typeList.size(); ++i) {
try {
TypeChecker.unify("choice block", typeList.get(i), lane.arguments.get(i).getType(Optional.empty()));
}
catch (HaskellTypeError e) {
}
}
try {
TypeChecker.unify("choice block", resultType, lane.getOutput().getType());
} catch (HaskellTypeError e) {
}
}
}
public void handleConnectionChanges(boolean finalPhase) {
lanes.forEach(lane -> lane.handleConnectionChanges(finalPhase));
// continue as normal with propagating changes on the outside
super.handleConnectionChanges(finalPhase);
}
@Override
public Expression getLocalExpr(Set<OutputAnchor> outsideAnchors) {
List<Alternative> bindings = lanes.stream().map(lane -> lane.getAlternative(outsideAnchors)).collect(Collectors.toList());
LetExpression let = new LetExpression(new Case(new Value(Type.tupleOf(), "()"), bindings), false);
lanes.forEach(lane -> {
for (int i = 0; i < getAllInputs().size(); ++i) {
int j = i;
getAllInputs().get(i).getOppositeAnchor().ifPresent(anchor -> let.addLetBinding(lane.arguments.get(j).binder, new LocalVar(anchor.binder)));
}
});
getAllInputs().forEach(in -> in.getOppositeAnchor().ifPresent(out -> outsideAnchors.add(out)));
return let;
}
@Override
protected void extendExprGraph(LetExpression exprGraph, BlockContainer container, Set<OutputAnchor> outsideAnchors) {
return; // FIXME this override is only because Choice block return internal anchors in getAllInputs()
}
@Override
public void invalidateVisualState() {
lanes.forEach(Lane::invalidateVisualState);
inputAnchors.forEach(ChoiceInputAnchor::invalidateVisualState);
signature.setText(output.getStringType());
output.invalidateVisualState();
}
@Override
public boolean belongsOnBottom() {
return true;
}
/** Adds an alternative to this block */
public void addLane() {
Lane lane = new Lane(this);
lanes.add(lane);
getAllInputs().forEach(anchor -> lane.addExtraInput());
altSpace.getChildren().add(lane);
initiateConnectionChanges();
}
/** Removes the last alternative from this block */
public void removeLastLane() {
if (lanes.size() > 1) {
Lane lane = lanes.remove(lanes.size()-1);
lane.deleteAllLinks();
altSpace.getChildren().remove(lane);
initiateConnectionChanges();
}
}
/** Returns the alternatives in this block */
public List<Lane> getLanes() {
return lanes;
}
/** Adds extra input anchor to this block */
public void addExtraInput() {
ChoiceInputAnchor arg = new ChoiceInputAnchor();
inputAnchors.add(arg);
inputSpace.getChildren().add(arg);
lanes.forEach(Lane::addExtraInput);
inputSpace.setMinHeight(35);
initiateConnectionChanges();
}
/** Removes the last input anchor of this block */
public void removeLastInput() {
if (!inputAnchors.isEmpty()) {
ChoiceInputAnchor arg = inputAnchors.remove(inputAnchors.size()-1);
arg.anchor.removeConnections();
inputSpace.getChildren().remove(arg);
lanes.forEach(Lane::removeLastInput);
initiateConnectionChanges();
}
if (inputAnchors.isEmpty()) {
inputSpace.setMinHeight(BASELINE_OFFSET_SAME_AS_HEIGHT);
}
}
@Override
public List<Lane> getInternalContainers() {
return ImmutableList.copyOf(this.lanes);
}
@Override
public void relocate(double x, double y) {
double dx = x-getLayoutX(), dy = y-getLayoutY();
super.relocate(x, y);
lanes.forEach(lane -> lane.moveNodes(dx, dy));
}
protected void shiftAllBut(double shiftX, double shiftY, Lane changedLane, double shiftXForRights) {
super.relocate(this.getLayoutX() + shiftX, this.getLayoutY() + shiftY);
int lx = this.lanes.indexOf(changedLane);
for (int i = 0; i < this.lanes.size(); i++) {
if (i < lx) {
this.lanes.get(i).moveNodes(shiftX, 0);
} else if (i > lx) {
this.lanes.get(i).moveNodes(shiftXForRights, 0);
}
}
}
@Override
public void deleteAllLinks() {
lanes.forEach(lane -> lane.deleteAllLinks());
super.deleteAllLinks();
}
@Override
public boolean canAlterAnchors() {
return true;
}
@Override
public void alterAnchorCount(boolean isRemove) {
if (isRemove) {
removeLastInput();
} else {
addExtraInput();
}
}
/** Combined input anchor and type label. */
private class ChoiceInputAnchor extends VBox implements ConnectionAnchor.Target {
/** The connection anchor for this input argument. */
private final InputAnchor anchor;
/** The input type label of this anchor. */
private final Label inputType;
private ChoiceInputAnchor() {
this.inputType = new Label(".....");
this.inputType.setMinWidth(USE_PREF_SIZE);
this.inputType.getStyleClass().add("inputType");
this.inputType.setAlignment(Pos.CENTER);
this.anchor = new InputAnchor(ChoiceBlock.this);
this.anchor.setAlignment(Pos.CENTER);
this.getChildren().addAll(this.anchor, this.inputType);
this.setTranslateY(-18);
this.setPickOnBounds(false);
this.setAlignment(Pos.CENTER);
}
@Override
public ConnectionAnchor getAssociatedAnchor() {
return this.anchor;
}
/** Refresh visual information such as types */
private void invalidateVisualState() {
this.anchor.invalidateVisualState();
boolean validConnection = this.anchor.hasValidConnection();
this.setTranslateY(validConnection ? -9 : -18);
this.inputType.setText(validConnection ? "zyxwv" : this.anchor.getStringType());
this.inputType.setVisible(!validConnection);
}
}
}