package audio

import audio.Bass.BASS_STREAMPROC_END
import audio.BassEnc.BASS_ENCODE_PCM
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import org.tinylog.Logger
import java.io.PipedInputStream
import java.io.PipedOutputStream
import java.net.DatagramPacket
import java.net.DatagramSocket
import java.net.InetSocketAddress
import java.util.function.BiConsumer
import java.util.function.Consumer

@Deprecated("Sepertinya gak jadi pake")
@Suppress("unused")
/**
 * UDPReceiverToFile is a class that listens for UDP packets on a specified address and port
 * and writes the received data to a specified file.
 * It is designed to run in a separate thread and can be stopped when no longer needed.
 * @param listeningAddress The address to listen for incoming UDP packets.
 * @param listeningPort The port to listen for incoming UDP packets.
 * @param samplingrate The sampling rate for the audio data, default is 44,100 Hz.
 * @param channel The number of audio channels, default is 1 (mono).
 * @param outputFilePath The path to the file where the received data will be written.
 * @param senderIP The IP address of the sender from which to accept packets.
 */
class UDPReceiverToFile(listeningAddress: String, listeningPort: Int, val samplingrate: Int=44100, val channel: Int=1, val outputFilePath: String, val senderIP: String) {

    private var socket: DatagramSocket? = null
    private val bass : Bass = Bass.Instance
    private val bassenc : BassEnc = BassEnc.Instance
    private var isReceiving: Boolean = false
    private val pipeIn= PipedInputStream(16*1024)   // 16K
    var isReady: Boolean = false; private set
    var bytesReceived: Long = 0; private set
    var bytesWritten: Long = 0; private set


    init {
        try{
            val socketaddress = InetSocketAddress(listeningAddress, listeningPort)
            socket = DatagramSocket(socketaddress)
            isReady = true
        } catch (e : Exception) {
            Logger.error {"Failed to create UDP socket: ${e.message}" }
        }
    }

    private val streamProc = Bass.STREAMPROC { handle, buffer, length, user ->
        try{
            val dd = ByteArray(length)
            val bytesread = pipeIn.read(dd)

            if (bytesread>0){
                buffer?.write(0, dd, 0, bytesread) // Write the data to the buffer
                bytesWritten += bytesread
                // Return the number of bytes read
                bytesread
            } else {
                // if bytesread is 0, it means the pipe is empty, return BASS_STREAMPROC_END
                BASS_STREAMPROC_END
            }
        } catch (e : Exception){
            // If an error occurs, log it and return BASS_STREAMPROC_END
            Logger.error { "STREAMPROC exception on UDPReceiverToFile $senderIP $outputFilePath" }
            BASS_STREAMPROC_END
        }
    }

    /**
     * Starts receiving data from the UDP socket and writing it to the specified file.
     * This method runs in a separate thread.
     * @param callback A BiConsumer that accepts a Boolean indicating success or failure and a String message.
     * @param udpIsReceiving A Consumer that accepts a Boolean indicating whether UDP is currently receiving
     */
    fun startReceiving(callback : BiConsumer<Boolean, String>, udpIsReceiving: Consumer<Boolean>) {
        var isReceiving = false
        if (isReady){

            val scope = CoroutineScope(Dispatchers.Default)
            scope.launch(CoroutineName("UDPReceiverToFile UDP $senderIP $outputFilePath")) {
                Logger.info { "UDPReceiverToFile started, listening on ${socket?.localSocketAddress} , saving to $outputFilePath" }

                PipedOutputStream(pipeIn).use { pipeOut ->
                    while (isReceiving) {
                        try{
                            val xx = DatagramPacket(ByteArray(1500),1500)
                            socket?.receive(xx)
                            if (xx.address.hostAddress!= senderIP) continue
                            if (xx.length < 1) continue
                            pipeOut.write(xx.data, 0, xx.length)
                            bytesReceived += xx.length
                            if (!isReceiving){
                                isReceiving = true
                                udpIsReceiving.accept(true)
                            }
                        } catch (e : Exception){
                            Logger.error { "Error receiving UDP packet: ${e.message}" }
                            continue
                        }
                    }
                }
                Logger.info { "UDPReceiverToFile ended" }
            }


            scope.launch(CoroutineName("UDPReceiverToFile BASS $senderIP $outputFilePath")) {
                bass.BASS_SetDevice(0)  // Set to No Sound device, we are not playing audio
                val streamhandle = bass.BASS_StreamCreate(samplingrate, channel, 0, streamProc, null)
                if (streamhandle!=0){
                    bass.BASS_ChannelPlay(streamhandle,false)
                    val encodehandle = bassenc.BASS_Encode_Start(streamhandle, outputFilePath, BASS_ENCODE_PCM, null, null)
                    if (encodehandle!=0){
                        Logger.info { "UDPReceiverToFile started writing to $outputFilePath" }
                        callback.accept(true, "UDPReceiverToFile started successfully, writing to $outputFilePath")
                        while (isReceiving) {
                            try {
                                delay(1000)
                            } catch (e: InterruptedException) {
                                Logger.error { "UDPReceiverToFile thread interrupted: ${e.message}" }
                                break
                            }
                        }
                        bassenc.BASS_Encode_Stop(encodehandle)
                        bass.BASS_StreamFree(streamhandle)
                        Logger.info { "UDPReceiverToFile stopped writing to $outputFilePath" }
                        callback.accept(false, "UDPReceiverToFile stopped successfully, written bytes: $bytesWritten")

                    } else {
                        callback.accept(false, "Failed to start encoding: ${bass.BASS_ErrorGetCode()}")
                    }

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






        } else callback.accept(false, "UDPReceiverToFile is not ready. Check if the socket was created successfully.")
    }

    /**
     * Stop UDPReceiverToFile from receiving data.
     */
    fun stopReceiving() {
        isReceiving = false
    }
}