package cz.jzitnik.sound;

import javax.sound.sampled.*;

import javax.sound.sampled.*;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicReference;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class SoundPlayer {
    private final AtomicReference<SourceDataLine> currentLine = new AtomicReference<>();

    public void playSound(String filePath, int backendVolume, int masterVolume) {
        long threadId = Thread.currentThread().getId();

        if (!filePath.endsWith(".ogg") || masterVolume == 0) {
            return;
        }

        try (var fileStream = getClass().getClassLoader().getResourceAsStream(filePath)) {
            if (fileStream == null) {
                return;
            }

            try (AudioInputStream audioStream = AudioSystem.getAudioInputStream(fileStream)) {
                AudioFormat baseFormat = audioStream.getFormat();
                AudioFormat targetFormat = new AudioFormat(
                        AudioFormat.Encoding.PCM_SIGNED,
                        baseFormat.getSampleRate(),
                        16,
                        baseFormat.getChannels(),
                        baseFormat.getChannels() * 2,
                        baseFormat.getSampleRate(),
                        false
                );

                try (AudioInputStream dataIn = AudioSystem.getAudioInputStream(targetFormat, audioStream)) {
                    DataLine.Info info = new DataLine.Info(SourceDataLine.class, targetFormat);

                    SourceDataLine line = (SourceDataLine) AudioSystem.getLine(info);
                    currentLine.set(line);

                    if (line != null) {
                        line.open(targetFormat);

                        if (line.isControlSupported(FloatControl.Type.MASTER_GAIN)) {
                            FloatControl gainControl = (FloatControl) line.getControl(FloatControl.Type.MASTER_GAIN);
                            float finalVolume = (backendVolume / 100.0f) * (masterVolume / 100.0f);
                            float dB = (float) (Math.log10(Math.max(finalVolume, 0.0001f)) * 20.0f);
                            gainControl.setValue(dB);
                        }

                        line.start();
                        log.info("[{}] Line started.", threadId);

                        byte[] buffer = new byte[4096];
                        int bytesRead;

                        while ((bytesRead = dataIn.read(buffer, 0, buffer.length)) != -1) {
                            if (Thread.currentThread().isInterrupted() || !line.isOpen()) {
                                log.info("[{}] Loop interrupted.", threadId);
                                break;
                            }

                            try {
                                line.write(buffer, 0, bytesRead);
                            } catch (Exception e) {
                                break;
                            }
                        }

                        // Cleanup (only if not already closed)
                        if (line.isOpen()) {
                            line.drain();
                            line.stop();
                            line.close();
                        }
                    }
                } catch (LineUnavailableException | IOException e) {
                    throw new RuntimeException(e);
                }
            } catch (UnsupportedAudioFileException e) {
                throw new RuntimeException(e);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            currentLine.set(null);
        }
    }

    /**
     * Call this method from your Main Thread / VideoSceneWithAudio
     * to stop the sound INSTANTLY.
     */
    public void stopCurrentSound() {
        SourceDataLine line = currentLine.get();
        if (line != null && line.isOpen()) {
            log.info("Force stopping sound...");
            line.flush();
            line.stop();
            line.close();
        }
    }
}
