package ru.gravit.launcher.gui.buttons;

import javafx.animation.Animation;
import javafx.animation.Interpolator;
import javafx.animation.RotateTransition;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.control.Skin;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Arc;
import javafx.scene.shape.Circle;
import javafx.util.Duration;
import ru.gravit.launcher.LauncherAPI;

/**
 * Skin of the ring progress indicator where an arc grows and by the progress value up to 100% where the arc becomes a ring.
 *
 * @author Andrea Vacondio
 */
@LauncherAPI
public class RingProgressIndicatorSkin implements Skin<RingProgressIndicator> {

    private final RingProgressIndicator indicator;
    private final Label percentLabel = new Label();
    private final Circle innerCircle = new Circle();
    private final Circle outerCircle = new Circle();
    private final StackPane container = new StackPane();
    private final Arc fillerArc = new Arc();
    private final RotateTransition transition = new RotateTransition(Duration.millis(2000), fillerArc);

    @LauncherAPI
    public RingProgressIndicatorSkin(final RingProgressIndicator indicator) {
        this.indicator = indicator;
        initContainer(indicator);
        initFillerArc();
        container.widthProperty().addListener((o, oldVal, newVal) -> fillerArc.setCenterX(newVal.intValue() / 2));
        container.heightProperty().addListener((o, oldVal, newVal) -> fillerArc.setCenterY(newVal.intValue() / 2));
        innerCircle.getStyleClass().add("ringindicator-inner-circle");
        outerCircle.getStyleClass().add("ringindicator-outer-circle-secondary");
        updateRadii();

        this.indicator.indeterminateProperty().addListener((o, oldVal, newVal) -> initIndeterminate(newVal));
        this.indicator.progressProperty().addListener((o, oldVal, newVal) -> {
            if (newVal.intValue() >= 0) {
                fillerArc.setLength(newVal.doubleValue() * -360);
            }
        });
        this.indicator.ringWidthProperty().addListener((o, oldVal, newVal) -> updateRadii());
        innerCircle.strokeWidthProperty().addListener((e) -> updateRadii());
        innerCircle.radiusProperty().addListener((e) -> updateRadii());
        initTransition();
        initIndeterminate(indicator.isIndeterminate());
        indicator.visibleProperty().addListener((o, oldVal, newVal) -> {
            if (newVal && this.indicator.isIndeterminate()) {
                transition.play();
            } else {
                transition.pause();
            }
        });
        container.getChildren().addAll(fillerArc, outerCircle, innerCircle, percentLabel);
    }

    private void initTransition() {
        transition.setAutoReverse(false);
        transition.setCycleCount(Animation.INDEFINITE);
        transition.setDelay(Duration.ZERO);
        transition.setInterpolator(Interpolator.LINEAR);
        transition.setByAngle(360);
    }

    private void initFillerArc() {
        fillerArc.setManaged(false);
        fillerArc.getStyleClass().add("ringindicator-filler");
        fillerArc.setStartAngle(90);
        fillerArc.setLength(indicator.getProgress() * -360);
    }

    private void initContainer(final RingProgressIndicator indicator) {
        container.getStylesheets().addAll(indicator.getStylesheets());
        container.getStyleClass().addAll("circleindicator-container");
        container.setMaxHeight(Region.USE_PREF_SIZE);
        container.setMaxWidth(Region.USE_PREF_SIZE);
    }

    private void updateRadii() {
        double ringWidth = indicator.getRingWidth();
        double innerCircleHalfStrokeWidth = innerCircle.getStrokeWidth() / 2;
        double innerCircleRadius = indicator.getInnerCircleRadius();
        outerCircle.setRadius(innerCircleRadius + innerCircleHalfStrokeWidth + ringWidth);
        fillerArc.setRadiusY(innerCircleRadius + innerCircleHalfStrokeWidth - 1 + (ringWidth / 2));
        fillerArc.setRadiusX(innerCircleRadius + innerCircleHalfStrokeWidth - 1 + (ringWidth / 2));
        fillerArc.setStrokeWidth(ringWidth);
        innerCircle.setRadius(innerCircleRadius);
    }

    private void initIndeterminate(boolean newVal) {
        percentLabel.setVisible(!newVal);
        if (newVal) {
            fillerArc.setLength(360);
            fillerArc.getStyleClass().add("indeterminate");
            if (indicator.isVisible()) {
                transition.play();
            }
        } else {
            fillerArc.getStyleClass().remove("indeterminate");
            fillerArc.setRotate(0);
            transition.stop();
        }
    }

    @LauncherAPI
    @Override
    public RingProgressIndicator getSkinnable() {
        return indicator;
    }

    @LauncherAPI
    @Override
    public Node getNode() {
        return container;
    }

    @LauncherAPI
    @Override
    public void dispose() {
        transition.stop();
    }

}
