package nl.utwente.viskell.ui.components;
import java.util.Map;
import java.util.Optional;
import com.google.common.collect.ImmutableMap;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import nl.utwente.viskell.ghcj.GhciSession;
import nl.utwente.viskell.haskell.type.*;
import nl.utwente.viskell.ui.ToplevelPane;
import nl.utwente.viskell.ui.serialize.Bundleable;
/** This variant of the ValueBlock uses QuickCheck to generate values based on the output type. */
public class ArbitraryBlock extends ValueBlock implements Bundleable {
/** The button for getting the next randomly generated value */
@FXML private Button rngTrigger;
/** The last type a for which a value has been generated, or empty is this block has no value. */
private Optional<Type> lastGenType;
/**
* Constructs a new ArbitraryBlock
* @param pane The parent pane this Block resides on.
*/
public ArbitraryBlock(ToplevelPane pane) {
super("ArbitraryBlock", pane, pane.getEnvInstance().buildType("Arbitrary a => a"));
this.rngTrigger.setOnAction(event -> this.getNextValue(event.hashCode(), true));
this.lastGenType = Optional.empty();
this.output.refreshType(new TypeScope());
this.getNextValue(this.hashCode(), false);
}
@Override
protected Map<String, Object> toBundleFragment() {
return ImmutableMap.of("value", getValue());
}
public static ArbitraryBlock fromBundleFragment(ToplevelPane pane, Map<String,Object> bundleFragment) {
ArbitraryBlock arbitraryBlock = new ArbitraryBlock(pane);
arbitraryBlock.setValue((String)bundleFragment.get("value"));
return arbitraryBlock;
}
private void getNextValue(int seed, boolean fromClick) {
Type outputType = this.output.getType(Optional.empty());
if (! this.output.hasConnection()) {
this.setValue("??");
this.lastGenType = Optional.empty();
return;
}
if (outputType instanceof TypeVar) {
TypeVar tv = (TypeVar)outputType;
if (!(tv.hasConcreteInstance() || tv.getConstraints().count() > 1)) {
this.setValue("???");
this.lastGenType = Optional.empty();
return;
}
}
Optional<Type> defType = outputType.getFresh().defaultedConcreteType(Type.con("Bool"));
Type type = defType.orElse(outputType);
if (this.lastGenType.isPresent() && !fromClick) {
try {
TypeChecker.unify("arbitrary type changed", this.lastGenType.get().getFresh(), type.getFresh());
// no incompatible type change, keep current value
return;
} catch (HaskellTypeError e) {
// time for a new generated value
}
}
this.setValue("???");
this.lastGenType = Optional.empty();
// we cannot generate values for polymorphic types and we don't try to for function types
if (type instanceof TypeVar || type instanceof FunType) {
return;
} else if (type instanceof TypeApp) {
for (Type xt : ((TypeApp)type).asFlattenedAppChain()) {
if (! (xt instanceof TypeCon)) {
return;
}
}
}
// now let QuickCheck try to generate an arbitrary value of this type
this.lastGenType = Optional.of(type);
GhciSession ghci = this.getToplevel().getGhciSession();
int genOffset = 2 + Math.abs(seed) % 7;
String haskellType = type.prettyPrint(10);
ListenableFuture<String> result = ghci.pullRaw("fmap (!!" + genOffset + ") $ sample' (arbitrary :: Gen " + haskellType + ")");
Futures.addCallback(result, new FutureCallback<String>() {
public void onSuccess(String s) {
// Can't call setOutput directly - this may not be JavaFX app thread.
// Instead, schedule setting the output.
Platform.runLater(() -> {
ArbitraryBlock.this.setValue(s);
// propagate the new generated value
ArbitraryBlock.this.initiateConnectionChanges();
});
}
public void onFailure(Throwable throwable) {
Platform.runLater(() -> ArbitraryBlock.this.setValue("..."));
}
});
}
@Override
public void invalidateVisualState() {
this.getNextValue(5, false);
super.invalidateVisualState();
}
@Override
public Optional<Block> getNewCopy() {
return Optional.of(new ArbitraryBlock(this.getToplevel()));
}
}