package audio

import audio.Bass.BASS_DEVICE_ENABLED
import audio.Bass.BASS_DEVICE_INIT
import audio.Bass.BASS_POS_BYTE
import audio.Bass.BASS_STREAM_DECODE
import audio.Bass.BASS_SAMPLE_MONO
import audio.BassEnc.BASS_ENCODE_PCM
import codes.Result_Boolean_String
import codes.Somecodes.Companion.ValidFile
import codes.Somecodes.Companion.ValidString
import com.sun.jna.Memory
import com.sun.jna.Pointer
import contentCache

import org.tinylog.Logger

@Suppress("unused")
class AudioPlayer (var samplingrate: Int) {
    val bass: Bass = Bass.Instance
    val bassenc : BassEnc = BassEnc.Instance
    var initedDevice = -1




    init {
        if (samplingrate<1) samplingrate = 44100 // Default sampling rate
        Logger.info {"Bass version ${Integer.toHexString(bass.BASS_GetVersion())}"}
        Logger.info { "BassEnc version ${Integer.toHexString(bassenc.BASS_Encode_GetVersion())}" }
        InitAudio(0)    // Audio 0 is No Sound, use for reading and writing wav silently
    }

    /**
     * Initializes the audio system with the specified device ID.
     * Call it before using any audio functions.
     * @param id The device ID to initialize.
     * @return True if initialization was successful, false otherwise.
     */
    fun InitAudio(id : Int) : Boolean {

        val bdi = Bass.BASS_DEVICEINFO()
        if (bass.BASS_GetDeviceInfo(id, bdi)){
            Logger.info { "Audio ID=$id Name=${bdi.name} Driver=${bdi.driver}" }
            if (bdi.flags and BASS_DEVICE_ENABLED == 0) {
                Logger.error { "Audio ID=$id is not enabled, cannot initialize" }
                return false
            }
            if (bdi.flags and BASS_DEVICE_INIT > 0){
                Logger.info { "Audio ID=$id is already initialized" }
                if (id > 0) {
                    initedDevice = id
                    Logger.info { "Real Audio Device reused ID=$initedDevice" }
                }
                return true
            }
        }

        if (bass.BASS_Init(id, 48000, 0)){
            Logger.info { "Audio ID=$id inited succesfully" }
            if (id > 0) {
                initedDevice = id
                Logger.info { "Real Audio Device used ID=$initedDevice" }
            }
            return true
        } else {
            Logger.error { "Audio ID=$id initialization failed: ${bass.BASS_ErrorGetCode()}" }
            return false
        }
    }

    /**
     * Uninitializes the audio system if it was previously initialized.
     */
    fun Close(){
        if (initedDevice != -1) {
            bass.BASS_SetDevice(initedDevice)
            if (bass.BASS_Free()) {
                Logger.info {"Audio ID=$initedDevice uninitialized successfully"}
            } else {
                Logger.error { "Audio ID=$initedDevice uninitialization failed: ${bass.BASS_ErrorGetCode()}"  }
            }
            initedDevice = -1
        }
    }

    /**
     * Loads an audio file and retrieves its information.
     * Check for isValid() method in AudioFileInfo class to verify the loaded file.
     * @param fileName The name of the audio file to load.
     * @return An AudioFileInfo object containing the file information.
     */
    fun LoadAudioFile(fileName: String) : AudioFileInfo {
        val result = AudioFileInfo()
        if (ValidFile(fileName)){
            result.fileName = fileName
            bass.BASS_SetDevice(0) // Set to No Sound device for reading
            val handle = bass.BASS_StreamCreateFile(false, fileName, 0L, 0L, BASS_STREAM_DECODE or BASS_SAMPLE_MONO)
            if (handle!=0){
                // successfully opened the file
                // read file size
                val size = bass.BASS_ChannelGetLength(handle, BASS_POS_BYTE)
                if (size > 0) {
                    result.fileSize = size
                }
                // read file duration
                val duration = bass.BASS_ChannelBytes2Seconds(handle, size)
                if (duration > 0) {
                    result.duration = duration
                }
                // read file bytes
                val mem = Memory(size)
                val bytesRead = bass.BASS_ChannelGetData(handle, mem, size.toInt())
                if (bytesRead > 0) {
                    result.bytes = mem.getByteArray(0, bytesRead)
                }

                // close the handle
                bass.BASS_StreamFree(handle)

            }
        }
        return result
    }

    /**
     * Writes the audio data from a byte array to a WAV file.
     * @param data The byte array containing the audio data.
     * @param target The target file name for the WAV file.
     * @param withChime If true, adds a chime sound at the beginning and end of the audio.
     * @return A Result_Boolean_String indicating success or failure and a message.
     */
    fun WavWriter(data: ByteArray, target: String, withChime: Boolean = true) : Result_Boolean_String {
        bass.BASS_SetDevice(0) // Set to No Sound device for writing
        val streamhandle = bass.BASS_StreamCreate(samplingrate, 1, BASS_STREAM_DECODE, Pointer(-1), null)

        if (streamhandle!=0){
            val encodehandle = bassenc.BASS_Encode_Start(streamhandle, target, BASS_ENCODE_PCM, null, null)
            if (encodehandle!=0){

                fun pushData(data: ByteArray): Boolean {
                    val mem = Memory(data.size.toLong())
                    mem.write(0, data, 0, data.size)
                    val pushresult = bass.BASS_StreamPutData(streamhandle, mem, data.size)
                    if (pushresult==-1){
                        Logger.error { "BASS_StreamPutData failed: ${bass.BASS_ErrorGetCode()}" }
                    }
                    return pushresult != -1
                }

                var all_success = true

                if (withChime){
                    val chup = contentCache.getAudioFile("chimeup")
                    if (chup!=null && chup.isValid()){
                        if (!pushData(chup.bytes)){
                            all_success = false
                            Logger.error { "Failed to push Chime Up" }
                        }
                    } else Logger.error { "withChime=true, but Chime Up not available" }
                }

                if (!pushData(data)){
                    all_success = false
                    Logger.error { "Failed to push Data ByteArray" }
                }


                if (withChime){
                    val chdn = contentCache.getAudioFile("chimedown")
                    if (chdn!=null && chdn.isValid()){
                        if (!pushData(chdn.bytes)){
                            all_success = false
                            Logger.error { "Failed to push Chime Down" }
                        }
                    } else Logger.error { "withChime=true, but Chime Down not available" }
                }

                val readsize: Long = 1024 * 1024 // read 1 MB at a time
                var totalread: Long = 0
                do{
                    val p = Memory(readsize)
                    val read = bass.BASS_ChannelGetData(streamhandle, p, 4096)
                    if (read > 0) {
                        totalread += read
                    }
                } while (read > 0)
                bassenc.BASS_Encode_Stop(encodehandle)
                bass.BASS_StreamFree(streamhandle)

                return if (all_success){
                    Result_Boolean_String(true, "WAV file written successfully: $target")
                } else {
                    Result_Boolean_String(false, "Failed to write some data to WAV file: $target")
                }


            } else {
                bass.BASS_StreamFree(streamhandle)
                return Result_Boolean_String(false, "Failed to start encoding: ${bass.BASS_ErrorGetCode()}")
            }

        } else {
            return Result_Boolean_String(false, "Failed to create stream: ${bass.BASS_ErrorGetCode()}")
        }



    }

    /**
     * Writes the audio data from the sources to a WAV file.
     * @param sources List of AudioFileInfo objects containing the audio data to write.
     * @param target The target file name for the WAV file.
     * @param withChime If true, adds a chime sound at the beginning and end of the audio.
     * @return A Result_Boolean_String indicating success or failure and a message.
     */
    fun WavWriter(sources: List<AudioFileInfo>, target: String, withChime: Boolean = true) : Result_Boolean_String {
        if (sources.isEmpty()) {
            return Result_Boolean_String(false,"Invalid Source")
        }
        if (!ValidString(target)) {
            return Result_Boolean_String(false, " Invalid target file name")
        }

        bass.BASS_SetDevice(0) // Set to No Sound device for writing
        val streamhandle = bass.BASS_StreamCreate(samplingrate, 1, BASS_STREAM_DECODE, Pointer(-1), null)
        if (streamhandle==0){
            return Result_Boolean_String(false, "Failed to create stream: ${bass.BASS_ErrorGetCode()}")
        }
        val encodehandle = bassenc.BASS_Encode_Start(streamhandle, target, BASS_ENCODE_PCM, null, null)
        if (encodehandle==0){
            bass.BASS_StreamFree(streamhandle)
            return Result_Boolean_String(false, "Failed to start encoding: ${bass.BASS_ErrorGetCode()}")
        }

        fun pushData(data: ByteArray): Boolean {
            val mem = Memory(data.size.toLong())
            mem.write(0, data, 0, data.size)
            val pushresult = bass.BASS_StreamPutData(streamhandle, mem, data.size)
            if (pushresult==-1){
                Logger.error { "BASS_StreamPutData failed: ${bass.BASS_ErrorGetCode()}" }
            }
            return pushresult != -1
        }



        var allsuccess = true
        if (withChime){
            val chup = contentCache.getAudioFile("chimeup")
            if (chup!=null && chup.isValid()){
                if (!pushData(chup.bytes)){
                    allsuccess = false
                    Logger.error { "Failed to push Chime Up" }
                }
            } else Logger.error { "withChime=true, but Chime Up not available" }
        }
        sources.forEach { source ->
            if (source.isValid()) {
                // write the bytes to the stream
                if (!pushData(source.bytes)){
                    allsuccess = false
                    Logger.error { "Source ${source.fileName} push failed" }
                }

            } else {
                allsuccess = false
                Logger.error { "Not pushing Source=${source.fileName} because invalid" }
            }

        }
        if (withChime){
            val chdn = contentCache.getAudioFile("chimedown")
            if (chdn!=null && chdn.isValid()){
                if (!pushData(chdn.bytes)){
                    allsuccess = false
                    Logger.error { "Failed to push Chime Down" }
                }
            } else Logger.error { "withChime=true, but Chime Down not available"}
        }

        val readsize: Long = 1024 * 1024 // read 1 MB at a time
        var totalread: Long = 0
        do{
            val p = Memory(readsize)
            val read = bass.BASS_ChannelGetData(streamhandle, p, 4096)
            if (read > 0) {
                totalread += read
            }
        } while (read > 0)


        // close the encoding handle
        bassenc.BASS_Encode_Stop(encodehandle)
        bass.BASS_ChannelFree(streamhandle)
        return if (allsuccess){

            Result_Boolean_String(true, "WAV file written successfully: $target")
        } else {
            Result_Boolean_String(false, "Failed to write some data to WAV file: $target")
        }



    }


}