package skadistats.clarity.analyzer.replay;

import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.IntegerBinding;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.Slider;
import org.fxmisc.easybind.EasyBind;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import skadistats.clarity.analyzer.Main;
import skadistats.clarity.decoder.Util;
import skadistats.clarity.processor.entities.UsesEntities;
import skadistats.clarity.source.LiveSource;

import java.io.File;
import java.io.IOException;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;

@UsesEntities
public class ReplayController {

    protected final Logger log = LoggerFactory.getLogger(getClass());

    private final Slider slider;
    private Timer timer;

    private Property<PropertySupportRunner> runner = new SimpleObjectProperty<>();

    private IntegerBinding tick = Bindings.selectInteger(
            EasyBind.select(runner)
                    .selectObject(PropertySupportRunner::tickProperty)
                    .orElse(0)
    );

    private IntegerBinding lastTick = Bindings.selectInteger(
            EasyBind.select(runner)
                    .selectObject(PropertySupportRunner::lastTickProperty)
                    .orElse(0)
    );

    private BooleanProperty playing = new SimpleBooleanProperty(false);

    public ReplayController(Slider slider) {
        this.slider = slider;
        slider.maxProperty().bind(lastTick);
        playing.addListener(this::playingStateChanged);
        slider.valueProperty().addListener(this::sliderValueChanged);
        tick.addListener(this::tickChanged);
        Main.primaryStage.setOnCloseRequest(event -> haltIfRunning());
    }

    private void playingStateChanged(ObservableValue<? extends Boolean> v, Boolean o, Boolean n) {
        if (n) {
            if (timer == null) {
                timer = new Timer();
                timer.scheduleAtFixedRate(new TickingTask(), 0L, 1000L / 30L);
            }
        } else {
            if (timer != null) {
                timer.cancel();
                timer = null;
            }
        }
    }

    private void sliderValueChanged(ObservableValue<? extends Number> v, Number o, Number n) {
        double val = n.doubleValue();
        // Hack: if the value is not exactly an integer, the slider has been clicked
        if (val != Math.floor(val)) {
            getRunner().setDemandedTick(n.intValue());
        }
    }

    private void tickChanged(ObservableValue<? extends Number> v, Number o, Number n) {
        if (!slider.isValueChanging()) {
            slider.setValue(n.doubleValue());
        }
    }

    private class TickingTask extends TimerTask {
        @Override
        public void run() {
            Platform.runLater(() -> {
                if (slider.isValueChanging()) {
                    return;
                }
                PropertySupportRunner r = getRunner();
                if (r.getTick() < r.getLastTick() && !r.isResetting()) {
                    r.setDemandedTick(r.getTick() + 1);
                }
            });
        }
    }

    public ObservableEntityList load(File f) throws IOException {
        haltIfRunning();
        PropertySupportRunner r = new PropertySupportRunner(new LiveSource(f.getAbsoluteFile().toString(), 10, TimeUnit.SECONDS));
        ObservableEntityList observableEntities = new ObservableEntityList(r.getEngineType());
        runner.setValue(r);
        r.runWith(this, observableEntities);
        return observableEntities;
    }

    public void haltIfRunning() {
        setPlaying(false);
        if (getRunner() != null) {
            getRunner().halt();
            try {
                getRunner().getSource().close();
            } catch (IOException e) {
                Util.uncheckedThrow(e);
            }
        }
    }

    public PropertySupportRunner getRunner() {
        return runner.getValue();
    }

    public Property<PropertySupportRunner> runnerProperty() {
        return runner;
    }

    public void setRunner(PropertySupportRunner runner) {
        this.runner.setValue(runner);
    }

    public IntegerBinding lastTickProperty() {
        return lastTick;
    }

    public IntegerBinding tickProperty() {
        return tick;
    }

    public boolean getPlaying() {
        return playing.get();
    }

    public BooleanProperty playingProperty() {
        return playing;
    }

    public void setPlaying(boolean playing) {
        this.playing.set(playing);
    }

}

@@ -773,127 +773,405 @@
 til.
-Timer;%0Aimport java.util.TimerTask;%0Aimport java.util.concurrent.TimeUnit;%0A%0A@UsesEntities%0Apublic class ReplayController %7B
+concurrent.Executors;%0Aimport java.util.concurrent.ScheduledExecutorService;%0Aimport java.util.concurrent.ScheduledFuture;%0Aimport java.util.concurrent.TimeUnit;%0A%0A@UsesEntities%0Apublic class ReplayController %7B%0A%0A    private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(r -%3E %7B%0A        Thread t = new Thread(r);%0A        t.setDaemon(true);%0A        return t;%0A    %7D);
 %0A%0A  
@@ -1284,21 +1284,34 @@
 private 
-Timer
+ScheduledFuture%3C?%3E
  timer;%0A
@@ -2430,97 +2430,261 @@
 r = 
-new Timer();%0A                timer.scheduleAtFixedRate(new TickingTask(), 0L, 1000L / 30L
+executor.scheduleAtFixedRate(%0A                        this::timerTick,%0A                        0L,%0A                        (long)(getRunner().getEngineType().getMillisPerTick() * 1000000.0f),%0A                        TimeUnit.NANOSECONDS%0A                
 );%0A 
@@ -2775,16 +2775,20 @@
 .cancel(
+true
 );%0A     
@@ -3379,87 +3379,22 @@
 ate 
-class TickingTask extends TimerTask %7B%0A        @Override%0A        public void run
+void timerTick
 () %7B
@@ -3402,20 +3402,16 @@
         
-  
-  
 Platform
@@ -3424,28 +3424,24 @@
 ter(() -%3E %7B%0A
-    
             
@@ -3484,28 +3484,24 @@
             
-  
-  
 return;%0A    
@@ -3500,36 +3500,32 @@
 rn;%0A            
-    
 %7D%0A              
@@ -3518,28 +3518,24 @@
             
- 
-   
 PropertySupp
@@ -3557,28 +3557,24 @@
 etRunner();%0A
-    
             
@@ -3646,20 +3646,16 @@
         
- 
-   
 r.setDem
@@ -3682,20 +3682,16 @@
 ) + 1);%0A
-    
         
@@ -3708,25 +3708,11 @@
     
-    %7D);%0A        %7D
+%7D);
 %0A   

use millis per tick from engine type