package cz.jzitnik.tui;

import lombok.extern.slf4j.Slf4j;

import javax.sound.sampled.*;
import java.io.IOException;

/**
 * The main sound provider for the game.
 * <p>
 * This class is responsible for loading and playing all in-game sounds
 * from the {@code resources/sounds/} directory using the Java Sound API.
 * It allows different sound categories to have individual volume levels,
 * while also respecting a global master volume.
 * </p>
 */
@Slf4j
public class SoundPlayer {
    /**
     * Plays a sound file from the resources directory.
     * <p>
     * The method supports 16-bit PCM signed audio. It converts the input audio
     * format to a standard PCM format, applies volume adjustment based on both
     * the backend category volume and the user-defined master volume, and plays
     * the result through the system's audio output.
     * </p>
     *
     * @param filePath      the relative file path of the sound (within the {@code sounds/} folder),
     *                      e.g. {@code "step.ogg"}
     * @param backendVolume the volume specific to the sound category (e.g. footsteps vs. mining),
     *                      ranging from 0 (mute) to 100 (full volume)
     * @param masterVolume  the global volume defined in the game options, ranging from 0 to 100
     * @throws LineUnavailableException        if a suitable audio line cannot be opened
     * @throws IOException                     if an I/O error occurs during reading
     * @throws UnsupportedAudioFileException   if the audio format is not supported
     */
    public static void playSound(String filePath, int backendVolume, int masterVolume)
            throws LineUnavailableException, IOException, UnsupportedAudioFileException {
        if (!filePath.endsWith(".ogg") || masterVolume == 0) {
            return; // No sound if master volume is 0
        }

        log.info("Loading resource: {}", "sounds/" + filePath);
        var file = SoundPlayer.class.getClassLoader().getResourceAsStream("sounds/" + filePath);
        AudioInputStream audioStream = AudioSystem.getAudioInputStream(file);
        AudioFormat baseFormat = audioStream.getFormat();

        AudioFormat targetFormat = new AudioFormat(
                AudioFormat.Encoding.PCM_SIGNED,
                baseFormat.getSampleRate(),
                16,
                baseFormat.getChannels(),
                baseFormat.getChannels() * 2,
                baseFormat.getSampleRate(),
                false
        );

        AudioInputStream dataIn = AudioSystem.getAudioInputStream(targetFormat, audioStream);

        byte[] buffer = new byte[8192];
        DataLine.Info info = new DataLine.Info(SourceDataLine.class, targetFormat);

        try (SourceDataLine line = (SourceDataLine) AudioSystem.getLine(info)) {
            if (line != null) {
                line.open(targetFormat);
                line.start();

                float finalVolume = (backendVolume / 100.0f) * (masterVolume / 100.0f);
                log.info("Applying volume: {} (backend: {}, master: {})", finalVolume, backendVolume, masterVolume);

                int bytesRead;
                while ((bytesRead = dataIn.read(buffer, 0, buffer.length)) != -1) {
                    applyVolume(buffer, bytesRead, finalVolume);
                    line.write(buffer, 0, bytesRead);
                }

                line.drain();
            }
        }

        dataIn.close();
        audioStream.close();
    }
    /**
     *
     * Applies volume adjustment to a buffer of 16-bit PCM audio samples.
     * <p>
     * This method modifies the input byte buffer in place by scaling each 16-bit
     * signed sample by the given volume factor. Each sample is assumed to be in
     * little-endian byte order (least significant byte first).
     * </p>
     *
     * @param buffer    the byte array containing 16-bit PCM audio samples
     * @param bytesRead the number of bytes read into the buffer; should be a multiple of 2
     * @param volume    the volume multiplier (e.g., 0.5 for half volume, 2.0 for double volume)
     */
    private static void applyVolume(byte[] buffer, int bytesRead, float volume) {
        for (int i = 0; i < bytesRead; i += 2) { // 16-bit PCM samples are 2 bytes each
            int sample = (buffer[i] & 0xFF) | (buffer[i + 1] << 8);
            sample = (int) (sample * volume);
            buffer[i] = (byte) (sample & 0xFF);
            buffer[i + 1] = (byte) ((sample >> 8) & 0xFF);
        }
    }
}
