package nl.utwente.viskell.ui.components;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import javafx.beans.binding.Bindings;
import javafx.fxml.FXML;
import javafx.geometry.BoundingBox;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.layout.*;
import nl.utwente.viskell.haskell.env.FunctionInfo;
import nl.utwente.viskell.haskell.expr.*;
import nl.utwente.viskell.haskell.type.FunType;
import nl.utwente.viskell.haskell.type.Type;
import nl.utwente.viskell.haskell.type.TypeScope;
import nl.utwente.viskell.ui.DragContext;
import nl.utwente.viskell.ui.ToplevelPane;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
public class BinOpApplyBlock extends Block {
/** Function input anchor that can be dragged down for currying. */
private class FunInputAnchor extends Pane 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;
/** Label with function arrow when in curried state. */
private final Label curryArrow;
/** The draggable pane combining both type label and curry arrow */
private final Pane typePane;
/** Whether this input argument is in curried state. */
private boolean curried;
/** The drag handler for this. */
private final DragContext dragContext;
public FunInputAnchor() {
this.curried = false;
this.inputType = new Label(".....");
this.inputType.setMinWidth(USE_PREF_SIZE);
this.inputType.getStyleClass().add("inputType");
this.curryArrow = new Label("->");
this.curryArrow.setMinWidth(USE_PREF_SIZE);
this.curryArrow.getStyleClass().add("curryArrow");
this.curryArrow.setVisible(false);
this.curryArrow.setMouseTransparent(true);
this.typePane = new HBox(this.inputType, this.curryArrow) {
@Override
public void relocate(double x, double y) {
super.relocate(x, y);
FunInputAnchor.this.dragShift(y);
}
};
this.typePane.setMinWidth(USE_PREF_SIZE);
this.typePane.setPickOnBounds(false);
this.anchor = new InputAnchor(BinOpApplyBlock.this);
this.anchor.layoutXProperty().bind(this.inputType.widthProperty().divide(2));
this.getChildren().addAll(this.anchor, this.typePane);
this.setTranslateY(-9);
this.setPickOnBounds(false);
dragContext = new DragContext(this.typePane);
dragContext.setDragInitAction(c -> {this.curried = false;});
dragContext.setDragFinishAction(c -> {
double height = this.inputType.getHeight();
boolean mostlyDown = (this.typePane.getLayoutY() > height*1.5) && !this.anchor.hasConnection();
double newY = mostlyDown ? 2.5*height : 0;
this.typePane.relocate(0, newY);
this.curried = mostlyDown;
BinOpApplyBlock.this.dragShiftOuput(newY);
BinOpApplyBlock.this.initiateConnectionChanges();
if (this.curried && this.anchor.getWireInProgress() != null) {
this.anchor.getWireInProgress().remove();
}
});
}
@Override
public ConnectionAnchor getAssociatedAnchor() {
return this.anchor;
}
@Override
public double computePrefHeight(double width) {
double height = this.inputType.prefHeight(width);
this.dragContext.setDragLimits(new BoundingBox(0, 0, 0, height*2.5));
return height;
}
/** Shift related things down to some distance. */
private void dragShift(double y) {
double height = this.inputType.getHeight();
this.anchor.setLayoutY(y);
this.anchor.setOpacity(1 - (y/(height)));
this.anchor.setVisible(y < height);
this.curryArrow.setManaged(y > height*1 || this == BinOpApplyBlock.this.leftInput);
this.curryArrow.setVisible(y > height*1);
BinOpApplyBlock.this.dragShiftOuput(y - height*1.5);
}
/** Refresh visual information such as types */
public void invalidateVisualState() {
this.anchor.invalidateVisualState();
boolean validConnection = this.anchor.hasValidConnection();
this.setTranslateY(validConnection ? 0 : -9);
this.inputType.setText(validConnection ? "zyxwv" : this.anchor.getStringType());
this.typePane.setVisible(!validConnection);
}
}
/** The information about the function. */
private FunctionInfo funInfo;
private final FunInputAnchor leftInput;
private final FunInputAnchor rightInput;
private final Pane currySpacer;
/** Text label for the output type */
private final Label resTypeLabel;
/** The uncurried output type */
private Type resType;
/** The result anchor of this function. */
private final OutputAnchor output;
/** The background for curriedType labels */
private final Pane curriedOutput;
/** The space containing anchors and type labels. */
@FXML private Pane bodySpace;
public BinOpApplyBlock(ToplevelPane pane, FunctionInfo funInfo) {
super(pane);
this.loadFXML("BinOpApplyBlock");
this.funInfo = funInfo;
String name = funInfo.getDisplayName();
/* The Label in which the information of the function is displayed. */
Label functionInfo = new Label(name.substring(1, name.length() - 1));
functionInfo.getStyleClass().add("operator");
Pane infoArea = new StackPane(functionInfo);
infoArea.setMinHeight(36);
infoArea.setMaxHeight(36);
this.output = new OutputAnchor(this, new Binder("res"));
this.leftInput = new FunInputAnchor();
this.rightInput = new FunInputAnchor();
this.rightInput.curryArrow.setManaged(false);
Label arrowSpacer = new Label("->");
arrowSpacer.getStyleClass().add("curryArrow");
arrowSpacer.setVisible(false);
Pane inputSpace = new HBox(0, this.leftInput, infoArea, arrowSpacer, this.rightInput);
inputSpace.setPickOnBounds(false);
this.currySpacer = new Pane();
this.currySpacer.setPrefHeight(0);
this.currySpacer.setVisible(false);
this.resTypeLabel = new Label("");
this.resTypeLabel.setMinWidth(USE_PREF_SIZE);
this.resTypeLabel.getStyleClass().add("resultType");
this.resTypeLabel.translateYProperty().bind(this.currySpacer.heightProperty());
this.output.translateYProperty().bind(this.currySpacer.heightProperty());
VBox outputSpace = new VBox(this.currySpacer, this.resTypeLabel, this.output) {
@Override
public double computePrefHeight(double width) {
return currySpacer.prefHeight(width) + resTypeLabel.prefHeight(width)/2;
}
};
outputSpace.setMinHeight(USE_PREF_SIZE);
outputSpace.setMaxHeight(USE_PREF_SIZE);
outputSpace.setAlignment(Pos.CENTER);
outputSpace.setPickOnBounds(false);
this.curriedOutput = new Pane() {
@Override
public double computePrefWidth(double heigth) {
return Math.max(inputSpace.prefWidth(heigth), outputSpace.getLayoutX()+resTypeLabel.prefWidth(heigth));
}
@Override
public double computePrefHeight(double width) {
return currySpacer.getHeight()*4;
}
};
this.curriedOutput.setVisible(false);
this.curriedOutput.getStyleClass().add("curriedOutput");
this.curriedOutput.setBorder(new Border(new BorderStroke(null, BorderStrokeStyle.SOLID, null, null, new Insets(-1))));
this.bodySpace.getChildren().addAll(this.curriedOutput, inputSpace, outputSpace);
outputSpace.layoutYProperty().bind(inputSpace.heightProperty());
outputSpace.layoutXProperty().bind(Bindings.max(0, inputSpace.widthProperty().divide(2).subtract(resTypeLabel.widthProperty().divide(2))));
this.curriedOutput.translateYProperty().bind(inputSpace.heightProperty());
}
/** @return a Map of class-specific properties of this Block. */
@Override
protected Map<String, Object> toBundleFragment() {
return ImmutableMap.of(
"curriedArgs", new boolean[]{this.leftInput.curried, this.rightInput.curried},
"funInfo", this.funInfo.toBundleFragment());
}
/** return a new instance of this Block type deserializing class-specific properties used in constructor **/
public static BinOpApplyBlock fromBundleFragment(ToplevelPane pane, Map<String,Object> bundleFragment) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
List<Boolean> curriedArgs = (ArrayList<Boolean>)bundleFragment.get("curriedArgs");
Map<String, Object> funInfoBundle = (Map<String, Object>)bundleFragment.get("funInfo");
FunctionInfo funInfo = FunctionInfo.fromBundleFragment(funInfoBundle);
BinOpApplyBlock binOpApplyBlock = new BinOpApplyBlock(pane, funInfo);
binOpApplyBlock.leftInput.curried = curriedArgs.get(0);
binOpApplyBlock.rightInput.curried = curriedArgs.get(1);
return binOpApplyBlock;
}
private void dragShiftOuput(double y) {
double shift = Math.max(0, y);
if (this.leftInput.curried || this.rightInput.curried) {
this.curriedOutput.setVisible(true);
this.curriedOutput.requestLayout();
} else {
this.currySpacer.setPrefHeight(shift);
this.curriedOutput.setVisible(false);
this.curriedOutput.requestLayout();
}
}
public FunctionInfo getFunInfo() {
return this.funInfo;
}
@Override
public List<InputAnchor> getAllInputs() {
return ImmutableList.of(this.leftInput.anchor, this.rightInput.anchor);
}
@Override
public List<OutputAnchor> getAllOutputs() {
return ImmutableList.of(this.output);
}
@Override
public Optional<Block> getNewCopy() {
if (this.leftInput.curried || this.rightInput.curried) {
return Optional.empty();
}
return Optional.of(new BinOpApplyBlock(this.getToplevel(), this.funInfo));
}
@Override
protected void refreshAnchorTypes() {
FunType ft1 = (FunType)funInfo.getFreshSignature();
FunType ft2 = (FunType) ft1.getResult();
Type al = ft1.getArgument();
Type ar = ft2.getArgument();
Type rt = ft2.getResult();
TypeScope scope = new TypeScope();
this.leftInput.anchor.setFreshRequiredType(al, scope);
this.rightInput.anchor.setFreshRequiredType(ar, scope);
this.resType = rt.getFresh(scope);
Type curriedType = this.resType;
if (this.rightInput.curried) {
curriedType = new FunType (this.rightInput.anchor.getType(), curriedType);
}
if (this.leftInput.curried) {
curriedType = new FunType (this.leftInput.anchor.getType(), curriedType);
}
this.output.setExactRequiredType(curriedType);
}
@Override
public Expression getLocalExpr(Set<OutputAnchor> outsideAnchors) {
Expression expr = new FunVar(this.funInfo);
List<Binder> curriedArgs = new ArrayList<>();
if (this.leftInput.curried) {
Binder ca = new Binder("ca");
curriedArgs.add(ca);
expr = new Apply(expr, new LocalVar(ca));
} else {
expr = new Apply(expr, this.leftInput.anchor.getLocalExpr(outsideAnchors));
}
if (this.rightInput.curried) {
Binder ca = new Binder("ca");
curriedArgs.add(ca);
expr = new Apply(expr, new LocalVar(ca));
} else {
expr = new Apply(expr, this.rightInput.anchor.getLocalExpr(outsideAnchors));
}
if (curriedArgs.isEmpty()) {
return expr;
} else {
return new Lambda(curriedArgs, expr);
}
}
@Override
public void invalidateVisualState() {
this.resTypeLabel.setText(this.resType.prettyPrint());
this.leftInput.invalidateVisualState();
this.rightInput.invalidateVisualState();
this.output.invalidateVisualState();
}
@Override
public String toString() {
return funInfo.getName();
}
}