package Audio;
import org.pmw.tinylog.Logger;

public class AudioPlayer {
    Bass bass;
    int deviceid = -1;

    public boolean isInited() {
        return inited;
    }

    boolean inited = false;

    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;
            }
        }
    }

    public int FindOutputDevice(String devName){
        Logger.info("Detecting Output Devices...");
        int DevNum = -1;
        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));
                if (info.name.contains(devName)){
                    DevNum = ii;
                    Logger.info(devName+ " is found at " + ii);
                    break;
                }else {ii++;}
            } else {
                break;
            }
        }
        return DevNum;
    }

    /**
     * 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 Volume
     * @param value volume value, 0-100
     */
    public void setMasterVolume(int value){
        if (value<0) value = 0;
        if (value>100) value = 100;
        if (inited){
            if (!bass.BASS_SetVolume(value/100.0f)){
                Logger.error("Failed to set Master Volume to {}, Error Code {}", value, bass.BASS_ErrorGetCode());
            }
        } else Logger.info("AudioPlayer not initialized");
    }

    /**
     * Get Master Volume
     * @return 0 - 100, or -1 if failed
     */
    public int getMasterVolume(){
        if (inited){
            float vol = bass.BASS_GetVolume();
            if (vol>=0 && vol<=1){
               return  (int)(vol*100);
            }
        } else Logger.info("AudioPlayer not initialized");
        return -1;
    }

    /**
     * Open Audio File
     * @param filename audio file name to open
     * @return AudioFileProperties object or null if failed
     */
    public AudioFileProperties OpenAudioFile(String filename){
        if (inited){
            int handle = bass.BASS_StreamCreateFile(false, filename, 0, 0,0);
            if (handle!=0){
                Logger.info("Audio file {} opened successfully", filename);
                AudioFileProperties prop = new AudioFileProperties(handle, filename);
                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 {}", filename, 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) {
        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.error("AudioFileProperties is null");
            } else Logger.info("AudioPlayer not initialized");
        }
    }

    public void PlayAudioFile(AudioFileProperties prop, boolean looping, PlaybackEvent event){
        if (inited){
            if (prop!=null){
                if (bass.BASS_ChannelStart(prop.handle)){
                    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");
        }
    }

}
