import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;

import java.io.File;
import java.nio.file.*;
import java.time.LocalTime;

public class MediaPlayerFileHandleDemo extends Application {

    private MediaPlayer player;
    private File selectedFile;

    private final TextArea log = new TextArea();
    private final Label fileLabel = new Label("No file selected");

    @Override
    public void start(final Stage stage) {
        this.log.setEditable(false);
        this.log.setPrefRowCount(18);

        final Button choose = new Button("1) Choose media file");
        choose.setOnAction(e -> chooseFile(stage));

        final Button createAndPlay = new Button("2) Create + Play (short)");
        createAndPlay.setOnAction(e -> createAndPlayShort());

        final Button stopDispose = new Button("3) Stop + Dispose");
        stopDispose.setOnAction(e -> stopAndDispose());

        final Button rename = new Button("4) Rename file (tests handle lock)");
        rename.setOnAction(e -> tryRename());

        final Button delete = new Button("5) Delete file (tests handle lock)");
        delete.setOnAction(e -> tryDelete());

        final HBox row1 = new HBox(10, choose, createAndPlay, stopDispose);
        final HBox row2 = new HBox(10, rename, delete);
        row1.setPadding(new Insets(10));
        row2.setPadding(new Insets(10));

        final VBox root = new VBox(10,
                new VBox(5, new Label("Selected:"), this.fileLabel),
                row1,
                row2,
                new Separator(),
                new Label("Log:"),
                this.log);
        root.setPadding(new Insets(10));

        stage.setScene(new Scene(root, 900, 500));
        stage.setTitle("JavaFX MediaPlayer File Handle Demo");
        stage.show();

        stage.setOnCloseRequest(e -> {
            safeStopDispose();
            Platform.exit();
        });
    }

    private void chooseFile(final Stage stage) {
        final FileChooser fc = new FileChooser();
        fc.setTitle("Pick an audio/video file (mp3/mp4/wav/...)");
        final File f = fc.showOpenDialog(stage);
        if (f != null) {
            this.selectedFile = f;
            this.fileLabel.setText(f.getAbsolutePath());
            log("Selected: " + f);
        }
    }

    private void createAndPlayShort() {
        if (!ensureFile()) {
            return;
        }

        safeStopDispose(); // clean up existing instance first

        try {
            final String uri = this.selectedFile.toURI().toString();
            final Media media = new Media(uri);
            this.player = new MediaPlayer(media);

            this.player.setOnError(() -> log("MediaPlayer error: " + this.player.getError()));
            media.setOnError(() -> log("Media error: " + media.getError()));

            // Play briefly, then stop (to ensure pipeline actually opened the file)
            this.player.play();
            log("player.play() called");

            // Stop after ~700ms (enough to trigger file open)
            new Thread(() -> {
                try {
                    Thread.sleep(700);
                } catch (final InterruptedException ignored) {}
                Platform.runLater(() -> {
                    if (this.player != null) {
                        this.player.stop();
                        log("player.stop() called (after short play)");
                    }
                });
            }, "StopLater").start();

        } catch (final Exception ex) {
            log("Create/play failed: " + ex);
        }
    }

    private void stopAndDispose() {
        if (this.player == null) {
            log("No player to dispose");
            return;
        }

        try {
            this.player.stop();
            log("player.stop()");

            this.player.dispose();
            log("player.dispose()");

            this.player = null;

            // System.gc();
            // log("System.gc() requested");

        } catch (final Exception ex) {
            log("Stop/dispose failed: " + ex);
        }
    }

    private void tryRename() {
        if (!ensureFile()) {
            return;
        }

        final Path p = this.selectedFile.toPath();
        final Path target = p.resolveSibling(p.getFileName().toString() + ".renamed.tmp");

        try {
            Files.deleteIfExists(target);

            Files.move(p, target, StandardCopyOption.ATOMIC_MOVE);
            log("RENAME OK: " + p.getFileName() + " -> " + target.getFileName());

            // move back
            Files.move(target, p, StandardCopyOption.ATOMIC_MOVE);
            log("Rename back OK");

        } catch (final Exception ex) {
            log("RENAME FAILED (possible handle lock): " + ex.getClass().getName() + ": " + ex.getMessage());
        }
    }

    private void tryDelete() {
        if (!ensureFile()) {
            return;
        }

        final Path p = this.selectedFile.toPath();
        try {
            Files.delete(p);
            log("DELETE OK: " + p.getFileName());
            this.selectedFile = null;
            this.fileLabel.setText("No file selected");

        } catch (final Exception ex) {
            log("DELETE FAILED (possible handle lock): " + ex.getClass().getName() + ": " + ex.getMessage());
        }
    }

    private boolean ensureFile() {
        if (this.selectedFile == null) {
            log("Select a file first");
            return false;
        }
        if (!this.selectedFile.exists()) {
            log("Selected file no longer exists: " + this.selectedFile);
            return false;
        }
        return true;
    }

    private void safeStopDispose() {
        if (this.player != null) {
            try {
                this.player.stop();
            } catch (final Exception ignored) {}
            try {
                this.player.dispose();
            } catch (final Exception ignored) {}
            this.player = null;
        }
    }

    private void log(final String msg) {
        final String line = "[" + LocalTime.now().withNano(0) + "] " + msg;
        System.out.println(line);
        this.log.appendText(line + "\n");
    }

    public static void main(final String[] args) {
        launch(args);
    }
}