package Audio;

import lombok.Getter;
import org.tinylog.Logger;

import java.io.File;


public class AudioPlayer {
    private final Bass bass;
    private int deviceid = -1;
    @Getter private boolean inited = false;

    int playbackhandle = 0;
    float playbackvolume = 1.0f;

    public AudioPlayer(){
        bass = Bass.Instance;
        Logger.info("Bass Version = {}", Integer.toHexString(bass.BASS_GetVersion()));
    }

    /**
     * Unload Bass
     */
    public void Unload(){
        if (inited){
            Logger.info("Freeing Device {}", deviceid);
            bass.BASS_SetDevice(deviceid);
            bass.BASS_Free();
        }
        deviceid = -1;
        inited = false;
    }

    /**
     * Detect Output Devices
     */
    public void DetectOutputDevices(){
        Logger.info("Detecting Output Devices...");
        int ii = 1;
        while (true){
            Bass.BASS_DEVICEINFO info = new Bass.BASS_DEVICEINFO();
            if (bass.BASS_GetDeviceInfo(ii, info)){
                Logger.info("Device {} = {}, flags = {}", ii, info.name, Integer.toHexString(info.flags));
                ii++;
            } else {
                break;
            }
        }
    }

    /**
     * Get BASS_DEVICEINFO for a device
     * @param device device id
     * @return BASS_DEVICEINFO object or null if failed
     */
    public Bass.BASS_DEVICEINFO GetDeviceInfo(int device){
        Bass.BASS_DEVICEINFO info = new Bass.BASS_DEVICEINFO();
        if (bass.BASS_GetDeviceInfo(device, info)){
            return info;
        }
        return null;
    }

    /**
     * Find Device ID with Name
     * @param name device name
     * @return device id, or -1 if not found
     */
    public int FindDeviceIDWithName(String name){
        int result = -1;
        int ii = 1;
        while(true){
            Bass.BASS_DEVICEINFO info = new Bass.BASS_DEVICEINFO();
            if (bass.BASS_GetDeviceInfo(ii, info)){
                if (info.name.contains(name)){
                    result = ii;
                    break;
                }
                ii++;
            } else {
                // gak ada lagi
                break;
            }
        }
        return  result;
    }

    /**
     * Open Output Device
     * @param device device id, starts from 1
     * @param freq output frequency
     * @return true if success
     */
    public boolean OpenDevice(int device, int freq){
        int flag = Bass.BASS_DEVICE_REINIT | Bass.BASS_DEVICE_16BITS | Bass.BASS_DEVICE_MONO | Bass.BASS_DEVICE_FREQ;
        boolean success =  bass.BASS_Init(device, freq, flag);
        if (success){
            Logger.info("Device {} opened successfully", device);
            deviceid = device;
            inited = true;
        } else {
            Logger.error("Failed to open device {}, Error Code {}", device, bass.BASS_ErrorGetCode());
        }
        return success;
    }

    /**
     * Set Master Output Volume
     * Master Output Volume related to the system volume (Alsamixer on Linux, Volume Mixer on Windows)
     * @param value volume value, 0-100
     */
    public void setMasterVolume(int value){
        if (value<0) value = 0;
        if (value>100) value = 100;
        if (inited){
            bass.BASS_SetDevice(deviceid);
            if (!bass.BASS_SetVolume(value/100.0f)){
                Logger.error("Failed to set Master Volume to {}, Error Code {}", value, bass.BASS_ErrorGetCode());
            } else Logger.info("Master Volume set to {}", value);
        } else Logger.info("AudioPlayer not initialized");
    }

    /**
     * Get Master Output Volume
     * Master Output Volume related to the system volume (Alsamixer on Linux, Volume Mixer on Windows)
     * @return 0 - 100, or -1 if failed
     */
    @SuppressWarnings("unused")
    public int getMasterVolume(){
        if (inited){
            bass.BASS_SetDevice(deviceid);
            float vol = bass.BASS_GetVolume();
            if (vol>=0 && vol<=1){
               return  (int)(vol*100);
            }
        } else Logger.info("AudioPlayer not initialized");
        return -1;
    }

    /**
     * Get Playback Volume
     * is the volume of the currently playing audio file
     * @return 0 - 100
     */
    public int getPlaybackvolume(){
        if (playbackvolume<0)
            return 0;
        else if (playbackvolume>1.0f)
            return 100;
        else
            return (int)(playbackvolume*100);
    }

    /**
     * Set Playback Volume
     * is the volume of the currently playing audio file
     * @param value 0 - 100
     */
    public void setPlaybackvolume(int value){
        if (value<0) value = 0;
        if (value>100) value = 100;
        playbackvolume = value/100.0f;
        if (playbackhandle!=0){
            bass.BASS_ChannelSetAttribute(playbackhandle, Bass.BASS_ATTRIB_VOL, playbackvolume);
        }
    }

    private float lastplaybackvolume = 1.0f;

    /**
     * Set Playback Volume to 0
     */
    public void Mute(){
        lastplaybackvolume = playbackvolume;
        setPlaybackvolume(0);
    }

    /**
     * Set Playback Volume to last volume before Mute
     */
    public void Unmute(){
        setPlaybackvolume((int)lastplaybackvolume*100);
    }

    /**
     * Open Audio File
     * @param ff audio file name to open
     * @return AudioFileProperties object or null if failed
     */
    public AudioFileProperties OpenAudioFile(File ff){
        if (inited){
            int handle = bass.BASS_StreamCreateFile(false, ff.getAbsolutePath(), 0, 0,0);
            if (handle!=0){
                Logger.info("Audio file {} opened successfully", ff.getName());
                AudioFileProperties prop = new AudioFileProperties(handle, ff.getName());
                prop.Length = bass.BASS_ChannelGetLength(handle, Bass.BASS_POS_BYTE);
                prop.duration = bass.BASS_ChannelBytes2Seconds(handle, prop.Length);
                return prop;
            } else Logger.error("Failed to open audio file {}, Error Code {}", ff.getAbsolutePath(), bass.BASS_ErrorGetCode());
        } else Logger.info("AudioPlayer not initialized");
        return null;
    }

    /**
     * Close Audio File
     * @param prop AudioFileProperties object to close
     */
    public void CloseAudioFile(AudioFileProperties prop) {
        playbackhandle = 0;
        if (inited) {
            if (prop != null) {
                if (!bass.BASS_StreamFree(prop.handle)) {
                    {
                        Logger.error("Failed to close audio file {}, Error Code {}", prop.filename, bass.BASS_ErrorGetCode());
                    }
                } else Logger.info("Audio file {} closed successfully", prop.filename);
            } else Logger.info("AudioPlayer not initialized");
        }
    }

    /**
     * Play Audio File
     * @param prop AudioFileProperties object to play
     * @param looping set true to loop the audio file
     * @param event PlaybackEvent object to handle playback event
     */
    public void PlayAudioFile(AudioFileProperties prop, boolean looping, PlaybackEvent event){
        if (inited){
            if (prop!=null){
                if (bass.BASS_ChannelStart(prop.handle)){
                    playbackhandle = prop.handle;
                    bass.BASS_ChannelSetAttribute(prop.handle, Bass.BASS_ATTRIB_VOL, playbackvolume);
                    if (looping) bass.BASS_ChannelFlags(prop.handle, Bass.BASS_SAMPLE_LOOP, Bass.BASS_SAMPLE_LOOP);
                    if (event!=null) event.onPlaybackStart(prop);
                    int devfailsync = bass.BASS_ChannelSetSync(prop.handle, Bass.BASS_SYNC_DEV_FAIL,0, (handle, channel, data, user)->{
                        if (event!=null) event.onPlaybackFailure(prop, "Device Failure");
                    }, null);
                    if (devfailsync==0) Logger.error("Failed to set Device Failure Sync, Error Code {}", bass.BASS_ErrorGetCode());
                    int endsync = bass.BASS_ChannelSetSync(prop.handle, Bass.BASS_SYNC_END, 0, (handle, channel, data, user)->{
                        if (looping) {
                            if (event!=null) event.onPlaybackLooped(prop);
                        } else if (event!=null) event.onPlaybackFinished(prop);
                    }, null);
                    if (endsync==0) Logger.error("Failed to set End Sync, Error Code {}", bass.BASS_ErrorGetCode());
                } else {
                    if (event!=null) event.onPlaybackFailure(prop, String.format("Failed to play audio file %s, Error Code %d", prop.filename, bass.BASS_ErrorGetCode()));
                }
            } else {
                if (event!=null) event.onPlaybackFailure(null, "AudioFileProperties is null");
            }
        } else {
            if (event!=null) event.onPlaybackFailure(prop, "AudioPlayer not initialized");
        }
    }

}
