package com.klq.gui.control;
import com.klq.ast.impl.Type;
import com.klq.ast.impl.expr.AExpression;
import com.klq.ast.impl.expr.ExpressionUtil;
import com.klq.ast.impl.expr.IdentifierNode;
import com.klq.ast.impl.stmt.QuestionNode;
import com.klq.ast.impl.value.UndefinedValue;
import com.klq.ast.impl.value.Value;
import com.klq.controller.Controller;
import com.klq.controller.VariableTable;
import com.klq.gui.IKLQItem;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.effect.BoxBlur;
import javafx.scene.layout.*;
import javafx.scene.paint.Paint;
import javafx.scene.text.Font;
import javafx.util.Duration;
import org.antlr.v4.runtime.misc.NotNull;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Timon on 09.03.2015.
*/
public abstract class ARenderedQuestion implements IKLQItem {
protected final Font DEFAULT_FONT = new Font("Arial", 12);
private final double EFFECT_DURATION = 500;
protected final Controller controller;
private final QuestionNode question;
private final List<AExpression> dependencies;
protected BooleanProperty visibleProperty;
private final VBox container;
private final Label label;
protected final Region renderedComponent;
protected ARenderedQuestion(QuestionNode question, List<AExpression> dependencies, Controller controller){
this.controller = controller;
this.question = question;
this.dependencies = dependencies;
visibleProperty = new SimpleBooleanProperty(dependencies.isEmpty());
container = new VBox(5);
label = createQuestionLabel();
renderedComponent = createQuestionControl();
container.getChildren().add(label);
container.getChildren().add(renderedComponent);
container.setMinHeight(50);
setBackground();
}
private Label createQuestionLabel() {
Label result = new Label(question.getText());
Font font = new Font("Arial Bold", 14);
result.setFont(font);
result.setWrapText(true);
return result;
}
private void setBackground(){
double absRadius = 10;
double relRadius = 0.05;
CornerRadii radii = new CornerRadii(
absRadius, relRadius, relRadius, absRadius,
absRadius, relRadius, relRadius, absRadius,
false, true, true, false, false, true, true, false
);
BorderStroke stroke = new BorderStroke(
Paint.valueOf("#000000"), BorderStrokeStyle.SOLID,
radii, BorderWidths.DEFAULT
);
BackgroundFill fill = new BackgroundFill(
Paint.valueOf("#EFEFEF"), radii, new Insets(1)
);
container.setPadding(new Insets(5));
container.setBorder(new Border(stroke));
container.setBackground(new Background(fill));
}
/**
* Abstract methods that need to be implemented */
protected abstract Region createQuestionControl();
/**
* Needs to accept empty Strings */
protected abstract boolean isValidInput(String input);
protected void questionAnswered(String result) {
if (result == null ||result.trim().isEmpty()){
controller.updateAnswer(question.getID(), new UndefinedValue());
} else if (isValidInput(result)) {
Value expr = ExpressionUtil.createTerminalFromString(question.getType(), result);
controller.updateAnswer(question.getID(), expr);
}
}
public Node getControl() {
return container;
}
public IdentifierNode getID() {
return question.getID();
}
public Type getType() {
return question.getType();
}
public String getText() {
return question.getText();
}
public void setVisible(boolean visible) {
if (visibleProperty.get() == visible){
return;
}
visibleProperty.set(visible);
if (visible){
show();
} else {
hide();
}
}
public boolean isVisible() {
return visibleProperty.get();
}
public List<AExpression> getDependencies() {
return dependencies;
}
public void addDependency(AExpression dependency) {
this.dependencies.add(dependency);
}
public boolean dependenciesResolved(VariableTable variableTable) {
for (AExpression dependency : dependencies){
Value eval = dependency.evaluate(variableTable);
if (eval.isUndefined()){
return false;
}
if (! (Boolean) eval.getValue()){
return false;
}
}
return true;
}
public boolean isComputed(){
return false;
}
//TODO QuestionPane gets lost, if user varies to fast bewteen accepted and not accepted input.
private void show(){
container.setManaged(true);
container.setVisible(true);
Timeline timeline = new Timeline();
timeline.getKeyFrames().addAll(createPositionTranslationFrames(false));
timeline.getKeyFrames().addAll(createBlurEffectFrames(10, false));
timeline.play();
}
private void hide(){
Timeline timeline = new Timeline();
timeline.getKeyFrames().addAll(createBlurEffectFrames(10, true));
timeline.getKeyFrames().addAll(createPositionTranslationFrames(true));
timeline.getKeyFrames().add(new KeyFrame(
Duration.millis(EFFECT_DURATION),
hide -> {
container.setManaged(false);
container.setVisible(false);
}
));
timeline.play();
}
private List<KeyFrame> createPositionTranslationFrames(boolean reverse){
List<KeyFrame> result = new ArrayList<>();
Duration first = (reverse ? new Duration(EFFECT_DURATION) : Duration.ZERO);
Duration last = (reverse ? Duration.ZERO : new Duration(EFFECT_DURATION));
result.add(new KeyFrame(first,
new KeyValue(container.translateYProperty(), -container.getHeight())));
result.add(new KeyFrame(last,
new KeyValue(container.translateYProperty(), 0)));
return result;
}
private List<KeyFrame> createBlurEffectFrames(double steps, boolean reverse){
List<KeyFrame> frames = new ArrayList<>();
for(double i=0; i<=steps; i++){
Duration duration = (reverse ? new Duration((1-i/steps)*EFFECT_DURATION)
: new Duration(i/steps*EFFECT_DURATION));
frames.add(new KeyFrame(duration,
new KeyValue(container.effectProperty(), new BoxBlur(steps-i, steps-i, 3))
)
);
}
return frames;
}
}