package eu.midnightdust.yaytris.util;

import eu.midnightdust.yaytris.Settings;

import javax.sound.sampled.*;
import java.io.IOException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;

public class SoundUtil {
    private static final Map<String, MusicThread> musicThreads = new HashMap<>();

    public static void playMusic(String fileLocation, boolean looped) {
        if (musicThreads.containsKey(fileLocation)) stopMusic(fileLocation);
        MusicThread musicThread = new MusicThread(fileLocation, looped);
        musicThread.start();
        musicThreads.put(fileLocation, musicThread);
    }

    public static void stopMusic(String fileLocation) {
        if (musicThreads.containsKey(fileLocation)) musicThreads.get(fileLocation).stopMusic();
    }

    // Adapted from: https://www.baeldung.com/java-play-sound
    public static void playSoundClip(String fileLocation) {
        try (AudioInputStream stream = AudioSystem.getAudioInputStream(getResource(fileLocation))) { // FIXME: Support audio files from JAR. File streams won't work here for some reason.
            AudioFormat format = stream.getFormat();
            DataLine.Info info = new DataLine.Info(Clip.class, format);

            Clip audioClip = (Clip) AudioSystem.getLine(info);
            audioClip.addLineListener(new LineUpdateListener());
            audioClip.open(stream);
            audioClip.start();
            setVolume(audioClip, Settings.soundVolume);
        } catch (LineUnavailableException | IOException | UnsupportedAudioFileException e) {
            throw new RuntimeException(e);
        }
    }

    private static URL getResource(String fileLocation) {
        return SoundUtil.class.getResource(fileLocation);
    }

    // Adapted from: https://stackoverflow.com/a/40698149
    private static void setVolume(Line line, int volume) {
        if (volume < 0 || volume > 100)
            throw new IllegalArgumentException("Volume not valid: " + volume);
        FloatControl gainControl = (FloatControl) line.getControl(FloatControl.Type.MASTER_GAIN);
        gainControl.setValue(20f * (float) Math.log10(volume/100f));
    }

    public static class MusicThread extends Thread {
        private static final int BUFFER_SIZE = 8192;
        private final boolean looped;
        private final String fileLocation;
        private boolean playing;

        MusicThread(String fileLocation, boolean looped) {
            this.fileLocation = fileLocation;
            this.looped = looped;
            this.playing = true;
        }

        public void stopMusic() {
            this.playing = false;
        }

        @Override
        public void run() {
            do {playMusic(fileLocation);} while (looped && playing);
        }

        // Adapted from: https://www.baeldung.com/java-play-sound
        private void playMusic(String fileLocation) {
            try (AudioInputStream stream = AudioSystem.getAudioInputStream(getResource(fileLocation))) {
                AudioFormat format = stream.getFormat();
                DataLine.Info info = new DataLine.Info(SourceDataLine.class, format);

                SourceDataLine sourceDataLine = (SourceDataLine) AudioSystem.getLine(info);
                sourceDataLine.open(format);
                sourceDataLine.start();
                setVolume(sourceDataLine, Settings.musicVolume);
                float fadeOut = 1.0f;

                byte[] bufferBytes = new byte[BUFFER_SIZE];
                int readBytes;
                while ((readBytes = stream.read(bufferBytes)) != -1) {
                    if (!playing) {
                        fadeOut = (float) Math.sin(fadeOut-0.01f); // Sinus fade-out
                        setVolume(sourceDataLine, (int) (fadeOut * Settings.musicVolume));
                        if (fadeOut <= 0) break;
                    }
                    sourceDataLine.write(bufferBytes, 0, readBytes);
                }

                sourceDataLine.drain();
                sourceDataLine.close();
            } catch (LineUnavailableException | IOException | UnsupportedAudioFileException e) {
                throw new RuntimeException(e);
            }
        }
    }

    public static class LineUpdateListener implements LineListener {
        @Override
        public void update(LineEvent event) {
            if (LineEvent.Type.STOP == event.getType()) {
                event.getLine().close();
            }
        }
    }
}
